/* RFC-0176: verify the inbound/queue runtime — authenticateInbound, IntegrationEventSchema, routeEventToReady (self-enabling Lagebild route), and consumeIntegrationBatch dedup via a KV-backed seen-set. fetch + KV are stubbed so the full producer→consumer flow is exercised without Cloudflare. Do not hit the network — fetch is stubbed. RFC-0176: initial queue/inbound runtime test. RFC-1311: carried legacy entry — Lagebild replaced Pipedrive as sole CRM destination — test updated accordingly. */ import { test, expect, afterEach, beforeEach } from "vitest"; import { IntegrationEventSchema, authenticateInbound, consumeIntegrationBatch, kvDedup, routeEventToReady, type IntegrationEvent, type IntegrationSecrets, type KvDedupStore, } from "../index.ts"; const SECRETS: IntegrationSecrets = { LAGEBILD_API_URL: "https://lagebild-api.example.workers.dev", LAGEBILD_API_KEY: "lbk_test_key_0001", LAGEBILD_TENANT_ID: "ten_warpgogol000001", LAGEBILD_SOURCE_SYSTEM_ID: "site_warpgogol_com", }; const EVENT: IntegrationEvent = { eventId: "evt-1", kind: "lead", source: "uchat", locale: "de", occurredAt: "2026-06-08T00:00:00.000Z", contact: { name: "Max", email: "max@example.org" }, payload: { note: "Hello from the chat" }, }; /** In-memory KV implementing the dedup store contract. */ function memoryKv(): KvDedupStore & { size(): number } { const map = new Map(); return { async get(key) { return map.has(key) ? (map.get(key) ?? null) : null; }, async put(key, value) { map.set(key, value); }, size: () => map.size, }; } let fetchCalls: string[] = []; const realFetch = globalThis.fetch; beforeEach(() => { fetchCalls = []; // Stub Lagebild ingress endpoint — returns accepted with a submission id. globalThis.fetch = (async (url: string | URL | Request) => { fetchCalls.push(String(url)); return { ok: true, status: 200, json: async () => ({ submission_id: "sub_test123", accepted: true, is_replay: false, correlation_id: null, }), } as unknown as Response; }) as typeof fetch; }); afterEach(() => { globalThis.fetch = realFetch; }); test("routeEventToReady routes a lead to the lagebild gogol-adapter destination", async () => { const result = await routeEventToReady(EVENT, SECRETS); expect(result.routed).toEqual(["crm:lagebild"]); expect(result.failed.length).toBe(0); // lagebild adapter performs a single POST to /v1/ingress. expect(fetchCalls.length).toBe(1); }); test("routeEventToReady skips when destination secrets are absent", async () => { const result = await routeEventToReady(EVENT, {}); expect(result.skipped).toEqual(["crm:lagebild"]); expect(result.routed.length).toBe(0); expect(fetchCalls.length).toBe(0); }); test("consumeIntegrationBatch dedups a queue redelivery via KV (no double-write)", async () => { const kv = memoryKv(); const dedup = kvDedup(kv); const first = await consumeIntegrationBatch([EVENT], SECRETS, dedup); expect(first[0].routed).toEqual(["crm:lagebild"]); const afterFirst = fetchCalls.length; expect(afterFirst).toBe(1); // Redeliver the SAME eventId — must be skipped by dedup, no new fetches. const second = await consumeIntegrationBatch([EVENT], SECRETS, dedup); expect(second[0].skipped).toEqual(["crm:lagebild"]); expect(fetchCalls.length).toBe(afterFirst); expect(kv.size()).toBe(1); }); test("authenticateInbound is fail-closed (bearer + header, unset secret rejects)", () => { const ok = new Headers({ authorization: "Bearer s3cret" }); const okHeader = new Headers({ "x-integration-secret": "s3cret" }); const wrong = new Headers({ authorization: "Bearer nope" }); expect(authenticateInbound(ok, "s3cret")).toBe(true); expect(authenticateInbound(okHeader, "s3cret")).toBe(true); expect(authenticateInbound(wrong, "s3cret")).toBe(false); expect(authenticateInbound(ok, undefined)).toBe(false); expect(authenticateInbound(new Headers(), "s3cret")).toBe(false); }); test("IntegrationEventSchema rejects malformed payloads", () => { expect(IntegrationEventSchema.safeParse(EVENT).success).toBe(true); expect(IntegrationEventSchema.safeParse({ ...EVENT, kind: "bogus" }).success).toBe(false); expect(IntegrationEventSchema.safeParse({ ...EVENT, eventId: "" }).success).toBe(false); expect(IntegrationEventSchema.safeParse({}).success).toBe(false); });