234 lines · 8.1 KB · TypeScript
RawDownload
import assert from "node:assert/strict"; import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import { Effect, Layer } from "effect";import { beforeEach } from "vitest"; import { ServerConfig } from "../../config.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { OpenCodeProvider } from "../Services/OpenCodeProvider.ts";import {  OpenCodeRuntime,  OpenCodeRuntimeError,  type OpenCodeRuntimeShape,} from "../opencodeRuntime.ts";import { OpenCodeProviderLive } from "./OpenCodeProvider.ts";import type { OpenCodeInventory } from "../opencodeRuntime.ts"; const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n"; const runtimeMock = {  state: {    runVersionError: null as Error | null,    versionStdout: DEFAULT_VERSION_STDOUT,    inventoryError: null as Error | null,    inventory: {      providerList: { connected: [] as string[], all: [] as unknown[], default: {} },      agents: [] as unknown[],    } as unknown,  },  reset() {    this.state.runVersionError = null;    this.state.versionStdout = DEFAULT_VERSION_STDOUT;    this.state.inventoryError = null;    this.state.inventory = {      providerList: { connected: [], all: [] as unknown[], default: {} },      agents: [] as unknown[],    };  },}; const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {  startOpenCodeServerProcess: () =>    Effect.succeed({      url: "http://127.0.0.1:4301",      exitCode: Effect.never,    }),  connectToOpenCodeServer: ({ serverUrl }) =>    Effect.succeed({      url: serverUrl ?? "http://127.0.0.1:4301",      exitCode: null,      external: Boolean(serverUrl),    }),  runOpenCodeCommand: () =>    runtimeMock.state.runVersionError      ? Effect.fail(          new OpenCodeRuntimeError({            operation: "runOpenCodeCommand",            detail: runtimeMock.state.runVersionError.message,            cause: runtimeMock.state.runVersionError,          }),        )      : Effect.succeed({ stdout: runtimeMock.state.versionStdout, stderr: "", code: 0 }),  createOpenCodeSdkClient: () =>    ({}) as unknown as ReturnType<OpenCodeRuntimeShape["createOpenCodeSdkClient"]>,  loadOpenCodeInventory: () =>    runtimeMock.state.inventoryError      ? Effect.fail(          new OpenCodeRuntimeError({            operation: "loadOpenCodeInventory",            detail: runtimeMock.state.inventoryError.message,            cause: runtimeMock.state.inventoryError,          }),        )      : Effect.succeed(runtimeMock.state.inventory as OpenCodeInventory),}; beforeEach(() => {  runtimeMock.reset();}); const makeTestLayer = (settingsOverrides?: Parameters<typeof ServerSettingsService.layerTest>[0]) =>  OpenCodeProviderLive.pipe(    Layer.provideMerge(Layer.succeed(OpenCodeRuntime, OpenCodeRuntimeTestDouble)),    Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),    Layer.provideMerge(ServerSettingsService.layerTest(settingsOverrides)),    Layer.provideMerge(NodeServices.layer),  ); it.layer(makeTestLayer())("OpenCodeProviderLive", (it) => {  it.effect("shows a codex-style missing binary message", () =>    Effect.gen(function* () {      runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT");      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, false);      assert.equal(snapshot.message, "OpenCode CLI (`opencode`) is not installed or not on PATH.");    }),  );   it.effect("hides generic Effect.tryPromise text for local CLI probe failures", () =>    Effect.gen(function* () {      runtimeMock.state.runVersionError = new Error("An error occurred in Effect.tryPromise");      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, true);      assert.equal(snapshot.message, "Failed to execute OpenCode CLI health check.");    }),  );   it.effect("refuses to probe when opencode is older than the required minimum", () =>    Effect.gen(function* () {      runtimeMock.state.versionStdout = "opencode 1.4.7\n";      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, true);      assert.equal(snapshot.version, "1.4.7");      assert.ok(snapshot.message?.includes("1.14.19"));      assert.ok(snapshot.message?.toLowerCase().includes("upgrade"));    }),  );   it.effect("refuses to probe when opencode --version output is unparseable", () =>    Effect.gen(function* () {      runtimeMock.state.versionStdout = "garbled binary output\n";      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, true);      assert.equal(snapshot.version, null);      assert.ok(snapshot.message?.includes("1.14.19"));    }),  );   it.effect("emits OpenCode variant defaults so trait picker can resolve a visible selection", () =>    Effect.gen(function* () {      runtimeMock.state.inventory = {        providerList: {          connected: ["openai"],          all: [            {              id: "openai",              name: "OpenAI",              models: {                "gpt-5.4": {                  id: "gpt-5.4",                  name: "GPT-5.4",                  variants: {                    none: {},                    low: {},                    medium: {},                    high: {},                    xhigh: {},                  },                },              },            },          ],          default: {},        },        agents: [          { name: "build", hidden: false, mode: "primary" },          { name: "plan", hidden: false, mode: "primary" },        ],      };       const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;      const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4");       assert.ok(model);      const variantDescriptor = model.capabilities?.optionDescriptors?.find(        (descriptor) => descriptor.id === "variant" && descriptor.type === "select",      );      assert.ok(variantDescriptor && variantDescriptor.type === "select");      assert.equal(variantDescriptor.options.find((option) => option.isDefault)?.id, "medium");      const agentDescriptor = model.capabilities?.optionDescriptors?.find(        (descriptor) => descriptor.id === "agent" && descriptor.type === "select",      );      assert.ok(agentDescriptor && agentDescriptor.type === "select");      assert.equal(agentDescriptor.options.find((option) => option.isDefault)?.id, "build");    }),  );}); it.layer(  makeTestLayer({    providers: {      opencode: {        serverUrl: "http://127.0.0.1:9999",        serverPassword: "secret-password",      },    },  }),)("OpenCodeProviderLive with configured server URL", (it) => {  it.effect("surfaces a friendly auth error for configured servers", () =>    Effect.gen(function* () {      runtimeMock.state.inventoryError = new Error("401 Unauthorized");      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, true);      assert.equal(        snapshot.message,        "OpenCode server rejected authentication. Check the server URL and password.",      );    }),  );   it.effect("surfaces a friendly connection error for configured servers", () =>    Effect.gen(function* () {      runtimeMock.state.inventoryError = new Error(        "fetch failed: connect ECONNREFUSED 127.0.0.1:9999",      );      const provider = yield* OpenCodeProvider;      const snapshot = yield* provider.refresh;       assert.equal(snapshot.status, "error");      assert.equal(snapshot.installed, true);      assert.equal(        snapshot.message,        "Couldn't reach the configured OpenCode server at http://127.0.0.1:9999. Check that the server is running and the URL is correct.",      );    }),  );});