/* RFC-0179: verify the dynamic-dispatch seam. executeDispatch (tenant-side) runs ready destinations with local secrets; dispatchToTenant (consumer-side) posts into the tenant, never carries a destination token, and maps the reply to ack vs retry. fetch/dispatcher are stubbed. No network — fetch + dispatcher are stubbed. RFC-0179: initial dispatch-seam test. */ import { test, expect } from "vitest"; import { dispatchToTenant, executeDispatch, type DispatchExecuteResult, type DispatchNamespaceBinding, } from "../dispatch.ts"; import type { IntegrationEvent } from "../port.ts"; const EVENT: IntegrationEvent = { eventId: "evt-1", kind: "lead", source: "uchat", locale: "de", occurredAt: new Date().toISOString(), contact: { name: "A", email: "a@example.org" }, payload: { message: "hi" }, }; test("executeDispatch skips destinations when the tenant holds no secrets", async () => { const result = await executeDispatch({ siteId: "warpgogol-com", event: EVENT }, {}); expect(result.routed.length).toBe(0); expect(result.skipped.includes("crm:lagebild")).toBeTruthy(); }); function stubDispatcher( reply: DispatchExecuteResult, status = 200, capture?: (r: Request) => void, ) { const binding: DispatchNamespaceBinding = { get() { return { async fetch(request: Request): Promise { capture?.(request); return new Response(JSON.stringify(reply), { status }); }, }; }, }; return binding; } test("dispatchToTenant acks on a routed reply and forwards only the inbound secret", async () => { let seen: Request | undefined; const dispatcher = stubDispatcher( { routed: ["crm:lagebild"], failed: [], skipped: [] }, 200, (r) => (seen = r), ); const outcome = await dispatchToTenant( dispatcher, { siteId: "warpgogol-com", event: EVENT }, "inbound-secret", ); expect(outcome.ok).toBe(true); expect(seen?.headers.get("x-integration-secret")).toBe("inbound-secret"); const body = await seen!.text(); expect(!body.includes("LAGEBILD")).toBeTruthy(); }); test("dispatchToTenant retries when nothing routed but something failed", async () => { const dispatcher = stubDispatcher({ routed: [], failed: ["crm:lagebild"], skipped: [] }); const outcome = await dispatchToTenant(dispatcher, { siteId: "x", event: EVENT }, "s"); expect(outcome.ok).toBe(false); }); test("dispatchToTenant retries on a non-2xx tenant response", async () => { const dispatcher = stubDispatcher({ routed: [], failed: [], skipped: [] }, 503); const outcome = await dispatchToTenant(dispatcher, { siteId: "x", event: EVENT }, "s"); expect(outcome.ok).toBe(false); expect(outcome.transportError ?? "").toMatch(/503/); });