1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
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<string, unknown>): HostAPI {
const api = {
provideService<T>(handle: ServiceHandle<T>, impl: T): void {
services.set(handle.id, impl);
},
getService<T>(handle: ServiceHandle<T>): 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<string, unknown>();
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<string, unknown>();
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<string, unknown>();
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<string, unknown>();
const host = createFakeHost(services);
host.provideService(remoteExecBackendFactoryHandle, () => createFakeRemoteBackend("unused"));
createExecBackendExtension().activate(host);
const resolver = host.getService(execBackendHandle);
expect(resolver(undefined)).toBe(localExecBackend);
});
});
|