87 lines
3.2 KiB
TypeScript
87 lines
3.2 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
import { expect } from "vitest";
|
|
import { test } from "@openwork/testkit";
|
|
|
|
const repoRoot = resolve(import.meta.dirname, "../..");
|
|
|
|
test("MCP agent rejects standalone GET without triggering the Bun SDK retry loop", ({ evidence }) => {
|
|
const reportDir = mkdtempSync(join(tmpdir(), "openwork-mcp-agent-standalone-get-"));
|
|
try {
|
|
const build = spawnSync("pnpm", [
|
|
"--filter",
|
|
"@openwork-ee/den-api",
|
|
"run",
|
|
"build:mcp-apps",
|
|
], {
|
|
cwd: repoRoot,
|
|
encoding: "utf8",
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
timeout: 120_000,
|
|
});
|
|
const buildOutput = `${build.stdout}${build.stderr}`;
|
|
expect(build.error, buildOutput).toBeUndefined();
|
|
expect(build.status, buildOutput).toBe(0);
|
|
|
|
const suites = [
|
|
{ file: "test/agent-mcp-routes.test.ts", tests: 3 },
|
|
{ file: "test/mcp-membership-revocation.test.ts", tests: 20 },
|
|
{ file: "test/mcp-agent-standalone-get.test.ts", tests: 2 },
|
|
];
|
|
for (const [index, suite] of suites.entries()) {
|
|
const reportPath = join(reportDir, `bun-junit-${String(index)}.xml`);
|
|
const result = spawnSync("pnpm", [
|
|
"--filter",
|
|
"@openwork-ee/den-api",
|
|
"exec",
|
|
"bun",
|
|
"test",
|
|
"--conditions",
|
|
"development",
|
|
"--timeout",
|
|
"15000",
|
|
suite.file,
|
|
"--reporter=junit",
|
|
"--reporter-outfile",
|
|
reportPath,
|
|
], {
|
|
cwd: repoRoot,
|
|
encoding: "utf8",
|
|
maxBuffer: 10 * 1024 * 1024,
|
|
timeout: 60_000,
|
|
});
|
|
const output = `${result.stdout}${result.stderr}`;
|
|
expect(result.error, output).toBeUndefined();
|
|
expect(result.status, output).toBe(0);
|
|
|
|
const junit = readFileSync(reportPath, "utf8");
|
|
const summary = junit.match(/<testsuites\b[^>]*>/)?.[0] ?? "";
|
|
expect(summary).toContain(`tests="${String(suite.tests)}"`);
|
|
expect(summary).toContain('failures="0"');
|
|
expect(summary).toContain('skipped="0"');
|
|
expect(junit).not.toContain("<failure");
|
|
expect(junit).not.toContain("<skipped");
|
|
}
|
|
|
|
evidence.recordAssertionEvidence(
|
|
"Anonymous standalone GET still returns the OAuth discovery challenge",
|
|
"The focused route witness sends GET /mcp/agent without a bearer and requires the RFC 9728 HTTP 401 challenge instead of bypassing authentication.",
|
|
true,
|
|
);
|
|
evidence.recordAssertionEvidence(
|
|
"Authenticated standalone GET is rejected as an unsupported method",
|
|
"The focused route witness verifies the bearer first, then requires HTTP 405 with Allow: POST and an empty body.",
|
|
true,
|
|
);
|
|
evidence.recordAssertionEvidence(
|
|
"Bun and MCP SDK 1.29.0 do not reopen the rejected standalone listener",
|
|
"The network witness starts the real StreamableHTTPClientTransport, sends notifications/initialized, observes exactly one GET, waits 2.25 seconds past two default retry intervals, and still observes exactly one GET.",
|
|
true,
|
|
);
|
|
} finally {
|
|
rmSync(reportDir, { force: true, recursive: true });
|
|
}
|
|
});
|