From 04c91609543d5ec66ae7a96f47441c933fe5af09 Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:57:16 -0700 Subject: [PATCH] rm segment and audience filter arg exclusivity --- cmd/campaigns.go | 2 +- cmd/campaigns_create_test.go | 36 ++++++++++++++++++++++++++++++++++++ cmd/campaigns_fields_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 14739ce..b2af86f 100644 --- a/cmd/campaigns.go +++ b/cmd/campaigns.go @@ -57,7 +57,7 @@ func addCampaignFieldFlags(cmd *cobra.Command) { cmd.Flags().String("audience-filter-file", "", "Path to a JSON file with an ad-hoc audience filter") cmd.Flags().Bool("schedule-now", false, "Send immediately when published") cmd.Flags().String("schedule-at", "", "Send at the given ISO 8601 timestamp (e.g. 2026-07-01T12:00:00Z)") - cmd.MarkFlagsMutuallyExclusive("audience-segment-id", "audience-filter", "audience-filter-file") + cmd.MarkFlagsMutuallyExclusive("audience-filter", "audience-filter-file") cmd.MarkFlagsMutuallyExclusive("schedule-now", "schedule-at") } diff --git a/cmd/campaigns_create_test.go b/cmd/campaigns_create_test.go index a2efba7..83f1bfa 100644 --- a/cmd/campaigns_create_test.go +++ b/cmd/campaigns_create_test.go @@ -83,4 +83,40 @@ func TestRunCampaignsCreate(t *testing.T) { t.Errorf("scheduling = %v", sched) } }) + + t.Run("sends audience segment and filter together", func(t *testing.T) { + got := serveJSONCapture(t, http.StatusCreated, body) + segment := "seg_1" + _, err := runCampaignsCreate(cfg(t), loops.CreateCampaignRequest{ + Name: "Spring", + AudienceSegmentID: &segment, + AudienceFilter: &loops.AudienceFilter{ + Match: "all", + Conditions: []loops.AudienceFilterCondition{ + { + Type: loops.AudienceConditionTypeProperty, + Property: &loops.PropertyCondition{Key: "plan", Operator: "equals"}, + }, + }, + }, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var sent map[string]any + if err := json.Unmarshal(got.Body, &sent); err != nil { + t.Fatalf("decode request body: %v\nraw: %s", err, got.Body) + } + if sent["audienceSegmentId"] != "seg_1" { + t.Errorf("audienceSegmentId = %v, want seg_1", sent["audienceSegmentId"]) + } + filter, ok := sent["audienceFilter"].(map[string]any) + if !ok { + t.Fatalf("audienceFilter not an object: %v", sent["audienceFilter"]) + } + if filter["match"] != "all" { + t.Errorf("audienceFilter.match = %v, want all", filter["match"]) + } + }) } diff --git a/cmd/campaigns_fields_test.go b/cmd/campaigns_fields_test.go index a0d824a..e5d37f2 100644 --- a/cmd/campaigns_fields_test.go +++ b/cmd/campaigns_fields_test.go @@ -226,6 +226,9 @@ func TestCampaignFieldParamsFromCmd(t *testing.T) { if err := cmd.ParseFlags([]string{"--audience-filter", "{}", "--audience-filter-file", "f.json"}); err != nil { t.Fatalf("ParseFlags: %v", err) } + if err := cmd.ValidateFlagGroups(); err == nil { + t.Error("ValidateFlagGroups: expected mutual-exclusion error, got nil") + } _, err := campaignFieldParamsFromCmd(cmd) if err == nil { t.Fatal("expected error, got nil") @@ -245,6 +248,30 @@ func TestCampaignFieldParamsFromCmd(t *testing.T) { } }) + t.Run("audience-segment-id and audience-filter together set both fields", func(t *testing.T) { + cmd := newCampaignFieldCmd() + filter := `{"match":"all","conditions":[{"type":"property","key":"plan","operator":"equals","value":"pro"}]}` + if err := cmd.ParseFlags([]string{"--audience-segment-id", "seg_1", "--audience-filter", filter}); err != nil { + t.Fatalf("ParseFlags: %v", err) + } + if err := cmd.ValidateFlagGroups(); err != nil { + t.Fatalf("ValidateFlagGroups: %v", err) + } + p, err := campaignFieldParamsFromCmd(cmd) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if p.AudienceSegmentID == nil || *p.AudienceSegmentID != "seg_1" { + t.Errorf("AudienceSegmentID = %v, want pointer to seg_1", p.AudienceSegmentID) + } + if p.AudienceFilter == nil || p.AudienceFilter.Match != "all" { + t.Errorf("AudienceFilter = %v, want match=all filter", p.AudienceFilter) + } + if !p.Set["audienceSegmentId"] || !p.Set["audienceFilter"] { + t.Errorf("Set = %v, want both audienceSegmentId and audienceFilter", p.Set) + } + }) + t.Run(`mailing-list-id "null" sentinel clears the field`, func(t *testing.T) { cmd := newCampaignFieldCmd() if err := cmd.ParseFlags([]string{"--mailing-list-id", "null"}); err != nil {