package campaign import ( "os" "path/filepath" "strings" "testing" ) func writeFile(t *testing.T, path, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatal(err) } } func TestParseFile_ParsesFrontmatterAndBody(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- subject: "Big Announcement" lists: ["Newsletter"] from_email: hello@reground.org tags: ["announcement"] attachments: - assets/one-pager.pdf --- Hello **world**. `) fm, body, err := ParseFile(path) if err != nil { t.Fatalf("ParseFile: %v", err) } if fm.Subject != "Big Announcement" { t.Errorf("unexpected subject: %q", fm.Subject) } if len(fm.Lists) != 1 || fm.Lists[0] != "Newsletter" { t.Errorf("unexpected lists: %v", fm.Lists) } if fm.Type != "regular" { t.Errorf("expected type to default to regular, got %q", fm.Type) } if !strings.Contains(body, "Hello **world**.") { t.Errorf("unexpected body: %q", body) } } func TestParseFile_MissingFrontmatterDelimiter(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, "no frontmatter here\n") if _, _, err := ParseFile(path); err == nil { t.Fatal("expected an error for a file with no frontmatter block") } } func TestParseFile_UnterminatedFrontmatter(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, "---\nsubject: hi\nbody with no closing delimiter") if _, _, err := ParseFile(path); err == nil { t.Fatal("expected an error for unterminated frontmatter") } } func TestParseFile_MissingSubject(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- lists: ["Newsletter"] from_email: hello@reground.org --- body `) if _, _, err := ParseFile(path); err == nil || !strings.Contains(err.Error(), "subject") { t.Fatalf("expected a 'missing subject' error, got %v", err) } } func TestParseFile_MissingFromEmail(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- subject: hi lists: ["Newsletter"] --- body `) if _, _, err := ParseFile(path); err == nil || !strings.Contains(err.Error(), "from_email") { t.Fatalf("expected a 'missing from_email' error, got %v", err) } } func TestParseFile_RequiresListsOrSegmentQuery(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- subject: hi from_email: hello@reground.org --- body `) if _, _, err := ParseFile(path); err == nil || !strings.Contains(err.Error(), "target audience") { t.Fatalf("expected a 'needs a target audience' error, got %v", err) } } func TestParseFile_SegmentQueryAloneSatisfiesTargetAudience(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- subject: hi from_email: hello@reground.org segment_query: "subscribers.attribs->>'source' = 'workshop'" --- body `) if _, _, err := ParseFile(path); err != nil { t.Fatalf("expected segment_query alone to be sufficient, got %v", err) } } func TestParseFile_RejectsOptin(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, `--- subject: hi lists: ["Newsletter"] from_email: hello@reground.org type: optin --- body `) if _, _, err := ParseFile(path); err == nil || !strings.Contains(err.Error(), "optin") { t.Fatalf("expected an 'optin not supported' error, got %v", err) } } func TestParseFile_MalformedYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "campaign.md") writeFile(t, path, "---\nsubject: [unterminated\n---\nbody\n") if _, _, err := ParseFile(path); err == nil { t.Fatal("expected an error for malformed YAML frontmatter") } }