import { describe, it, assert } from "@effect/vitest";import { ProviderDriverKind, ProviderInstanceId, type ServerProvider, type ServerProviderUpdateState,} from "@t3tools/contracts";import { ServerProviderUpdateError } from "@t3tools/contracts";import * as Cause from "effect/Cause";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { HttpClient, HttpClientResponse } from "effect/unstable/http";import { ChildProcessSpawner } from "effect/unstable/process";import { HostProcessEnvironment, HostProcessPlatform } from "@t3tools/shared/hostProcess";import { SpawnExecutableResolution } from "@t3tools/shared/shell"; import { ProviderRegistry, type ProviderRegistryShape } from "./Services/ProviderRegistry.ts";import * as ProviderMaintenanceRunner from "./providerMaintenanceRunner.ts";import { makeProviderMaintenanceCapabilities, ProviderVersionCache, type ProviderMaintenanceCapabilities,} from "./providerMaintenance.ts";const isServerProviderUpdateError = Schema.is(ServerProviderUpdateError); const CODEX_DRIVER = ProviderDriverKind.make("codex");const CURSOR_DRIVER = ProviderDriverKind.make("cursor");const OPENCODE_DRIVER = ProviderDriverKind.make("opencode");const CODEX_INSTANCE_ID = ProviderInstanceId.make("codex");const CURSOR_INSTANCE_ID = ProviderInstanceId.make("cursor");const OPENCODE_INSTANCE_ID = ProviderInstanceId.make("opencode");const encoder = new TextEncoder(); // Pin a non-win32 platform so `resolveSpawnCommand` is a no-op and the raw// `{ command, args }` assertions below hold deterministically on any host// (including Windows). Windows-specific resolution is covered by the dedicated// win32 case at the end of this suite.const NonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux"); function lifecycleFor(provider: ProviderDriverKind): ProviderMaintenanceCapabilities { if (provider === CURSOR_DRIVER) { return makeProviderMaintenanceCapabilities({ provider, packageName: null, updateExecutable: "cursor-agent", updateArgs: ["update"], updateLockKey: "cursor-agent", }); } return makeProviderMaintenanceCapabilities({ provider, packageName: provider === OPENCODE_DRIVER ? "opencode-ai" : "@openai/codex", updateExecutable: "npm", updateArgs: provider === OPENCODE_DRIVER ? ["install", "-g", "opencode-ai@latest"] : ["install", "-g", "@openai/codex@latest"], updateLockKey: "npm-global", });} const baseProvider: ServerProvider = { instanceId: CODEX_INSTANCE_ID, driver: CODEX_DRIVER, enabled: true, installed: true, version: "0.0.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-04-10T00:00:00.000Z", models: [], slashCommands: [], skills: [],}; const baseCursorProvider: ServerProvider = { ...baseProvider, instanceId: CURSOR_INSTANCE_ID, driver: CURSOR_DRIVER,}; const baseOpenCodeProvider: ServerProvider = { ...baseProvider, instanceId: OPENCODE_INSTANCE_ID, driver: OPENCODE_DRIVER,}; const latestVersionHttpClient = (version: string) => Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed( HttpClientResponse.fromWeb( request, Response.json({ version }, { headers: { "content-type": "application/json" } }), ), ), ), ); function mockHandle(result: { readonly stdout?: string; readonly stderr?: string; readonly code?: number; readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>;}) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: result.exitCode ?? Effect.succeed(ChildProcessSpawner.ExitCode(result.code ?? 0)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.make(encoder.encode(result.stdout ?? "")), stderr: Stream.make(encoder.encode(result.stderr ?? "")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, });} function mockSpawnerLayer( handler: ( command: string, args: ReadonlyArray<string>, options: { readonly env?: NodeJS.ProcessEnv | undefined }, ) => { readonly stdout?: string; readonly stderr?: string; readonly code?: number; readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>; },) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const childProcess = command as unknown as { readonly command: string; readonly args: ReadonlyArray<string>; readonly options: { readonly env?: NodeJS.ProcessEnv | undefined }; }; return Effect.succeed( mockHandle(handler(childProcess.command, childProcess.args, childProcess.options)), ); }), );} function makeRegistry( initialProviders: ServerProvider | ReadonlyArray<ServerProvider> = baseProvider,) { return Effect.gen(function* () { const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>( Array.isArray(initialProviders) ? initialProviders : [initialProviders], ); const updateStatesRef = yield* Ref.make<ReadonlyArray<ServerProviderUpdateState>>([]); const setProviderMaintenanceActionState = Effect.fn( "providerMaintenanceRunner.test.setProviderMaintenanceActionState", )(function* (input: { readonly instanceId: ProviderInstanceId; readonly action: "update"; readonly state: ServerProviderUpdateState | null; }) { const updateState = input.state; if (updateState) { yield* Ref.update(updateStatesRef, (states) => [...states, updateState]); } return yield* Ref.updateAndGet(providersRef, (providers) => providers.map((candidate) => { if (candidate.instanceId !== input.instanceId) { return candidate; } if (!updateState) { const { updateState: _updateState, ...providerWithoutUpdateState } = candidate; return providerWithoutUpdateState; } return { ...candidate, updateState, }; }), ); }); const registry: ProviderRegistryShape = { getProviders: Ref.get(providersRef), refresh: () => Ref.get(providersRef), refreshInstance: () => Ref.get(providersRef), refreshWorkspaceSnapshot: () => Ref.get(providersRef), getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider) => Effect.succeed(lifecycleFor(provider)), setProviderMaintenanceActionState, streamChanges: Stream.empty, }; return { registry, providersRef, updateStatesRef, }; });} const makeTestRunner = (registry: ProviderRegistryShape) => Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe( Effect.provide( ProviderMaintenanceRunner.layer.pipe( Layer.provide( Layer.mergeAll( Layer.succeed(ProviderRegistry, registry), // Fresh per runner so a version cached by one test cannot leak into another. Layer.sync(ProviderVersionCache, () => new Map()), ), ), ), ), ); describe("providerMaintenanceRunner", () => { it.effect("runs the allowlisted provider update command and records success", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry, updateStatesRef } = yield* makeRegistry(baseCursorProvider); const updater = yield* makeTestRunner(registry); const result = yield* updater.updateProvider(CURSOR_DRIVER); assert.deepStrictEqual(calls, [ { command: "cursor-agent", args: ["update"], }, ]); assert.strictEqual(result.providers[0]?.updateState?.status, "succeeded"); assert.deepStrictEqual( (yield* Ref.get(updateStatesRef)).map((state) => state.status), ["queued", "running", "succeeded"], ); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect("reports unchanged when the updater exits 0 but the provider is gone", () => { return Effect.gen(function* () { const { registry, providersRef } = yield* makeRegistry(baseProvider); // After the update, the refreshed snapshot no longer sees an install. const updater = yield* makeTestRunner({ ...registry, refreshInstance: () => Ref.updateAndGet(providersRef, (providers) => providers.map((provider) => ({ ...provider, installed: false, version: null })), ), }); const result = yield* updater.updateProvider(CODEX_DRIVER); assert.strictEqual(result.providers[0]?.updateState?.status, "unchanged"); assert.match(result.providers[0]?.updateState?.message ?? "", /could not verify/); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer(() => ({ stdout: "updated" })), ), ), ); }); it.effect( "keeps a successful update when the binary is present but its version is unreadable", () => { return Effect.gen(function* () { const { registry, providersRef } = yield* makeRegistry(baseCursorProvider); // Cursor's `agent about` probe can fail right after an update while the // new binary is perfectly fine. const updater = yield* makeTestRunner({ ...registry, refreshInstance: () => Ref.updateAndGet(providersRef, (providers) => providers.map((provider) => ({ ...provider, installed: true, version: null })), ), }); const result = yield* updater.updateProvider(CURSOR_DRIVER); assert.strictEqual(result.providers[0]?.updateState?.status, "succeeded"); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer(() => ({ stdout: "updated" })), ), ), ); }, ); it.effect("spawns the updater with the environment its capabilities declare", () => { const seen: Array<NodeJS.ProcessEnv | undefined> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider) => Effect.succeed({ ...lifecycleFor(provider), update: { command: "codex update", executable: "/work/codex-home/packages/standalone/bin/codex", args: ["update"], lockKey: "codex-native", env: { CODEX_HOME: "/work/codex-home" }, }, }), }); yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(seen, [{ CODEX_HOME: "/work/codex-home" }]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((_command, _args, options) => { seen.push(options.env); return { stdout: "updated" }; }), ), ), ); }); it.effect("re-resolves ownership before running and executes the fresh command", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; const fresh: Array<boolean> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider, options) => { fresh.push(options?.fresh === true); return Effect.succeed( makeProviderMaintenanceCapabilities({ provider, packageName: "@openai/codex", updateExecutable: options?.fresh ? "/opt/homebrew/bin/brew" : "brew", updateArgs: ["upgrade", "--cask", "codex"], updateLockKey: "homebrew", }), ); }, }); yield* updater.updateProvider(CODEX_DRIVER); // Cached read picks the lock; the two fresh reads bracket the command. assert.deepStrictEqual(fresh, [false, true, true]); assert.deepStrictEqual(calls, [ { command: "/opt/homebrew/bin/brew", args: ["upgrade", "--cask", "codex"] }, ]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect("aborts without running when the installation changed since the advisory", () => { const calls: Array<string> = []; return Effect.gen(function* () { const { registry, updateStatesRef } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider, options) => Effect.succeed( options?.fresh ? makeProviderMaintenanceCapabilities({ provider, packageName: "@openai/codex", updateExecutable: null, updateArgs: [], updateLockKey: null, }) : lifecycleFor(provider), ), }); const result = yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, []); assert.strictEqual(result.providers[0]?.updateState?.status, "failed"); assert.strictEqual( result.providers[0]?.updateState?.message, "Provider installation changed. Refresh and try again.", ); assert.deepStrictEqual( (yield* Ref.get(updateStatesRef)).map((state) => state.status), ["queued", "running", "failed"], ); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command) => { calls.push(command); return { stdout: "updated" }; }), ), ), ); }); it.effect("uses the resolved provider capabilities when choosing the update executable", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry({ ...baseProvider, versionAdvisory: { status: "behind_latest", currentVersion: "2.0.14", latestVersion: "2.1.123", updateCommand: "bun i -g @anthropic-ai/claude-code@latest", canUpdate: true, checkedAt: "2026-04-30T12:00:00.000Z", message: "Update available.", }, }); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: () => Effect.succeed( makeProviderMaintenanceCapabilities({ provider: CODEX_DRIVER, packageName: "@openai/codex", updateExecutable: "bun", updateArgs: ["i", "-g", "@openai/codex@latest"], updateLockKey: "bun-global", }), ), }); yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, [ { command: "bun", args: ["i", "-g", "@openai/codex@latest"], }, ]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect( "runs update commands through Effect ChildProcess when no test runner is injected", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const runner = yield* makeTestRunner(registry); const result = yield* runner.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, [ { command: "npm", args: ["install", "-g", "@openai/codex@latest"], }, ]);Lines 1–500 of 913. Load more
import { describe, it, assert } from "@effect/vitest"; import { ProviderDriverKind, ProviderInstanceId, type ServerProvider, type ServerProviderUpdateState, } from "@t3tools/contracts"; import { ServerProviderUpdateError } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; import { HostProcessEnvironment, HostProcessPlatform } from "@t3tools/shared/hostProcess"; import { SpawnExecutableResolution } from "@t3tools/shared/shell"; import { ProviderRegistry, type ProviderRegistryShape } from "./Services/ProviderRegistry.ts"; import * as ProviderMaintenanceRunner from "./providerMaintenanceRunner.ts"; import { makeProviderMaintenanceCapabilities, ProviderVersionCache, type ProviderMaintenanceCapabilities, } from "./providerMaintenance.ts"; const isServerProviderUpdateError = Schema.is(ServerProviderUpdateError); const CODEX_DRIVER = ProviderDriverKind.make("codex"); const CURSOR_DRIVER = ProviderDriverKind.make("cursor"); const OPENCODE_DRIVER = ProviderDriverKind.make("opencode"); const CODEX_INSTANCE_ID = ProviderInstanceId.make("codex"); const CURSOR_INSTANCE_ID = ProviderInstanceId.make("cursor"); const OPENCODE_INSTANCE_ID = ProviderInstanceId.make("opencode"); const encoder = new TextEncoder(); // Pin a non-win32 platform so `resolveSpawnCommand` is a no-op and the raw // `{ command, args }` assertions below hold deterministically on any host // (including Windows). Windows-specific resolution is covered by the dedicated // win32 case at the end of this suite. const NonWindowsPlatform = Layer.succeed(HostProcessPlatform, "linux"); function lifecycleFor(provider: ProviderDriverKind): ProviderMaintenanceCapabilities { if (provider === CURSOR_DRIVER) { return makeProviderMaintenanceCapabilities({ provider, packageName: null, updateExecutable: "cursor-agent", updateArgs: ["update"], updateLockKey: "cursor-agent", }); } return makeProviderMaintenanceCapabilities({ provider, packageName: provider === OPENCODE_DRIVER ? "opencode-ai" : "@openai/codex", updateExecutable: "npm", updateArgs: provider === OPENCODE_DRIVER ? ["install", "-g", "opencode-ai@latest"] : ["install", "-g", "@openai/codex@latest"], updateLockKey: "npm-global", }); } const baseProvider: ServerProvider = { instanceId: CODEX_INSTANCE_ID, driver: CODEX_DRIVER, enabled: true, installed: true, version: "0.0.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-04-10T00:00:00.000Z", models: [], slashCommands: [], skills: [], }; const baseCursorProvider: ServerProvider = { ...baseProvider, instanceId: CURSOR_INSTANCE_ID, driver: CURSOR_DRIVER, }; const baseOpenCodeProvider: ServerProvider = { ...baseProvider, instanceId: OPENCODE_INSTANCE_ID, driver: OPENCODE_DRIVER, }; const latestVersionHttpClient = (version: string) => Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed( HttpClientResponse.fromWeb( request, Response.json({ version }, { headers: { "content-type": "application/json" } }), ), ), ), ); function mockHandle(result: { readonly stdout?: string; readonly stderr?: string; readonly code?: number; readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>; }) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: result.exitCode ?? Effect.succeed(ChildProcessSpawner.ExitCode(result.code ?? 0)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.make(encoder.encode(result.stdout ?? "")), stderr: Stream.make(encoder.encode(result.stderr ?? "")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }); } function mockSpawnerLayer( handler: ( command: string, args: ReadonlyArray<string>, options: { readonly env?: NodeJS.ProcessEnv | undefined }, ) => { readonly stdout?: string; readonly stderr?: string; readonly code?: number; readonly exitCode?: Effect.Effect<ChildProcessSpawner.ExitCode>; }, ) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const childProcess = command as unknown as { readonly command: string; readonly args: ReadonlyArray<string>; readonly options: { readonly env?: NodeJS.ProcessEnv | undefined }; }; return Effect.succeed( mockHandle(handler(childProcess.command, childProcess.args, childProcess.options)), ); }), ); } function makeRegistry( initialProviders: ServerProvider | ReadonlyArray<ServerProvider> = baseProvider, ) { return Effect.gen(function* () { const providersRef = yield* Ref.make<ReadonlyArray<ServerProvider>>( Array.isArray(initialProviders) ? initialProviders : [initialProviders], ); const updateStatesRef = yield* Ref.make<ReadonlyArray<ServerProviderUpdateState>>([]); const setProviderMaintenanceActionState = Effect.fn( "providerMaintenanceRunner.test.setProviderMaintenanceActionState", )(function* (input: { readonly instanceId: ProviderInstanceId; readonly action: "update"; readonly state: ServerProviderUpdateState | null; }) { const updateState = input.state; if (updateState) { yield* Ref.update(updateStatesRef, (states) => [...states, updateState]); } return yield* Ref.updateAndGet(providersRef, (providers) => providers.map((candidate) => { if (candidate.instanceId !== input.instanceId) { return candidate; } if (!updateState) { const { updateState: _updateState, ...providerWithoutUpdateState } = candidate; return providerWithoutUpdateState; } return { ...candidate, updateState, }; }), ); }); const registry: ProviderRegistryShape = { getProviders: Ref.get(providersRef), refresh: () => Ref.get(providersRef), refreshInstance: () => Ref.get(providersRef), refreshWorkspaceSnapshot: () => Ref.get(providersRef), getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider) => Effect.succeed(lifecycleFor(provider)), setProviderMaintenanceActionState, streamChanges: Stream.empty, }; return { registry, providersRef, updateStatesRef, }; }); } const makeTestRunner = (registry: ProviderRegistryShape) => Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe( Effect.provide( ProviderMaintenanceRunner.layer.pipe( Layer.provide( Layer.mergeAll( Layer.succeed(ProviderRegistry, registry), // Fresh per runner so a version cached by one test cannot leak into another. Layer.sync(ProviderVersionCache, () => new Map()), ), ), ), ), ); describe("providerMaintenanceRunner", () => { it.effect("runs the allowlisted provider update command and records success", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry, updateStatesRef } = yield* makeRegistry(baseCursorProvider); const updater = yield* makeTestRunner(registry); const result = yield* updater.updateProvider(CURSOR_DRIVER); assert.deepStrictEqual(calls, [ { command: "cursor-agent", args: ["update"], }, ]); assert.strictEqual(result.providers[0]?.updateState?.status, "succeeded"); assert.deepStrictEqual( (yield* Ref.get(updateStatesRef)).map((state) => state.status), ["queued", "running", "succeeded"], ); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect("reports unchanged when the updater exits 0 but the provider is gone", () => { return Effect.gen(function* () { const { registry, providersRef } = yield* makeRegistry(baseProvider); // After the update, the refreshed snapshot no longer sees an install. const updater = yield* makeTestRunner({ ...registry, refreshInstance: () => Ref.updateAndGet(providersRef, (providers) => providers.map((provider) => ({ ...provider, installed: false, version: null })), ), }); const result = yield* updater.updateProvider(CODEX_DRIVER); assert.strictEqual(result.providers[0]?.updateState?.status, "unchanged"); assert.match(result.providers[0]?.updateState?.message ?? "", /could not verify/); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer(() => ({ stdout: "updated" })), ), ), ); }); it.effect( "keeps a successful update when the binary is present but its version is unreadable", () => { return Effect.gen(function* () { const { registry, providersRef } = yield* makeRegistry(baseCursorProvider); // Cursor's `agent about` probe can fail right after an update while the // new binary is perfectly fine. const updater = yield* makeTestRunner({ ...registry, refreshInstance: () => Ref.updateAndGet(providersRef, (providers) => providers.map((provider) => ({ ...provider, installed: true, version: null })), ), }); const result = yield* updater.updateProvider(CURSOR_DRIVER); assert.strictEqual(result.providers[0]?.updateState?.status, "succeeded"); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer(() => ({ stdout: "updated" })), ), ), ); }, ); it.effect("spawns the updater with the environment its capabilities declare", () => { const seen: Array<NodeJS.ProcessEnv | undefined> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider) => Effect.succeed({ ...lifecycleFor(provider), update: { command: "codex update", executable: "/work/codex-home/packages/standalone/bin/codex", args: ["update"], lockKey: "codex-native", env: { CODEX_HOME: "/work/codex-home" }, }, }), }); yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(seen, [{ CODEX_HOME: "/work/codex-home" }]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((_command, _args, options) => { seen.push(options.env); return { stdout: "updated" }; }), ), ), ); }); it.effect("re-resolves ownership before running and executes the fresh command", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; const fresh: Array<boolean> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider, options) => { fresh.push(options?.fresh === true); return Effect.succeed( makeProviderMaintenanceCapabilities({ provider, packageName: "@openai/codex", updateExecutable: options?.fresh ? "/opt/homebrew/bin/brew" : "brew", updateArgs: ["upgrade", "--cask", "codex"], updateLockKey: "homebrew", }), ); }, }); yield* updater.updateProvider(CODEX_DRIVER); // Cached read picks the lock; the two fresh reads bracket the command. assert.deepStrictEqual(fresh, [false, true, true]); assert.deepStrictEqual(calls, [ { command: "/opt/homebrew/bin/brew", args: ["upgrade", "--cask", "codex"] }, ]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect("aborts without running when the installation changed since the advisory", () => { const calls: Array<string> = []; return Effect.gen(function* () { const { registry, updateStatesRef } = yield* makeRegistry(baseProvider); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: (_instanceId, provider, options) => Effect.succeed( options?.fresh ? makeProviderMaintenanceCapabilities({ provider, packageName: "@openai/codex", updateExecutable: null, updateArgs: [], updateLockKey: null, }) : lifecycleFor(provider), ), }); const result = yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, []); assert.strictEqual(result.providers[0]?.updateState?.status, "failed"); assert.strictEqual( result.providers[0]?.updateState?.message, "Provider installation changed. Refresh and try again.", ); assert.deepStrictEqual( (yield* Ref.get(updateStatesRef)).map((state) => state.status), ["queued", "running", "failed"], ); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command) => { calls.push(command); return { stdout: "updated" }; }), ), ), ); }); it.effect("uses the resolved provider capabilities when choosing the update executable", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry({ ...baseProvider, versionAdvisory: { status: "behind_latest", currentVersion: "2.0.14", latestVersion: "2.1.123", updateCommand: "bun i -g @anthropic-ai/claude-code@latest", canUpdate: true, checkedAt: "2026-04-30T12:00:00.000Z", message: "Update available.", }, }); const updater = yield* makeTestRunner({ ...registry, getProviderMaintenanceCapabilitiesForInstance: () => Effect.succeed( makeProviderMaintenanceCapabilities({ provider: CODEX_DRIVER, packageName: "@openai/codex", updateExecutable: "bun", updateArgs: ["i", "-g", "@openai/codex@latest"], updateLockKey: "bun-global", }), ), }); yield* updater.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, [ { command: "bun", args: ["i", "-g", "@openai/codex@latest"], }, ]); }).pipe( Effect.provide( Layer.mergeAll( NonWindowsPlatform, latestVersionHttpClient("0.0.0"), mockSpawnerLayer((command, args) => { calls.push({ command, args }); return { stdout: "updated" }; }), ), ), ); }); it.effect( "runs update commands through Effect ChildProcess when no test runner is injected", () => { const calls: Array<{ command: string; args: ReadonlyArray<string> }> = []; return Effect.gen(function* () { const { registry } = yield* makeRegistry(baseProvider); const runner = yield* makeTestRunner(registry); const result = yield* runner.updateProvider(CODEX_DRIVER); assert.deepStrictEqual(calls, [ { command: "npm", args: ["install", "-g", "@openai/codex@latest"], }, ]);