mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 05:02:58 +00:00
Reuse verified host-scoped plugin bundles across client launches and reconstruct complete catalogs when unchanged code is omitted. Keep store ownership on the connected client, including adopted bootstrap clients, and persist in the background with bounded writes. Remove catalog catch-up deferral and fix repeated timeline preparation that promoted cached completed history into a live head. This preserves completed plugin-rendered chat when catalog installation precedes timeline catch-up. Carries forward #6732 on an upstream branch, with the original hash/cache contribution and integration attribution preserved. Co-authored-by: luke <luopeihuan@gmail.com> Co-authored-by: RoundRedDot <roundreddot@gmail.com>
301 lines
8.5 KiB
TypeScript
301 lines
8.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { DaemonClientConfig } from "@getpaseo/client/internal/daemon-client";
|
|
import { DaemonAuthenticationError } 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,
|
|
createDesktopTransportFactory: () => null,
|
|
buildDesktopTransportUrl: (target) => {
|
|
if (target.transportType === "ssh") {
|
|
return `paseo+desktop://ssh?host=${encodeURIComponent(target.host)}`;
|
|
}
|
|
return `paseo+desktop://${target.transportType}?path=${encodeURIComponent(target.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("keeps direct TCP probes on the renderer WebSocket", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const deps = {
|
|
...probe.deps,
|
|
createWebSocketTransportFactory: () => {
|
|
throw new Error("Direct TCP must not use the desktop WebSocket bridge");
|
|
},
|
|
};
|
|
|
|
const result = await connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
},
|
|
undefined,
|
|
deps,
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.transportFactory).toBeUndefined();
|
|
});
|
|
|
|
it("keeps the bundle cache factory on a probe client that the host runtime can adopt", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const createPluginClientBundleStore = () => ({
|
|
async load() {
|
|
return [];
|
|
},
|
|
async replace() {},
|
|
});
|
|
const { client } = await connectToDaemon(
|
|
{ id: "direct:lan:6767", type: "directTcp", endpoint: "lan:6767" },
|
|
{ createPluginClientBundleStore },
|
|
probe.deps,
|
|
);
|
|
expect(client.config.createPluginClientBundleStore).toBe(createPluginClientBundleStore);
|
|
await client.close();
|
|
});
|
|
|
|
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+desktop://socket?path=%2Ftmp%2Fpaseo.sock");
|
|
});
|
|
|
|
it("uses the desktop transport for Remote SSH connections", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const transportFactory = vi.fn();
|
|
const result = await connectToDaemon(
|
|
{
|
|
id: "ssh:deploy%40example.com:2222:%2Fkeys%2Fpaseo",
|
|
type: "remoteSsh",
|
|
host: "deploy@example.com",
|
|
sshPort: 2222,
|
|
daemonPort: 7777,
|
|
},
|
|
undefined,
|
|
{
|
|
...probe.deps,
|
|
createDesktopTransportFactory: () => transportFactory,
|
|
},
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]).toMatchObject({
|
|
url: "paseo+desktop://ssh?host=deploy%40example.com",
|
|
transportFactory,
|
|
});
|
|
});
|
|
|
|
it("passes the host password 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" },
|
|
probe.deps,
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.password).toBe("shared-secret");
|
|
});
|
|
|
|
it("passes performance tracing into the connected client", async () => {
|
|
const { connectToDaemon } = await import("./test-daemon-connection");
|
|
const trace = {
|
|
isEnabled: () => true,
|
|
beginSection: vi.fn(),
|
|
endSection: vi.fn(),
|
|
};
|
|
const result = await connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
},
|
|
{ trace },
|
|
probe.deps,
|
|
);
|
|
await result.client.close();
|
|
|
|
expect(probe.createdConfigs()[0]?.trace).toBe(trace);
|
|
});
|
|
|
|
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 DaemonAuthenticationError("incorrect_password"),
|
|
"Transport closed (code 4001)",
|
|
);
|
|
|
|
await expect(
|
|
connectToDaemon(
|
|
{
|
|
id: "direct:lan:6767",
|
|
type: "directTcp",
|
|
endpoint: "lan:6767",
|
|
},
|
|
{ password: "wrong-secret" },
|
|
probe.deps,
|
|
),
|
|
).rejects.toMatchObject({
|
|
message: "Incorrect password",
|
|
authFailureReason: "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" },
|
|
probe.deps,
|
|
),
|
|
).rejects.toMatchObject({
|
|
message: "Transport error",
|
|
});
|
|
});
|
|
});
|