Skip to content
Source
Sign inSign up
Open History to see the last commit

handlers.test.ts

97 lines3.9 KB

1import { expect, it } from "@effect/vitest";
2import {
3 DEFAULT_SERVER_SETTINGS,
4 EnvironmentId,
5 ProviderInstanceId,
6 ThreadId,
7 type OrchestrationV2ThreadShell,
8} from "@t3tools/contracts";
9import * as Deferred from "effect/Deferred";
10import * as Effect from "effect/Effect";
11import * as Fiber from "effect/Fiber";
12import * as Layer from "effect/Layer";
13import * as Ref from "effect/Ref";
14import * as Stream from "effect/Stream";
15​
16import * as Environment from "../../../environment/ServerEnvironment.ts";
17import * as ThreadCommandExecutor from "../../../orchestration-v2/ThreadCommandExecutor.ts";
18import * as ThreadManagement from "../../../orchestration-v2/ThreadManagementService.ts";
19import * as Settings from "../../../serverSettings.ts";
20import * as McpInvocationContext from "../../McpInvocationContext.ts";
21import * as McpToolAccess from "../../McpToolAccess.ts";
22import { liveThreadShell } from "../../McpToolAccess.testkit.ts";
23import * as EnvironmentHandlers from "./handlers.ts";
24import { EnvironmentToolkit } from "./tools.ts";
25​
26const environmentId = EnvironmentId.make("environment:preferences");
27const threadId = ThreadId.make("thread:preferences");
28​
29it.effect("refuses a preferences update when the caller's turn ends while it waits", () =>
30 Effect.gen(function* () {
31 const caller = yield* Ref.make<OrchestrationV2ThreadShell>(liveThreadShell(threadId));
32 const updates = yield* Ref.make(0);
33 const checked = yield* Deferred.make<void>();
34 const layerDependencies = Layer.mergeAll(
35 ThreadCommandExecutor.layer,
36 Layer.succeed(McpInvocationContext.McpInvocationContext, {
37 environmentId,
38 requestNamespace: "provider:preferences",
39 thread: {
40 threadId,
41 providerSessionId: "provider:preferences",
42 providerInstanceId: ProviderInstanceId.make("codex"),
43 },
44 client: undefined,
45 capabilities: new Set(["orchestration" as const]),
46 issuedAt: 0,
47 }),
48 Layer.mock(ThreadManagement.ThreadManagementService)({
49 getThreadShell: () =>
50 Ref.get(caller).pipe(Effect.tap(() => Deferred.succeed(checked, undefined))),
51 }),
52 Layer.mock(Environment.ServerEnvironment)({
53 getDescriptor: Effect.succeed({
54 environmentId,
55 label: "Test",
56 platform: { os: "linux", arch: "x64" },
57 serverVersion: "0.0.0",
58 capabilities: { repositoryIdentity: false },
59 }),
60 }),
61 Layer.mock(Settings.ServerSettingsService)({
62 getSettings: Effect.succeed(DEFAULT_SERVER_SETTINGS),
63 updateSettings: () =>
64 Ref.update(updates, (count) => count + 1).pipe(Effect.as(DEFAULT_SERVER_SETTINGS)),
65 }),
66 );
67 yield* Effect.gen(function* () {
68 const toolkit = yield* EnvironmentToolkit;
69 const executor = yield* ThreadCommandExecutor.ThreadCommandExecutor;
70 const held = yield* Deferred.make<void>();
71 const release = yield* Deferred.make<void>();
72 const holder = yield* executor
73 .withLock(
74 threadId,
75 Deferred.succeed(held, undefined).pipe(Effect.andThen(Deferred.await(release))),
76 )
77 .pipe(Effect.forkChild);
78 yield* Deferred.await(held);
79 const update = yield* toolkit
80 .handle("t3_environment_preferences_update", { newWorktreesStartFromOrigin: true })
81 .pipe(Stream.unwrap, Stream.runCollect, Effect.forkChild);
82 yield* Deferred.await(checked);
83 yield* Ref.update(caller, (shell) => ({ ...shell, activeRunId: null }));
84 yield* Deferred.succeed(release, undefined);
85 yield* Fiber.join(holder);
86 const result = yield* Fiber.join(update);
87 expect(result.at(-1)?.result).toMatchObject({ code: "parent_not_active" });
88 expect(yield* Ref.get(updates)).toBe(0);
89 }).pipe(
90 Effect.provide(
91 McpToolAccess.HandlersLayer.layer(EnvironmentHandlers.layer).pipe(
92 Layer.provideMerge(layerDependencies),
93 ),
94 ),
95 );
96 }),
97);