Stop failing sync over already-sent campaigns still in the repo
CI / test (push) Successful in 9s

A campaign.md is meant to stay around after it's sent as a record of what
went out and when. Treating any non-draft campaign as a hard rejection
forced deleting it just to keep CI green, and there was no way to fail on
resolving a stale list/attachment reference either -- an archived
campaign has nothing left to resolve, so check status before doing any of
that work. Non-draft campaigns now report as a distinct, non-error
"skipped" bucket instead of "rejected".
This commit is contained in:
2026-08-13 13:24:39 -04:00
parent 0c8bc6bd3b
commit 34d61c2e69
4 changed files with 64 additions and 16 deletions
+33 -3
View File
@@ -479,7 +479,7 @@ func TestSyncDir_UpdatesWhenContentChanges(t *testing.T) {
}
}
func TestSyncDir_RejectsNonDraftCampaign(t *testing.T) {
func TestSyncDir_SkipsNonDraftCampaignWithoutFailing(t *testing.T) {
f := newFakeListmonk(t)
f.lists = []fakeList{{ID: 3, Name: "Newsletter"}}
f.campaigns = []fakeCampaign{{ID: 1, Name: "launch", Status: "running", Subject: "Original"}}
@@ -492,14 +492,44 @@ func TestSyncDir_RejectsNonDraftCampaign(t *testing.T) {
if err != nil {
t.Fatalf("SyncDir: %v", err)
}
if len(result.Rejected) != 1 || !strings.Contains(result.Rejected[0], "running") {
t.Fatalf("expected a rejection mentioning 'running', got %v", result.Rejected)
// A sent/running campaign is a historical record, not an error -- it
// must never land in Rejected (which fails the whole CI job) just
// because its campaign.md is still around.
if len(result.Rejected) != 0 {
t.Fatalf("expected no rejection for an already-sent campaign, got %v", result.Rejected)
}
if len(result.Skipped) != 1 || !strings.Contains(result.Skipped[0], "running") {
t.Fatalf("expected launch in Skipped mentioning 'running', got %v", result.Skipped)
}
if f.campaigns[0].Subject != "Original" {
t.Errorf("expected the live campaign's content to be untouched, got %+v", f.campaigns[0])
}
}
func TestSyncDir_NonDraftCampaignSkipsEvenWithAStaleListReference(t *testing.T) {
// A finished campaign's campaign.md might reference a list that's since
// been renamed or removed -- that must never break the build, since an
// archived campaign has nothing left to resolve.
f := newFakeListmonk(t)
f.campaigns = []fakeCampaign{{ID: 1, Name: "launch", Status: "finished", Subject: "Original"}}
lm := f.client()
root := t.TempDir()
fm := "subject: \"Big Announcement\"\nlists: [\"No Longer Exists\"]\nfrom_email: hello@example.com\n"
writeCampaignDir(t, root, "launch", fm, "Hello world.")
result, err := SyncDir(lm, root, nil)
if err != nil {
t.Fatalf("SyncDir: %v", err)
}
if len(result.Rejected) != 0 {
t.Fatalf("expected no rejection despite the stale list reference, got %v", result.Rejected)
}
if len(result.Skipped) != 1 {
t.Fatalf("expected launch in Skipped, got %v", result.Skipped)
}
}
func TestSyncDir_AmbiguousListNameRejects(t *testing.T) {
f := newFakeListmonk(t)
f.lists = []fakeList{{ID: 3, Name: "Newsletter"}, {ID: 4, Name: "Newsletter"}}