tests: cover agent sequencing, tick watchdog, presence fingerprint
This commit is contained in:
67
src/gateway/client.test.ts
Normal file
67
src/gateway/client.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createServer } from "node:net";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { GatewayClient } from "./client.js";
|
||||
|
||||
// Find a free localhost port for ad-hoc WS servers.
|
||||
async function getFreePort(): Promise<number> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const server = createServer();
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const port = (server.address() as { port: number }).port;
|
||||
server.close((err) => (err ? reject(err) : resolve(port)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("GatewayClient", () => {
|
||||
let wss: WebSocketServer | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (wss) {
|
||||
await new Promise<void>((resolve) => wss?.close(() => resolve()));
|
||||
wss = null;
|
||||
}
|
||||
});
|
||||
|
||||
test("closes on missing ticks", async () => {
|
||||
const port = await getFreePort();
|
||||
wss = new WebSocketServer({ port, host: "127.0.0.1" });
|
||||
|
||||
wss.on("connection", (socket) => {
|
||||
socket.once("message", () => {
|
||||
// Respond with tiny tick interval to trigger watchdog quickly.
|
||||
const helloOk = {
|
||||
type: "hello-ok",
|
||||
protocol: 1,
|
||||
server: { version: "dev", connId: "c1" },
|
||||
features: { methods: [], events: [] },
|
||||
snapshot: {
|
||||
presence: [],
|
||||
health: {},
|
||||
stateVersion: { presence: 1, health: 1 },
|
||||
uptimeMs: 1,
|
||||
},
|
||||
policy: {
|
||||
maxPayload: 512 * 1024,
|
||||
maxBufferedBytes: 1024 * 1024,
|
||||
tickIntervalMs: 5,
|
||||
},
|
||||
};
|
||||
socket.send(JSON.stringify(helloOk));
|
||||
});
|
||||
});
|
||||
|
||||
const closed = new Promise<{ code: number; reason: string }>((resolve) => {
|
||||
const client = new GatewayClient({
|
||||
url: `ws://127.0.0.1:${port}`,
|
||||
onClose: (code, reason) => resolve({ code, reason }),
|
||||
});
|
||||
client.start();
|
||||
});
|
||||
|
||||
const res = await closed;
|
||||
expect(res.code).toBe(4000);
|
||||
expect(res.reason).toContain("tick timeout");
|
||||
}, 4000);
|
||||
});
|
||||
@@ -538,4 +538,47 @@ describe("gateway server", () => {
|
||||
ws2.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("presence includes client fingerprint", async () => {
|
||||
const { server, ws } = await startServerWithClient();
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "hello",
|
||||
minProtocol: 1,
|
||||
maxProtocol: 1,
|
||||
client: {
|
||||
name: "fingerprint",
|
||||
version: "9.9.9",
|
||||
platform: "test",
|
||||
mode: "ui",
|
||||
instanceId: "abc",
|
||||
},
|
||||
caps: [],
|
||||
}),
|
||||
);
|
||||
await onceMessage(ws, (o) => o.type === "hello-ok");
|
||||
|
||||
const presenceP = onceMessage(
|
||||
ws,
|
||||
(o) => o.type === "res" && o.id === "fingerprint",
|
||||
4000,
|
||||
);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "req",
|
||||
id: "fingerprint",
|
||||
method: "system-presence",
|
||||
}),
|
||||
);
|
||||
|
||||
const presenceRes = await presenceP;
|
||||
const entries = presenceRes.payload as Array<Record<string, unknown>>;
|
||||
const clientEntry = entries.find((e) => e.instanceId === "abc");
|
||||
expect(clientEntry?.host).toBe("fingerprint");
|
||||
expect(clientEntry?.version).toBe("9.9.9");
|
||||
expect(clientEntry?.mode).toBe("ui");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
});
|
||||
|
||||
23
src/infra/agent-events.test.ts
Normal file
23
src/infra/agent-events.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { emitAgentEvent, onAgentEvent } from "./agent-events.js";
|
||||
|
||||
describe("agent-events sequencing", () => {
|
||||
test("maintains monotonic seq per runId", async () => {
|
||||
const seen: Record<string, number[]> = {};
|
||||
const stop = onAgentEvent((evt) => {
|
||||
const list = seen[evt.runId] ?? [];
|
||||
seen[evt.runId] = list;
|
||||
list.push(evt.seq);
|
||||
});
|
||||
|
||||
emitAgentEvent({ runId: "run-1", stream: "job", data: {} });
|
||||
emitAgentEvent({ runId: "run-1", stream: "job", data: {} });
|
||||
emitAgentEvent({ runId: "run-2", stream: "job", data: {} });
|
||||
emitAgentEvent({ runId: "run-1", stream: "job", data: {} });
|
||||
|
||||
stop();
|
||||
|
||||
expect(seen["run-1"]).toEqual([1, 2, 3]);
|
||||
expect(seen["run-2"]).toEqual([1]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user