Open History to see the last commit
1import { EnvironmentId } from "@t3tools/contracts";
2import { refreshUsageLimits } from "@t3tools/client-runtime/state/usage";
3import * as Cause from "effect/Cause";
4import { AsyncResult, AtomRegistry } from "effect/reactivity";
5import { expect, it, vi } from "vite-plus/test";
6
7type RefreshResult = AsyncResult.AsyncResult<void, string>;
8
9const state = vi.hoisted(() => ({
10 refresh: (_environmentId: string): Promise<RefreshResult> => {
11 throw new Error("refresh not set up");
12 },
13}));
14vi.mock("../../state/server", () => ({
15 serverEnvironment: {
16 refreshProviders: {
17 label: "refresh providers",
18 run: (_registry: unknown, input: { readonly environmentId: string }) =>
19 state.refresh(input.environmentId),
20 },
21 },
22}));
23
24import {
25 environmentIdsKey,
26 limitsRefreshFailuresAtom,
27 refreshEnvironmentLimits,
28} from "./usageLimitsRefresh";
29
30const success: RefreshResult = AsyncResult.success(undefined);
31const failure: RefreshResult = AsyncResult.failure(Cause.fail("offline"));
32
33function setup(refresh: (environmentId: string) => Promise<RefreshResult>) {
34 state.refresh = refresh;
35 const registry = AtomRegistry.make();
36 return {
37 registry,
38 failures: (ids: readonly EnvironmentId[]) => {
39 const atom = limitsRefreshFailuresAtom(environmentIdsKey(ids));
40 registry.mount(atom);
41 return registry.get(atom);
42 },
43 };
44}
45
46it("keeps a newer environment failure when an older refresh finishes", async () => {
47 const a = EnvironmentId.make("mobile-limits-a");
48 const b = EnvironmentId.make("mobile-limits-b");
49 const pending = Promise.withResolvers<RefreshResult>();
50 const { registry, failures } = setup((environmentId) =>
51 environmentId === a ? pending.promise : Promise.resolve(failure),
52 );
53 failures([a, b]);
54 const first = refreshEnvironmentLimits(registry, a);
55 await refreshEnvironmentLimits(registry, b, { automatic: true });
56 expect(failures([a, b])).toEqual([b]);
57 pending.resolve(success);
58 await first;
59 expect(failures([a, b])).toEqual([b]);
60});
61
62it("does not let an older refresh clear a newer failure for the same environment", async () => {
63 const a = EnvironmentId.make("mobile-limits-race-a");
64 const b = EnvironmentId.make("mobile-limits-race-b");
65 const aFirst = Promise.withResolvers<RefreshResult>();
66 const bFirst = Promise.withResolvers<RefreshResult>();
67 let aCalls = 0;
68 const { registry, failures } = setup((environmentId) =>
69 environmentId === b
70 ? bFirst.promise
71 : ++aCalls === 1
72 ? aFirst.promise
73 : Promise.resolve(failure),
74 );
75 failures([a, b]);
76 const older = Promise.all([
77 refreshEnvironmentLimits(registry, a, { automatic: true }),
78 refreshEnvironmentLimits(registry, b, { automatic: true }),
79 ]);
80 aFirst.resolve(success);
81 await refreshUsageLimits(a, () => aFirst.promise);
82 await refreshEnvironmentLimits(registry, a);
83 expect(failures([a])).toEqual([a]);
84 bFirst.resolve(success);
85 await older;
86 expect(failures([a, b])).toEqual([a]);
87});
88
89it("skips an automatic refresh inside the throttle window without touching the outcome", async () => {
90 const a = EnvironmentId.make("mobile-limits-throttle");
91 const { registry, failures } = setup(() => Promise.resolve(failure));
92 failures([a]);
93 await refreshEnvironmentLimits(registry, a);
94 expect(failures([a])).toEqual([a]);
95 state.refresh = () => Promise.resolve(success);
96 await refreshEnvironmentLimits(registry, a, { automatic: true });
97 expect(failures([a])).toEqual([a]);
98 await refreshEnvironmentLimits(registry, a);
99 expect(failures([a])).toEqual([]);
100});