import type { HostAPI, ServiceHandle } from "@dispatch/kernel"; import { describe, expect, it } from "vitest"; import type { ExecBackend } from "./backend.js"; import { createExecBackendExtension } from "./extension.js"; import { localExecBackend } from "./local.js"; import { execBackendHandle, remoteExecBackendFactoryHandle } from "./service.js"; /** * Resolver tests — pure core, zero internal mocks. * * The resolver's ONLY external dependency is `host.getService` (the service * registry — the outermost edge). We inject a minimal fake host that mirrors * the real `bus.getService` contract: returns the provided impl, or throws when * nothing provided the handle. No `vi.mock("@dispatch/*")` — the resolver + * handles + local backend under test are all real. * * Three cases (matching the task spec): * 1. `computerId` undefined → `localExecBackend` (byte-identical local path). * 2. `computerId` set + factory provided → the factory's backend. * 3. `computerId` set + factory NOT provided (ssh not loaded) → a clear * "SSH remote execution is not configured" error, not a crash. */ /** * A minimal fake host exposing only the service-registry surface the resolver * touches (`getService`/`provideService`). Throws on a missing service exactly * like the real `bus.getService`, so the resolver's try/catch path is exercised * against behavior-equivalent input. */ function createFakeHost(services: Map): HostAPI { const api = { provideService(handle: ServiceHandle, impl: T): void { services.set(handle.id, impl); }, getService(handle: ServiceHandle): T { const impl = services.get(handle.id); if (impl === undefined) { throw new Error( `Service "${handle.id}" has no provider. Call provideService before getService.`, ); } return impl as T; }, }; // The resolver only calls getService; the rest of HostAPI is unused here. return api as unknown as HostAPI; } /** A fake remote backend — identifiable so we can assert it's the one returned. */ function createFakeRemoteBackend(marker: string): ExecBackend { const fail = (): never => { throw new Error(`fake remote backend (${marker}) should not be called in this test`); }; return { spawn: fail, readFile: fail, writeFile: fail, stat: fail, readdir: fail, exists: fail, }; } describe("ExecBackend resolver", () => { it("returns localExecBackend for computerId === undefined (local path unchanged)", () => { const services = new Map(); const host = createFakeHost(services); // Activate the extension so it registers its resolver, then retrieve it. createExecBackendExtension().activate(host); const resolver = host.getService(execBackendHandle); expect(resolver(undefined)).toBe(localExecBackend); expect(resolver()).toBe(localExecBackend); }); it("returns the factory's backend for a set computerId when the factory is provided", () => { const services = new Map(); const host = createFakeHost(services); // The `ssh` extension (not built yet) would do this: const remoteBackend = createFakeRemoteBackend("ssh-alias"); const factory = (computerId: string): ExecBackend => { // Confirm the alias is threaded through to the factory. expect(computerId).toBe("ssh-alias"); return remoteBackend; }; host.provideService(remoteExecBackendFactoryHandle, factory); createExecBackendExtension().activate(host); const resolver = host.getService(execBackendHandle); expect(resolver("ssh-alias")).toBe(remoteBackend); }); it("throws a clear 'not configured' error when the factory is NOT provided (ssh not loaded)", () => { const services = new Map(); const host = createFakeHost(services); // No remoteExecBackendFactoryHandle provided → simulates ssh not loaded. createExecBackendExtension().activate(host); const resolver = host.getService(execBackendHandle); // Not a crash: a clear, actionable error mentioning computerId + ssh. expect(() => resolver("some-host")).toThrow(/SSH remote execution is not configured/); expect(() => resolver("some-host")).toThrow(/ssh extension is not loaded/); expect(() => resolver("some-host")).toThrow(/some-host/); }); it("local path is unaffected by whether the factory is provided", () => { // Even with a factory present, computerId === undefined still returns local. const services = new Map(); const host = createFakeHost(services); host.provideService(remoteExecBackendFactoryHandle, () => createFakeRemoteBackend("unused")); createExecBackendExtension().activate(host); const resolver = host.getService(execBackendHandle); expect(resolver(undefined)).toBe(localExecBackend); }); });