import { afterAll, describe, expect, it } from "vitest"; import redis from "../../app/lib/redis.server"; import { countActiveHolds, releaseHold, tryCreateHold, type SlotIdentity } from "../../app/services/holds.server"; // The concurrency test CLAUDE.md calls out as non-negotiable: "Slot-holds // (Redis, TTL) prevent last-slot double-booking — this has a dedicated // concurrency test that must pass." This needs a real Redis — the // atomicity guarantee comes from a Lua script Redis runs single-threaded, // which a mock can't meaningfully exercise. Run via `npm run test:integration` // against the docker-compose Redis (or CI's redis service). function uniqueSlot(): SlotIdentity { return { shopDomain: "concurrency-test.myshopify.com", locationId: "loc_test", method: "PICKUP", slotStartIso: `2026-08-25T13:00:00.000Z#${Math.random().toString(36).slice(2)}`, }; } describe("tryCreateHold concurrency", () => { afterAll(async () => { await redis.quit(); }); it("lets exactly one of many concurrent requests claim the last unit of capacity", async () => { const slot = uniqueSlot(); const capacity = 1; const contenders = 20; const results = await Promise.all( Array.from({ length: contenders }, (_, i) => tryCreateHold(slot, `cart-${i}`, capacity)), ); const successes = results.filter((r) => r.success); expect(successes).toHaveLength(1); const activeCount = await countActiveHolds(slot); expect(activeCount).toBe(1); }); it("allows exactly `capacity` concurrent holds, no more, no fewer", async () => { const slot = uniqueSlot(); const capacity = 5; const contenders = 30; const results = await Promise.all( Array.from({ length: contenders }, (_, i) => tryCreateHold(slot, `cart-${i}`, capacity)), ); expect(results.filter((r) => r.success)).toHaveLength(capacity); expect(await countActiveHolds(slot)).toBe(capacity); }); it("releasing a hold frees capacity for a subsequent request", async () => { const slot = uniqueSlot(); const capacity = 1; const first = await tryCreateHold(slot, "cart-a", capacity); expect(first.success).toBe(true); const blocked = await tryCreateHold(slot, "cart-b", capacity); expect(blocked.success).toBe(false); await releaseHold(slot, "cart-a"); const afterRelease = await tryCreateHold(slot, "cart-b", capacity); expect(afterRelease.success).toBe(true); }); it("an expired hold no longer counts against capacity", async () => { const slot = uniqueSlot(); const capacity = 1; // A negative TTL means it's already expired the instant it's created. const first = await tryCreateHold(slot, "cart-expired", capacity, -1000); expect(first.success).toBe(true); const second = await tryCreateHold(slot, "cart-fresh", capacity); expect(second.success).toBe(true); expect(await countActiveHolds(slot)).toBe(1); }); it("the same cart re-requesting the same slot does not consume a second unit", async () => { const slot = uniqueSlot(); const capacity = 1; const first = await tryCreateHold(slot, "cart-repeat", capacity); expect(first.success).toBe(true); // ZADD on an existing member updates its score rather than adding a // second entry, so a shopper re-confirming the same slot (e.g. a retried // request) doesn't burn extra capacity against themselves. const second = await tryCreateHold(slot, "cart-repeat", capacity); expect(second.success).toBe(true); expect(await countActiveHolds(slot)).toBe(1); }); });