96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { allocateFreePort } from "@openwork/cdp";
|
|
import { startMockMcp } from "../src/mock-mcp.ts";
|
|
|
|
test("records an unauthenticated initialize attempt as a handshake", async () => {
|
|
await using mock = await startMockMcp({ port: await allocateFreePort() });
|
|
const startedAt = new Date().toISOString();
|
|
|
|
const response = await fetch(mock.mcpUrl, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: "initialize",
|
|
params: {
|
|
protocolVersion: "2025-06-18",
|
|
capabilities: {},
|
|
clientInfo: { name: "mock-handshake-test", version: "1.0.0" },
|
|
},
|
|
}),
|
|
});
|
|
|
|
assert.equal(response.status, 401);
|
|
const handshakes = await mock.handshakes({ sinceIso: startedAt });
|
|
assert.equal(handshakes.length, 1);
|
|
assert.equal(handshakes[0]?.method, "POST");
|
|
assert.equal(handshakes[0]?.path, "/mcp");
|
|
});
|
|
|
|
function completionBody(marker: string, completedTools: number): Record<string, unknown> {
|
|
return {
|
|
model: "mock-agent-workload-model",
|
|
stream: true,
|
|
tools: [
|
|
{ type: "function", function: { name: "write", parameters: { type: "object" } } },
|
|
{ type: "function", function: { name: "read", parameters: { type: "object" } } },
|
|
],
|
|
messages: [
|
|
{ role: "user", content: `run ${marker}` },
|
|
...Array.from({ length: completedTools }, (_, index) => ({
|
|
role: "tool",
|
|
tool_call_id: `call-${index}`,
|
|
content: `tool result ${index}`,
|
|
})),
|
|
],
|
|
};
|
|
}
|
|
|
|
test("scripts and records deterministic OpenAI-compatible agent tool rounds", async () => {
|
|
const marker = "agent-workload-unit-marker";
|
|
await using mock = await startMockMcp({
|
|
port: await allocateFreePort(),
|
|
agentWorkloads: [{
|
|
promptMarker: marker,
|
|
finalReply: "unit workload complete",
|
|
steps: [
|
|
{ tool: "write", arguments: { filePath: "/tmp/unit.txt", content: marker } },
|
|
{ tool: "read", arguments: { filePath: "/tmp/unit.txt" } },
|
|
],
|
|
}],
|
|
});
|
|
const startedAt = new Date().toISOString();
|
|
|
|
const first = await fetch(`${mock.url}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(completionBody(marker, 0)),
|
|
});
|
|
const firstText = await first.text();
|
|
assert.equal(first.status, 200);
|
|
assert.match(firstText, /"name":"write"/);
|
|
assert.match(firstText, /unit\.txt/);
|
|
|
|
const second = await fetch(`${mock.url}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(completionBody(marker, 1)),
|
|
});
|
|
assert.match(await second.text(), /"name":"read"/);
|
|
|
|
const final = await fetch(`${mock.url}/v1/chat/completions`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(completionBody(marker, 2)),
|
|
});
|
|
assert.match(await final.text(), /unit workload complete/);
|
|
|
|
const requests = await mock.agentRequests({ promptMarker: marker, sinceIso: startedAt, atLeast: 3, timeoutMs: 5_000 });
|
|
assert.deepEqual(requests.map((request) => request.kind), ["tool", "tool", "final"]);
|
|
assert.deepEqual(requests.map((request) => request.completedTools), [0, 1, 2]);
|
|
assert.deepEqual(requests.map((request) => request.matchedMarkers), [[marker], [marker], [marker]]);
|
|
});
|