import { assert, describe, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Ref from "effect/Ref"; import * as TestClock from "effect/testing/TestClock"; import * as ResetCreditCoordinator from "./resetCreditCoordinator.ts"; describe("ResetCreditCoordinator", () => { it.effect("re-sends the same idempotency key after a failed attempt, then clears it", () => Effect.gen(function* () { const { redeem } = yield* ResetCreditCoordinator.ResetCreditCoordinator; const keys = yield* Ref.make>([]); const attempts = yield* Ref.make(0); const consume = (key: string) => Effect.gen(function* () { yield* Ref.update(keys, (seen) => [...seen, key]); const attempt = yield* Ref.updateAndGet(attempts, (n) => n + 1); if (attempt === 1) return yield* Effect.fail("timed out" as const); return "reset" as const; }); const first = yield* redeem("acct", consume).pipe(Effect.result); assert.isTrue(first._tag === "Failure"); const second = yield* redeem("acct", consume); assert.strictEqual(second, "reset"); yield* redeem("acct", consume); const seen = yield* Ref.get(keys); assert.strictEqual(seen.length, 3); assert.strictEqual(seen[0], seen[1]); assert.notStrictEqual(seen[1], seen[2]); }).pipe(Effect.provide(ResetCreditCoordinator.layerTest)), ); it.effect("starts a fresh attempt after a settled failure", () => Effect.gen(function* () { const { redeem } = yield* ResetCreditCoordinator.ResetCreditCoordinator; const keys = yield* Ref.make>([]); const consume = (key: string) => Ref.update(keys, (seen) => [...seen, key]).pipe( Effect.andThen(Effect.fail("cooldown" as const)), ); const isSettled = (error: "cooldown") => error === "cooldown"; yield* redeem("acct", consume, isSettled).pipe(Effect.result); yield* redeem("acct", consume, isSettled).pipe(Effect.result); const seen = yield* Ref.get(keys); assert.strictEqual(seen.length, 2); assert.notStrictEqual(seen[0], seen[1]); }).pipe(Effect.provide(ResetCreditCoordinator.layerTest)), ); it.effect("serialises concurrent redemptions on the same account, not per caller", () => Effect.gen(function* () { const { redeem } = yield* ResetCreditCoordinator.ResetCreditCoordinator; const release = yield* Deferred.make(); const inFlight = yield* Ref.make(0); const peak = yield* Ref.make(0); const consume = () => Effect.gen(function* () { const now = yield* Ref.updateAndGet(inFlight, (n) => n + 1); yield* Ref.update(peak, (p) => Math.max(p, now)); yield* Deferred.await(release); yield* Ref.update(inFlight, (n) => n - 1); return "reset" as const; }); const a = yield* redeem("acct", consume).pipe(Effect.forkChild); const b = yield* redeem("acct", consume).pipe(Effect.forkChild); yield* Effect.yieldNow; yield* Deferred.succeed(release, undefined); yield* Fiber.join(a); yield* Fiber.join(b); assert.strictEqual(yield* Ref.get(peak), 1); }).pipe(Effect.provide(ResetCreditCoordinator.layerTest)), ); it.effect("keeps different accounts independent", () => Effect.gen(function* () { const { redeem } = yield* ResetCreditCoordinator.ResetCreditCoordinator; const release = yield* Deferred.make(); const peak = yield* Ref.make(0); const inFlight = yield* Ref.make(0); const consume = () => Effect.gen(function* () { const now = yield* Ref.updateAndGet(inFlight, (n) => n + 1); yield* Ref.update(peak, (p) => Math.max(p, now)); yield* Deferred.await(release); return "reset" as const; }); const a = yield* redeem("acct-a", consume).pipe(Effect.forkChild); const b = yield* redeem("acct-b", consume).pipe(Effect.forkChild); yield* Effect.yieldNow; yield* Deferred.succeed(release, undefined); yield* Fiber.join(a); yield* Fiber.join(b); assert.strictEqual(yield* Ref.get(peak), 2); }).pipe(Effect.provide(ResetCreditCoordinator.layerTest)), ); it.effect("re-probes after a reset until the limits are fresh, then gives up", () => Effect.gen(function* () { const stale = { usageLimits: { checkedAt: "t0", windows: [] } }; const run = (freshFrom: number) => Effect.gen(function* () { const probes = yield* Ref.make(0); const refresh = Ref.updateAndGet(probes, (n) => n + 1).pipe( Effect.map((n) => n >= freshFrom ? { usageLimits: { checkedAt: `t${n}`, windows: [] } } : stale, ), ); const fiber = yield* ResetCreditCoordinator.refreshAfterRedemption({ outcome: "reset", getSnapshot: Effect.succeed(stale), refresh, }).pipe(Effect.forkChild); yield* TestClock.adjust("1 minute"); return { confirmed: yield* Fiber.join(fiber), probes: yield* Ref.get(probes) }; }); assert.deepStrictEqual(yield* run(3), { confirmed: true, probes: 3 }); assert.deepStrictEqual(yield* run(Infinity), { confirmed: false, probes: 4 }); }), ); });