import { describe, expect, test } from "bun:test"; import { getPageBlockSchema, handleSectionTool, validatePayloadAgainstMetadata, } from "./section-toolkit"; const websiteBlockTypes = [ "HERO", "ANNOUNCEMENT_BAR", "NEWSLETTER", "EDITORIAL_TILE", "EDITORIAL_CAROUSEL", "PRODUCTS", "CATEGORIES", "TEXT", "BANNER", "HERO_ARTICLE", "RICH_TEXT", "SCENT_NOTES_BREAKDOWN", "QUOTE_CALLOUT", "PRODUCT_CAROUSEL", "ACCORDION", "CONTACT_INFO", "CONTACT_FORM", "HERO_V2", "USP_BAR", "CUSTOM", ] as const; describe("Page Builder block contracts", () => { for (const type of websiteBlockTypes) { test(`${type} exposes a valid recommended payload`, () => { const schema = getPageBlockSchema(type); expect(schema.renderability.websitePageRenderable).toBe(true); expect(schema.recommendedPayload).not.toBeNull(); expect(validatePayloadAgainstMetadata(type, schema.recommendedPayload).valid).toBe(true); }); } test("HERO_V2 rejects the legacy empty payload before backend writes", () => { const result = validatePayloadAgainstMetadata("HERO_V2", {}); expect(result.valid).toBe(false); expect(result.missingFields).toEqual(["title", "imageUrl"]); }); test("USP_BAR rejects an empty payload because the website renders nothing", () => { const result = validatePayloadAgainstMetadata("USP_BAR", {}); expect(result.valid).toBe(false); expect(result.missingFields).toContain("items"); }); test("CUSTOM rejects component IDs missing from the website registry", () => { const result = validatePayloadAgainstMetadata("CUSTOM", { componentId: "UnknownComponent", props: {}, }); expect(result.valid).toBe(false); expect(result.validationIssues).toContainEqual({ path: "componentId", message: "Unsupported website custom component. Use BakeryCategories or StoryFeatureMosaic.", code: "custom", }); }); test("create_page_with_section rejects invalid payloads without calling backend", async () => { let postCalls = 0; const api = { post: async () => { postCalls += 1; throw new Error("Backend must not be called"); }, } as any; const result = await handleSectionTool(api, "create_page_with_section", { title: "Invalid Hero", slug: "invalid-hero", type: "HERO_V2", payload: {}, }); expect(result && "isError" in result ? result.isError : false).toBe(true); expect(postCalls).toBe(0); }); test("create_page_with_section uses one atomic backend call and returns compact output", async () => { const calls: Array<{ url: string; payload: unknown }> = []; const api = { post: async (url: string, payload: unknown) => { calls.push({ url, payload }); return { data: { success: true, pageId: 50, sectionId: 120, page: { id: 50, slug: "hero-v2" }, section: { id: 120, type: "HERO_V2" }, }, }; }, } as any; const payload = getPageBlockSchema("HERO_V2").recommendedPayload; const result = await handleSectionTool(api, "create_page_with_section", { title: "Hero V2", slug: "hero-v2", type: "HERO_V2", payload, isPublished: false, }); const response = JSON.parse(result?.content[0]?.text ?? "{}"); expect(calls).toHaveLength(1); expect(calls[0]?.url).toBe("/pages/with-section"); expect(response).toEqual({ success: true, tool: "create_page_with_section", pageId: 50, pageSlug: "hero-v2", sectionId: 120, type: "HERO_V2", preview: { lang: "th", previewPath: "/th/hero-v2", previewUrl: "/th/hero-v2", }, warnings: [ "contentAlignment must be left, center, or right. Use an image URL allowed by apps/website.", ], }); }); });