import { strict as assert } from "node:assert"; import { describe, it } from "node:test"; import { pickLanguage } from "../app/services/invoice/i18n"; import { reserveWebhook, type DedupeDeps } from "../app/services/webhooks/dedupe.server"; describe("pickLanguage", () => { it("returns 'de' only for explicit German locales", () => { assert.equal(pickLanguage("de"), "de"); assert.equal(pickLanguage("de-AT"), "de"); assert.equal(pickLanguage("de-DE"), "de"); assert.equal(pickLanguage("de_CH"), "de"); assert.equal(pickLanguage("DE-AT"), "de"); // case-insensitive }); it("returns 'en' for non-German locales (regression: it/fr/es no longer fall back to de)", () => { assert.equal(pickLanguage("en"), "en"); assert.equal(pickLanguage("en-US"), "en"); assert.equal(pickLanguage("it"), "en"); assert.equal(pickLanguage("it-IT"), "en"); assert.equal(pickLanguage("fr"), "en"); assert.equal(pickLanguage("fr-FR"), "en"); assert.equal(pickLanguage("es"), "en"); assert.equal(pickLanguage("hu-HU"), "en"); }); it("falls back to 'de' for empty/unknown input so the per-shop default chain still works", () => { assert.equal(pickLanguage(undefined), "de"); assert.equal(pickLanguage(null), "de"); assert.equal(pickLanguage(""), "de"); }); }); function makeRequest(headers: Record = {}): Request { return new Request("https://example.com/webhooks/test", { method: "POST", headers, }); } type ExistingRow = { webhookId: string; status: string; receivedAt: Date } | null; /** * Build a DedupeDeps stub. * - "ok" : create() succeeds (fresh reservation). * - "p2002" : create() conflicts; findUnique() returns `existing`. * - "boom" : create() throws a non-P2002 error (fail-open). */ function makeDeps( behaviour: "ok" | "p2002" | "boom", existing: ExistingRow = null, ): DedupeDeps & { calls: { commit: number; release: number; update: number } } { const calls = { commit: 0, release: 0, update: 0 }; return { calls, db: { processedWebhook: { create: async () => { if (behaviour === "ok") return {}; if (behaviour === "p2002") { const err = new Error("Unique constraint failed") as Error & { code?: string }; err.code = "P2002"; throw err; } throw new Error("DB unavailable"); }, findUnique: async () => existing, update: async () => { calls.update += 1; calls.commit += 1; return {}; }, delete: async () => { calls.release += 1; return {}; }, }, }, }; } describe("reserveWebhook", () => { it("returns a reservation on first delivery (insert succeeds)", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const res = await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", makeDeps("ok")); assert.ok(res, "expected a reservation"); assert.equal(res!.webhookId, "abc-123"); }); it("returns null for an already-processed (done) delivery", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const deps = makeDeps("p2002", { webhookId: "abc-123", status: "done", receivedAt: new Date(), }); assert.equal(await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", deps), null); }); it("returns null for a fresh in-flight (processing) delivery", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const deps = makeDeps("p2002", { webhookId: "abc-123", status: "processing", receivedAt: new Date(), // fresh lease }); assert.equal(await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", deps), null); }); it("reclaims a stale (crashed) processing reservation", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const deps = makeDeps("p2002", { webhookId: "abc-123", status: "processing", receivedAt: new Date(Date.now() - 10 * 60 * 1000), // 10 min ago > 5 min lease }); const res = await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", deps); assert.ok(res, "expected to reclaim the stale reservation"); assert.equal(deps.calls.update, 1, "stale reclaim should renew the lease via update()"); }); it("commit() flips the row to done; release() deletes it", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const deps = makeDeps("ok"); const res = await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", deps); await res!.commit(); assert.equal(deps.calls.commit, 1); await res!.release(); assert.equal(deps.calls.release, 1); }); it("fails open (returns a no-op reservation) when the dedupe table errors", async () => { const req = makeRequest({ "x-shopify-webhook-id": "abc-123" }); const res = await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", makeDeps("boom")); assert.ok(res, "fail-open must still process — never silently drop a webhook"); assert.equal(res!.webhookId, "abc-123"); }); it("returns a no-op reservation when the X-Shopify-Webhook-Id header is missing", async () => { const req = makeRequest(); const res = await reserveWebhook(req, "shop.myshopify.com", "ORDERS_CREATE", makeDeps("ok")); assert.ok(res, "missing id => process without dedupe"); assert.equal(res!.webhookId, null); }); });