* Extract client SDK package * Polish SDK client identity defaults * Build client before dependent CI jobs * Restore daemon client server export * Extract protocol and client SDK packages * Fix provider override schema validation * Fix app test daemon client imports * Simplify workspace build targets * Fix CLI test server build bootstrap * Run SDK package tests in CI * Fix rebase package split drift * Restore lockfile registry metadata * Update SDK config test for prompt default * Move terminal stream router test to client package * Fix rebase drift for protocol imports * Fix SDK agent capability fixture * Restore legacy server client exports * Fix server export compatibility test * Advertise custom mode icon client capability * Remove server daemon-client exports * Format rebased mode control import * Fix rebase drift for protocol imports Files added by upstream PRs (#893, #1147, #1154) referenced the pre-split shared/ paths that this branch moves into @getpaseo/protocol. Redirect those imports to the protocol package so typecheck stays green after the rebase.
212 lines
5.9 KiB
TypeScript
212 lines
5.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { DaemonClientConfig } from "@getpaseo/client/internal/daemon-client";
|
|
import type { DaemonConnectionDependencies, DaemonProbeClient } from "./test-daemon-connection";
|
|
|
|
class FakeDaemonClient implements DaemonProbeClient {
|
|
readonly lastError: string | null;
|
|
|
|
constructor(
|
|
private readonly probe: FakeDaemonProbe,
|
|
readonly config: DaemonClientConfig,
|
|
) {
|
|
this.lastError = probe.nextLastError;
|
|
}
|
|
|
|
async connect(): Promise<void> {
|
|
if (this.probe.nextConnectError) {
|
|
throw this.probe.nextConnectError;
|
|
}
|
|
}
|
|
|
|
getLastServerInfoMessage() {
|
|
return {
|
|
serverId: "srv_probe_test",
|
|
hostname: "probe-host",
|
|
};
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
this.probe.closedClients.push(this);
|
|
}
|
|
}
|
|
|
|
class FakeDaemonProbe {
|
|
createdClients: FakeDaemonClient[] = [];
|
|
closedClients: FakeDaemonClient[] = [];
|
|
clientIdsRequested = 0;
|
|
nextConnectError: Error | null = null;
|
|
nextLastError: string | null = null;
|
|
|
|
readonly deps: DaemonConnectionDependencies<FakeDaemonClient> = {
|
|
getClientId: async () => {
|
|
this.clientIdsRequested += 1;
|
|
return "cid_shared_probe_test";
|
|
},
|
|
resolveAppVersion: () => null,
|
|
createLocalTransportFactory: () => null,
|
|
buildLocalTransportUrl: ({ transportType, transportPath }) =>
|
|
`paseo+local://${transportType}?path=${encodeURIComponent(transportPath)}`,
|
|
createClient: (config) => {
|
|
const client = new FakeDaemonClient(this, config);
|
|
this.createdClients.push(client);
|
|
return client;
|
|
},
|
|
};
|
|
|
|
failNextConnection(error: Error, lastError: string | null): void {
|
|
this.nextConnectError = error;
|
|
this.nextLastError = lastError;
|
|
}
|
|
|
|
createdConfigs(): DaemonClientConfig[] {
|
|
return this.createdClients.map((client) => client.config);
|
|
}
|
|
}
|
|
|
|
describe("test-daemon-connection connectToDaemon", () => {
|
|
let probe: FakeDaemonProbe;
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal("__DEV__", false);
|
|
probe = new FakeDaemonProbe();
|
|
});
|
|
|
|
it("reuses the app clientId for direct connections", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const first = await connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
);
|
|
await first.client.close();
|
|
|
|
const second = await connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
);
|
|
await second.client.close();
|
|
|
|
const [firstConfig, secondConfig] = probe.createdConfigs();
|
|
expect(firstConfig?.clientId).toBe("cid_shared_probe_test");
|
|
expect(secondConfig?.clientId).toBe("cid_shared_probe_test");
|
|
expect(probe.clientIdsRequested).toBe(2);
|
|
});
|
|
|
|
it("encodes the local socket target into the client config", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const result = await connectToDaemon(
|
|
{
|
|
id: "socket:/tmp/paseo.sock",
|
|
type: "directSocket",
|
|
path: "/tmp/paseo.sock",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.url).toBe("paseo+local://socket?path=%2Ftmp%2Fpaseo.sock");
|
|
});
|
|
|
|
it("passes direct TCP connection passwords into the client config", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const result = await connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
password: "shared-secret",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.password).toBe("shared-secret");
|
|
});
|
|
|
|
it("uses relay TLS from the stored connection", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const tlsResult = await connectToDaemon(
|
|
{
|
|
id: "relay:wss:[::1]:443",
|
|
type: "relay",
|
|
relayEndpoint: "[::1]:443",
|
|
useTls: true,
|
|
daemonPublicKeyB64: "pubkey",
|
|
},
|
|
{ serverId: "srv_probe_test" },
|
|
probe.deps,
|
|
);
|
|
await tlsResult.client.close();
|
|
|
|
const plainResult = await connectToDaemon(
|
|
{
|
|
id: "relay:relay.paseo.sh:443",
|
|
type: "relay",
|
|
relayEndpoint: "relay.paseo.sh:443",
|
|
useTls: false,
|
|
daemonPublicKeyB64: "pubkey",
|
|
},
|
|
{ serverId: "srv_probe_test" },
|
|
probe.deps,
|
|
);
|
|
await plainResult.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.url).toMatch(/^wss:\/\/\[::1\]\/ws\?/);
|
|
expect(probe.createdConfigs()[1]?.url).toMatch(/^ws:\/\/relay\.paseo\.sh:443\/ws\?/);
|
|
});
|
|
|
|
it("surfaces auth rejection as an incorrect password", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
probe.failNextConnection(
|
|
new Error("Transport closed (code 4001)"),
|
|
"Transport closed (code 4001)",
|
|
);
|
|
|
|
await expect(
|
|
connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
password: "wrong-secret",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
),
|
|
).rejects.toMatchObject({
|
|
message: "Incorrect password",
|
|
});
|
|
});
|
|
|
|
it("keeps generic transport failures generic when a password was supplied", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
probe.failNextConnection(new Error("Transport error"), "Transport error");
|
|
|
|
await expect(
|
|
connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
password: "shared-secret",
|
|
},
|
|
undefined,
|
|
probe.deps,
|
|
),
|
|
).rejects.toMatchObject({
|
|
message: "Transport error",
|
|
});
|
|
});
|
|
});
|