913 lines · 31.8 KB · TypeScript
RawDownload
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.