/** * P014: lib/orders-store.ts previously had ZERO test coverage and no * idempotency mechanism -- a retried POST /api/orders created a second, * indistinguishable row every time (recon: "Duplicate delivery idempotent: * NOT MET"). These tests exercise the in-memory fallback path directly * (no DATABASE_URL is set in this test environment, matching every other * test in this repo) and prove: a retry under the same Idempotency-Key * replays the original order; a reused key with a different submission is * refused as a conflict, not silently merged; and omitting the key * preserves the exact pre-P014 "always create a new order" behavior. */ import { describe, it, expect, beforeEach } from "vitest"; import { createOrder, findOrderByIdempotencyKey, IdempotencyKeyConflictError, type OrderConfig, } from "@/lib/orders-store"; const CONFIG_A: OrderConfig = { markets: ["SE"], type: "Bolag", fieldIds: ["identity"], volume: 1000 }; const CONFIG_B: OrderConfig = { markets: ["NO"], type: "Bolag", fieldIds: ["identity"], volume: 5000 }; describe("createOrder idempotency (in-memory fallback)", () => { it("no Idempotency-Key: every call creates a distinct order (unchanged pre-P014 behavior)", async () => { const first = await createOrder("custom", CONFIG_A); const second = await createOrder("custom", CONFIG_A); expect(first.order.order_id).not.toBe(second.order.order_id); expect(first.replayed).toBe(false); expect(second.replayed).toBe(false); }); it("same Idempotency-Key + same (flow, config): the second call replays the first order, no duplicate row", async () => { const key = `idem_${crypto.randomUUID()}`; const first = await createOrder("custom", CONFIG_A, key); const second = await createOrder("custom", CONFIG_A, key); expect(first.replayed).toBe(false); expect(second.replayed).toBe(true); expect(second.order.order_id).toBe(first.order.order_id); expect(second.order.created_at).toBe(first.order.created_at); }); it("retrying the SAME key three times still yields exactly one underlying order", async () => { const key = `idem_${crypto.randomUUID()}`; const a = await createOrder("custom", CONFIG_A, key); const b = await createOrder("custom", CONFIG_A, key); const c = await createOrder("custom", CONFIG_A, key); const ids = new Set([a.order.order_id, b.order.order_id, c.order.order_id]); expect(ids.size).toBe(1); }); it("same Idempotency-Key reused with a DIFFERENT config: refused as a conflict, not silently merged", async () => { const key = `idem_${crypto.randomUUID()}`; await createOrder("custom", CONFIG_A, key); await expect(createOrder("custom", CONFIG_B, key)).rejects.toBeInstanceOf(IdempotencyKeyConflictError); }); it("same Idempotency-Key reused with a DIFFERENT flow: refused as a conflict", async () => { const key = `idem_${crypto.randomUUID()}`; await createOrder("custom", CONFIG_A, key); await expect(createOrder("subscription", CONFIG_A, key)).rejects.toBeInstanceOf(IdempotencyKeyConflictError); }); it("a conflicting retry does not mutate or replace the original order", async () => { const key = `idem_${crypto.randomUUID()}`; const original = await createOrder("custom", CONFIG_A, key); await expect(createOrder("custom", CONFIG_B, key)).rejects.toThrow(); const stillThere = await findOrderByIdempotencyKey(key); expect(stillThere?.order_id).toBe(original.order.order_id); expect(stillThere?.config).toEqual(CONFIG_A); }); it("different Idempotency-Keys for the same config create independent orders", async () => { const a = await createOrder("custom", CONFIG_A, `idem_${crypto.randomUUID()}`); const b = await createOrder("custom", CONFIG_A, `idem_${crypto.randomUUID()}`); expect(a.order.order_id).not.toBe(b.order.order_id); }); it("an empty-string Idempotency-Key is treated as absent (no dedup, no crash)", async () => { const a = await createOrder("custom", CONFIG_A, ""); const b = await createOrder("custom", CONFIG_A, " "); expect(a.order.order_id).not.toBe(b.order.order_id); }); });