import { describe, expect, it } from "@suaveplan/testing/runner"; import { CampaignIdParamSchema, CampaignListResponseSchema, CampaignQuerySchema, CampaignResponseSchema, CampaignStatusSchema, CampaignTypeSchema, CreateCampaignSchema, EMPTY_STATS, UpdateCampaignSchema, } from "./schemas.js"; describe("campaign route schemas", () => { it("CampaignStatusSchema accepts all 7 statuses", { timeout: 5000 }, () => { for (const s of [ "draft", "scheduled", "sending", "paused", "completed", "cancelled", "failed", ] as const) { expect(CampaignStatusSchema.parse(s)).toBe(s); } expect(() => CampaignStatusSchema.parse("bogus")).toThrow(); }); it("CampaignTypeSchema accepts all 4 types", { timeout: 5000 }, () => { for (const t of [ "broadcast", "triggered", "ab_test", "drip", ] as const) { expect(CampaignTypeSchema.parse(t)).toBe(t); } expect(() => CampaignTypeSchema.parse("bogus")).toThrow(); }); it("CampaignIdParamSchema rejects empty id", { timeout: 5000 }, () => { expect(() => CampaignIdParamSchema.parse({ id: "" })).toThrow(); expect(CampaignIdParamSchema.parse({ id: "a" })).toEqual({ id: "a" }); }); it("CreateCampaignSchema applies all documented defaults", { timeout: 5000, }, () => { const parsed = CreateCampaignSchema.parse({ name: "x" }); expect(parsed.type).toBe("broadcast"); expect(parsed.channel).toBe("email"); expect(parsed.audienceConfig).toEqual({ groupIds: [] }); expect(parsed.templateData).toEqual({}); expect(parsed.metadata).toEqual({}); }); it("CreateCampaignSchema rejects empty name", { timeout: 5000 }, () => { expect(() => CreateCampaignSchema.parse({ name: "" })).toThrow(); }); it("CreateCampaignSchema accepts a full body", { timeout: 5000 }, () => { const out = CreateCampaignSchema.parse({ name: "Promo", type: "drip", channel: "sms", audienceConfig: { groupIds: ["g1"], excludeGroupIds: ["g2"], }, templateId: "tpl1", templateData: { foo: 1 }, schedule: { type: "immediate" }, throttle: { messagesPerSecond: 5 }, abTestConfig: { splitType: "even" }, metadata: { campaignId: "X" }, }); expect(out.type).toBe("drip"); expect(out.audienceConfig.groupIds).toEqual(["g1"]); }); it("UpdateCampaignSchema accepts a partial body", { timeout: 5000 }, () => { const out = UpdateCampaignSchema.parse({ name: "New" }); expect(out.name).toBe("New"); }); it("UpdateCampaignSchema rejects empty name", { timeout: 5000 }, () => { expect(() => UpdateCampaignSchema.parse({ name: "" })).toThrow(); }); it("CampaignQuerySchema coerces query strings and applies defaults", { timeout: 5000, }, () => { const out = CampaignQuerySchema.parse({ status: "draft", type: "broadcast", limit: "25", offset: "5", }); expect(out.limit).toBe(25); expect(out.offset).toBe(5); expect(out.status).toBe("draft"); }); it("CampaignQuerySchema clamps limit between 1 and 200", { timeout: 5000, }, () => { expect(() => CampaignQuerySchema.parse({ limit: 0 })).toThrow(); expect(() => CampaignQuerySchema.parse({ limit: 300 })).toThrow(); }); it("CampaignResponseSchema accepts a minimally valid response", { timeout: 5000, }, () => { const ok = CampaignResponseSchema.safeParse({ id: "1", tenantId: "t", name: "x", status: "draft", type: "broadcast", channel: "email", audienceConfig: { groupIds: [] }, templateData: {}, metadata: {}, stats: EMPTY_STATS, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); expect(ok.success).toBe(true); }); it("CampaignListResponseSchema wraps the campaign array", { timeout: 5000, }, () => { const ok = CampaignListResponseSchema.safeParse({ campaigns: [], total: 0, }); expect(ok.success).toBe(true); }); it("EMPTY_STATS exposes every zero counter", { timeout: 5000 }, () => { expect(Object.values(EMPTY_STATS).every((v) => v === 0)).toBe(true); expect(Object.keys(EMPTY_STATS)).toHaveLength(9); }); });