diff --git a/src/app/api/affiliates/offers/route.test.ts b/src/app/api/affiliates/offers/route.test.ts index 134e0615..73a348f3 100644 --- a/src/app/api/affiliates/offers/route.test.ts +++ b/src/app/api/affiliates/offers/route.test.ts @@ -231,6 +231,44 @@ describe("POST /api/affiliates/offers", () => { expect(insertData.title).not.toContain(""); }); + it("falls back to a non-empty slug when the title has no slug-safe characters", async () => { + mockGetAuthContext.mockResolvedValue({ user: { id: "user1" } }); + + const insertMock = vi.fn().mockReturnValue({ + select: () => ({ + single: () => Promise.resolve({ + data: { id: "new-id", slug: "offer", title: "🔥🔥🔥" }, + error: null, + }), + }), + }); + + mockFrom.mockReturnValue({ + select: () => ({ + eq: () => ({ + single: () => Promise.resolve({ data: null, error: null }), + }), + }), + insert: insertMock, + }); + + const req = new NextRequest("http://localhost/api/affiliates/offers", { + method: "POST", + body: JSON.stringify({ + title: "🔥🔥🔥", + description: "A description that is long enough for validation", + price_sats: 1000, + commission_type: "percentage", + commission_rate: 0.2, + }), + }); + + const res = await POST(req); + expect(res.status).toBe(201); + const insertData = insertMock.mock.calls[0][0]; + expect(insertData.slug).toBe("offer"); + }); + it("rejects negative commission_flat_sats (#23)", async () => { mockGetAuthContext.mockResolvedValue({ user: { id: "user1" } }); diff --git a/src/app/api/affiliates/offers/route.ts b/src/app/api/affiliates/offers/route.ts index 14c149bb..e82abb0e 100644 --- a/src/app/api/affiliates/offers/route.ts +++ b/src/app/api/affiliates/offers/route.ts @@ -20,11 +20,12 @@ function parsePaginationParam( } function slugify(text: string): string { - return text + const slug = text .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") .slice(0, 80); + return slug || "offer"; } /**