1import type { AtomCommand } from "@t3tools/client-runtime/state/runtime";
2import {
3 AuthOrchestrationOperateScope,
4 EnvironmentAuthorizationError,
5 EnvironmentId,
6} from "@t3tools/contracts";
7import * as Cause from "effect/Cause";
8import { AsyncResult } from "effect/reactivity";
9import { assert, beforeEach, expect, it, vi } from "vite-plus/test";
10
11const state = vi.hoisted(() => ({
12 allowed: new Set<string>(),
13 readScope: vi.fn(),
14 run: vi.fn(),
15}));
16
17vi.mock("react", () => ({ useCallback: (callback: unknown) => callback }));
18vi.mock("./session", () => ({ readEnvironmentScope: state.readScope }));
19vi.mock("./use-atom-command", () => ({ useAtomCommand: () => state.run }));
20
21import { useOrchestrationCommand } from "./use-orchestration-command";
22
23const first = EnvironmentId.make("primary");
24const second = EnvironmentId.make("secondary");
25type Target = { environmentId: EnvironmentId; input: { threadId: string } };
26const command: AtomCommand<Target, string, Error> = { label: "thread mutation", run: state.run };
27const target = (environmentId: EnvironmentId): Target => ({
28 environmentId,
29 input: { threadId: "thread-1" },
30});
31
32beforeEach(() => {
33 state.allowed.clear();
34 state.run.mockReset().mockResolvedValue(AsyncResult.success("receipt"));
35 state.readScope
36 .mockReset()
37 .mockImplementation(
38 (environmentId, scope) =>
39 scope === AuthOrchestrationOperateScope && state.allowed.has(environmentId),
40 );
41});
42
43it("checks the command's environment instead of borrowing the primary grant", async () => {
44 state.allowed.add(first);
45 const mutate = useOrchestrationCommand(command, { reportFailure: false });
46 const result = await mutate(target(second));
47 assert(result._tag === "Failure");
48 const error = Cause.squash(result.cause);
49 expect(error).toBeInstanceOf(EnvironmentAuthorizationError);
50 expect(error).toMatchObject({ requiredScope: AuthOrchestrationOperateScope });
51 expect(state.run).not.toHaveBeenCalled();
52
53 expect(await mutate(target(first))).toMatchObject({ _tag: "Success", value: "receipt" });
54 expect(state.run).toHaveBeenCalledWith(target(first));
55});
56
57it("does not dispatch a later mutation after access changes while awaiting a receipt", async () => {
58 state.allowed.add(second);
59 let finish: (() => void) | undefined;
60 state.run.mockImplementationOnce(
61 () =>
62 new Promise((resolve) => {
63 finish = () => resolve(AsyncResult.success("first receipt"));
64 }),
65 );
66 const mutate = useOrchestrationCommand(command);
67 const pending = mutate(target(second));
68 state.allowed.delete(second);
69 finish!();
70 expect((await pending)._tag).toBe("Success");
71 expect((await mutate(target(second)))._tag).toBe("Failure");
72 expect(state.run).toHaveBeenCalledTimes(1);
73});