141 lines
3.7 KiB
TypeScript
141 lines
3.7 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
import { DaemonClientV2, type DaemonTransport } from "./daemon-client-v2";
|
|
|
|
function createMockLogger() {
|
|
return {
|
|
debug: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createMockTransport() {
|
|
const sent: string[] = [];
|
|
|
|
let onMessage: (data: unknown) => void = () => {};
|
|
let onOpen: () => void = () => {};
|
|
let onClose: (_event?: unknown) => void = () => {};
|
|
let onError: (_event?: unknown) => void = () => {};
|
|
|
|
const transport: DaemonTransport = {
|
|
send: (data) => sent.push(data),
|
|
close: () => {},
|
|
onMessage: (handler) => {
|
|
onMessage = handler;
|
|
return () => {};
|
|
},
|
|
onOpen: (handler) => {
|
|
onOpen = handler;
|
|
return () => {};
|
|
},
|
|
onClose: (handler) => {
|
|
onClose = handler;
|
|
return () => {};
|
|
},
|
|
onError: (handler) => {
|
|
onError = handler;
|
|
return () => {};
|
|
},
|
|
};
|
|
|
|
return {
|
|
transport,
|
|
sent,
|
|
triggerOpen: () => onOpen(),
|
|
triggerClose: (event?: unknown) => onClose(event),
|
|
triggerError: (event?: unknown) => onError(event),
|
|
triggerMessage: (data: unknown) => onMessage(data),
|
|
};
|
|
}
|
|
|
|
describe("DaemonClientV2", () => {
|
|
const clients: DaemonClientV2[] = [];
|
|
|
|
afterEach(async () => {
|
|
for (const client of clients) {
|
|
await client.close();
|
|
}
|
|
clients.length = 0;
|
|
});
|
|
|
|
test("dedupes in-flight checkout status requests per agentId", async () => {
|
|
const logger = createMockLogger();
|
|
const mock = createMockTransport();
|
|
|
|
const client = new DaemonClientV2({
|
|
url: "ws://test",
|
|
logger,
|
|
reconnect: { enabled: false },
|
|
transportFactory: () => mock.transport,
|
|
});
|
|
clients.push(client);
|
|
|
|
const connectPromise = client.connect();
|
|
mock.triggerOpen();
|
|
await connectPromise;
|
|
|
|
const p1 = client.getCheckoutStatus("/tmp/project");
|
|
const p2 = client.getCheckoutStatus("/tmp/project");
|
|
|
|
expect(mock.sent).toHaveLength(1);
|
|
|
|
const request = JSON.parse(mock.sent[0]) as {
|
|
type: "session";
|
|
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
|
};
|
|
|
|
const response = {
|
|
type: "session",
|
|
message: {
|
|
type: "checkout_status_response",
|
|
payload: {
|
|
cwd: "/tmp/project",
|
|
error: null,
|
|
requestId: request.message.requestId,
|
|
isGit: false,
|
|
isPaseoOwnedWorktree: false,
|
|
repoRoot: null,
|
|
currentBranch: null,
|
|
isDirty: null,
|
|
baseRef: null,
|
|
aheadBehind: null,
|
|
aheadOfOrigin: null,
|
|
hasRemote: false,
|
|
remoteUrl: null,
|
|
},
|
|
},
|
|
};
|
|
|
|
mock.triggerMessage(JSON.stringify(response));
|
|
const [r1, r2] = await Promise.all([p1, p2]);
|
|
expect(r1).toMatchObject({ cwd: "/tmp/project", requestId: request.message.requestId, isGit: false });
|
|
expect(r2).toMatchObject({ cwd: "/tmp/project", requestId: request.message.requestId, isGit: false });
|
|
|
|
// After completion, a new call should issue a new request.
|
|
const p3 = client.getCheckoutStatus("/tmp/project");
|
|
expect(mock.sent).toHaveLength(2);
|
|
|
|
const request2 = JSON.parse(mock.sent[1]) as {
|
|
type: "session";
|
|
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
|
};
|
|
|
|
mock.triggerMessage(
|
|
JSON.stringify({
|
|
...response,
|
|
message: {
|
|
...response.message,
|
|
payload: { ...response.message.payload, requestId: request2.message.requestId },
|
|
},
|
|
})
|
|
);
|
|
|
|
await expect(p3).resolves.toMatchObject({
|
|
cwd: "/tmp/project",
|
|
requestId: request2.message.requestId,
|
|
isGit: false,
|
|
});
|
|
});
|
|
});
|