Skip to content
Source
Sign inSign up

providerInstallation.test.ts

256 lines9.7 KB

1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import {
4 ProviderDriverKind,
5 ProviderInstanceId,
6 type ProviderInstallState,
7} from "@t3tools/contracts";
8import * as HostProcess from "@t3tools/shared/HostProcess";
9import * as Effect from "effect/Effect";
10import * as Deferred from "effect/Deferred";
11import * as SubscriptionRef from "effect/SubscriptionRef";
12import * as FileSystem from "effect/FileSystem";
13import * as Layer from "effect/Layer";
14import * as Path from "effect/Path";
15import * as Stream from "effect/Stream";
16​
17import * as ServerSettings from "../serverSettings.ts";
18import * as CodexInstallation from "./CodexInstallation.ts";
19import * as AntigravityInstallation from "./AntigravityInstallation.ts";
20import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
21import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
22import * as ProviderInstallation from "./providerInstallation.ts";
23import * as ProviderInstanceRegistry from "./ProviderInstanceRegistry.ts";
24import * as ProviderRegistry from "./ProviderRegistry.ts";
25​
26const instanceId = ProviderInstanceId.make("antigravity");
27const driver = ProviderDriverKind.make("antigravity");
28const state: ProviderInstallState = {
29 driver,
30 operationId: null,
31 phase: "idle",
32 downloadedBytes: 0,
33 totalBytes: null,
34 version: null,
35 installedVersion: null,
36 canRemove: false,
37 message: null,
38};
39​
40function instance(kind = driver, id = instanceId): ProviderInstance {
41 return {
42 instanceId: id,
43 driverKind: kind,
44 enabled: false,
45 displayName: undefined,
46 continuationIdentity: { driverKind: kind, continuationKey: instanceId },
47 get orchestrationAdapter(): never {
48 throw new Error("Installation must not start a provider session.");
49 },
50 get snapshot(): never {
51 throw new Error("Installation routing must not probe the provider.");
52 },
53 get textGeneration(): never {
54 throw new Error("Installation must not generate text.");
55 },
56 };
58​
59const makeHarness = Effect.fn("providerInstallation.test.makeHarness")(function* (
60 input: {
61 instance?: ProviderInstance;
62 settings?: Parameters<typeof ServerSettings.layerTest>[0];
63 } = {},
64) {
65 const calls: string[] = [];
66 const refreshed = yield* Deferred.make<void>();
67 const observed = yield* Deferred.make<void>();
68 const listening = yield* Deferred.make<void>();
69 const installationState = yield* SubscriptionRef.make(state);
70 let protectedPaths: ReadonlyArray<string> = [];
71 const configured = input.instance ?? instance();
72 const dependencies = Layer.mergeAll(
73 ServerSettings.layerTest(input.settings),
74 Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({
75 getInstance: (id) => Effect.succeed(id === configured.instanceId ? configured : undefined),
76 listInstances: Effect.succeed([configured]),
77 }),
78 Layer.mock(ProviderRegistry.ProviderRegistry)({
79 refreshInstance: () =>
80 Effect.sync(() => {
81 calls.push("refresh");
82 return [];
83 }).pipe(Effect.tap(() => Deferred.succeed(refreshed, undefined))),
84 }),
85 Layer.mock(CodexInstallation.CodexInstallation)({
86 managedDirectory: "/unused-managed-codex",
87 start: Effect.sync(() => {
88 calls.push("codex-start");
89 return { ...state, driver: ProviderDriverKind.make("codex") };
90 }),
91 cancel: () =>
92 Effect.sync(() => {
93 calls.push("codex-cancel");
94 return { ...state, driver: ProviderDriverKind.make("codex") };
95 }),
96 state: Effect.succeed({ ...state, driver: ProviderDriverKind.make("codex") }),
97 changes: Stream.succeed({ ...state, driver: ProviderDriverKind.make("codex") }),
98 checkForUpdate: Effect.void,
99 remove: (paths) =>
100 Effect.sync(() => {
101 protectedPaths = paths ?? [];
102 calls.push("codex-remove");
103 }),
104 }),
105 Layer.mock(AntigravityInstallation.AntigravityInstallation)({
106 managedDirectory: "/unused-managed-runtime",
107 checkForUpdate: Effect.void,
108 start: Effect.sync(() => {
109 calls.push("start");
110 return { ...state, operationId: "install-1", phase: "downloading" as const };
111 }).pipe(Effect.tap((started) => SubscriptionRef.set(installationState, started))),
112 cancel: () =>
113 Effect.sync(() => {
114 calls.push("cancel");
115 return state;
116 }),
117 state: Effect.succeed(state),
118 changes: input.instance?.refreshModels
119 ? SubscriptionRef.changes(installationState).pipe(
120 Stream.tap(() => Deferred.succeed(listening, undefined)),
121 Stream.ensuring(Deferred.succeed(observed, undefined)),
122 )
123 : Stream.succeed(state),
124 remove: (paths) =>
125 Effect.sync(() => {
126 protectedPaths = paths ?? [];
127 calls.push("remove");
128 }),
129 }),
130 );
131 const context = yield* Layer.build(ProviderInstallation.layer.pipe(Layer.provide(dependencies)));
132 const connect = Effect.service(ProviderInstallation.ProviderInstallation).pipe(
133 Effect.provide(context),
134 );
135 const router = yield* connect;
136 return {
137 router,
138 connect,
139 listening,
140 calls,
141 refreshed,
142 observed,
143 installationState,
144 protectedPaths: () => protectedPaths,
145 };
146});
147​
148describe("provider installation routing", () => {
149 it.effect.each([
150 { phase: "succeeded", enabled: true, checkFails: false, expectedChecks: 1 },
151 { phase: "succeeded", enabled: true, checkFails: true, expectedChecks: 1 },
152 { phase: "succeeded", enabled: false, checkFails: false, expectedChecks: 0 },
153 { phase: "cancelled", enabled: true, checkFails: false, expectedChecks: 0 },
154 { phase: "failed", enabled: true, checkFails: false, expectedChecks: 0 },
155 ] as const)(
156 "checks account access once after installation is $phase, enabled=$enabled, checkFails=$checkFails",
157 ({ phase, enabled, checkFails, expectedChecks }) =>
158 Effect.gen(function* () {
159 let checks = 0;
160 const harness = yield* makeHarness({
161 instance: Object.assign(instance(), {
162 enabled,
163 refreshModels: () =>
164 Effect.sync(() => {
165 checks++;
166 }).pipe(
167 Effect.andThen(
168 checkFails
169 ? Effect.fail(
170 new ProviderDriverError({
171 driver,
172 instanceId,
173 detail: "Sign in required.",
174 }),
175 )
176 : Effect.void,
177 ),
178 ),
179 }),
180 });
181 yield* Effect.gen(function* () {
182 const first = yield* harness.connect;
183 const second = yield* harness.connect;
184 yield* Effect.all([first.start({ instanceId }), second.start({ instanceId })], {
185 concurrency: "unbounded",
186 });
187 yield* Deferred.await(harness.listening);
188 }).pipe(Effect.scoped);
189 assert.isFalse(yield* Deferred.isDone(harness.observed));
190 assert.strictEqual(checks, 0);
191 yield* SubscriptionRef.set(harness.installationState, {
192 ...state,
193 operationId: "install-1",
194 phase,
195 installedVersion: phase === "succeeded" ? "1.5.1" : null,
196 });
197 yield* Deferred.await(harness.observed);
198 if (expectedChecks > 0) yield* Deferred.await(harness.refreshed);
199 assert.strictEqual(checks, expectedChecks);
200 assert.deepEqual(
201 harness.calls,
202 expectedChecks > 0 ? ["start", "start", "refresh"] : ["start", "start"],
203 );
204 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
205 );
206​
207 it.effect("keeps external installs manual without hiding shared install status", () =>
208 Effect.gen(function* () {
209 const harness = yield* makeHarness({
210 settings: {
211 providerInstances: {
212 [ProviderInstanceId.make("antigravity")]: {
213 driver: ProviderDriverKind.make("antigravity"),
214 config: { binaryPath: "/external/agy" },
215 },
216 },
217 },
218 });
219 const start = yield* Effect.flip(harness.router.start({ instanceId }));
220 const remove = yield* Effect.flip(harness.router.remove({ instanceId }));
221 assert.include(start.detail, "custom executable");
222 assert.include(remove.detail, "custom executable");
223 const observed = yield* Stream.runCollect(harness.router.subscribe({ instanceId }));
224 assert.deepEqual(Array.from(observed), [state]);
225 assert.deepEqual(harness.calls, []);
226 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
227 );
228​
229 it.effect("protects another instance's binary found through its own PATH", () =>
230 Effect.gen(function* () {
231 const fs = yield* FileSystem.FileSystem;
232 const path = yield* Path.Path;
233 const platform = yield* HostProcess.Platform;
234 const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-provider-install-route-" });
235 const binary = platform === "win32" ? "agy-test.exe" : "agy-test";
236 const executable = path.join(directory, binary);
237 yield* fs.writeFileString(executable, "test");
238 yield* fs.chmod(executable, 0o755);
239 const other = ProviderInstanceId.make("antigravity-work");
240 const harness = yield* makeHarness({
241 settings: {
242 providerInstances: {
243 [other]: {
244 driver,
245 config: { binaryPath: binary },
246 environment: [{ name: "PATH", value: directory, sensitive: false }],
247 },
248 },
249 },
250 });
251 yield* harness.router.remove({ instanceId });
252 assert.include(harness.protectedPaths(), executable);
253 assert.deepEqual(harness.calls, ["remove", "refresh"]);
254 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
255 );
256});