Fixed test failures induced by MCP SDK update (added session id generator to Streamable test server). Cleanup up some port usage to avoid potential issues (always use port 0 for servers). Added stdout/stderr to cli status assertion failures to make CLI test failures easier to debug from test output.
This commit is contained in:
parent
f97725760a
commit
ee29c34bd7
3 changed files with 41 additions and 20 deletions
|
|
@ -1,18 +1,30 @@
|
||||||
import { expect } from "vitest";
|
import { expect } from "vitest";
|
||||||
import type { CliResult } from "./cli-runner.js";
|
import type { CliResult } from "./cli-runner.js";
|
||||||
|
|
||||||
|
function formatCliOutput(result: CliResult): string {
|
||||||
|
const out = result.stdout?.trim() || "(empty)";
|
||||||
|
const err = result.stderr?.trim() || "(empty)";
|
||||||
|
return `stdout: ${out}\nstderr: ${err}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that CLI command succeeded (exit code 0)
|
* Assert that CLI command succeeded (exit code 0)
|
||||||
*/
|
*/
|
||||||
export function expectCliSuccess(result: CliResult) {
|
export function expectCliSuccess(result: CliResult) {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(
|
||||||
|
result.exitCode,
|
||||||
|
`CLI exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
|
||||||
|
).toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that CLI command failed (non-zero exit code)
|
* Assert that CLI command failed (non-zero exit code)
|
||||||
*/
|
*/
|
||||||
export function expectCliFailure(result: CliResult) {
|
export function expectCliFailure(result: CliResult) {
|
||||||
expect(result.exitCode).not.toBe(0);
|
expect(
|
||||||
|
result.exitCode,
|
||||||
|
`CLI unexpectedly exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
|
||||||
|
).not.toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fs from "fs";
|
import * as fs from "fs";
|
||||||
import path from "path";
|
import * as path from "path";
|
||||||
import os from "os";
|
import * as os from "os";
|
||||||
import crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { getTestMcpServerCommand } from "./test-server-stdio.js";
|
import { getTestMcpServerCommand } from "./test-server-stdio.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type { Request, Response } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { createServer as createHttpServer, Server as HttpServer } from "http";
|
import { createServer as createHttpServer, Server as HttpServer } from "http";
|
||||||
import { createServer as createNetServer } from "net";
|
import { createServer as createNetServer } from "net";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import type { ServerConfig } from "./test-fixtures.js";
|
import type { ServerConfig } from "./test-fixtures.js";
|
||||||
|
|
||||||
|
|
@ -187,22 +188,24 @@ export class TestServerHttp {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the server with the specified transport
|
* Start the server with the specified transport.
|
||||||
|
* When requestedPort is omitted, uses port 0 so the OS assigns a unique port (avoids EADDRINUSE when tests run in parallel).
|
||||||
*/
|
*/
|
||||||
async start(
|
async start(
|
||||||
transport: "http" | "sse",
|
transport: "http" | "sse",
|
||||||
requestedPort?: number,
|
requestedPort?: number,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const port = requestedPort
|
const port =
|
||||||
? await findAvailablePort(requestedPort)
|
requestedPort !== undefined ? await findAvailablePort(requestedPort) : 0;
|
||||||
: await findAvailablePort(transport === "http" ? 3001 : 3000);
|
|
||||||
|
|
||||||
this.url = `http://localhost:${port}`;
|
|
||||||
|
|
||||||
if (transport === "http") {
|
if (transport === "http") {
|
||||||
return this.startHttp(port);
|
const actualPort = await this.startHttp(port);
|
||||||
|
this.url = `http://localhost:${actualPort}`;
|
||||||
|
return actualPort;
|
||||||
} else {
|
} else {
|
||||||
return this.startSse(port);
|
const actualPort = await this.startSse(port);
|
||||||
|
this.url = `http://localhost:${actualPort}`;
|
||||||
|
return actualPort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,8 +216,10 @@ export class TestServerHttp {
|
||||||
// Create HTTP server
|
// Create HTTP server
|
||||||
this.httpServer = createHttpServer(app);
|
this.httpServer = createHttpServer(app);
|
||||||
|
|
||||||
// Create StreamableHTTP transport
|
// Create StreamableHTTP transport (stateful so it can handle multiple requests per session)
|
||||||
this.transport = new StreamableHTTPServerTransport({});
|
this.transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: () => randomUUID(),
|
||||||
|
});
|
||||||
|
|
||||||
// Set up Express route to handle MCP requests
|
// Set up Express route to handle MCP requests
|
||||||
app.post("/mcp", async (req: Request, res: Response) => {
|
app.post("/mcp", async (req: Request, res: Response) => {
|
||||||
|
|
@ -292,10 +297,12 @@ export class TestServerHttp {
|
||||||
// Connect transport to server
|
// Connect transport to server
|
||||||
await this.mcpServer.connect(this.transport);
|
await this.mcpServer.connect(this.transport);
|
||||||
|
|
||||||
// Start listening
|
// Start listening (port 0 = OS assigns a unique port)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.httpServer!.listen(port, () => {
|
this.httpServer!.listen(port, () => {
|
||||||
resolve(port);
|
const assignedPort = (this.httpServer!.address() as { port: number })
|
||||||
|
?.port;
|
||||||
|
resolve(assignedPort ?? port);
|
||||||
});
|
});
|
||||||
this.httpServer!.on("error", reject);
|
this.httpServer!.on("error", reject);
|
||||||
});
|
});
|
||||||
|
|
@ -371,10 +378,12 @@ export class TestServerHttp {
|
||||||
// Note: SSE transport is created per request, so we don't store a single instance
|
// Note: SSE transport is created per request, so we don't store a single instance
|
||||||
this.transport = undefined;
|
this.transport = undefined;
|
||||||
|
|
||||||
// Start listening
|
// Start listening (port 0 = OS assigns a unique port)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.httpServer!.listen(port, () => {
|
this.httpServer!.listen(port, () => {
|
||||||
resolve(port);
|
const assignedPort = (this.httpServer!.address() as { port: number })
|
||||||
|
?.port;
|
||||||
|
resolve(assignedPort ?? port);
|
||||||
});
|
});
|
||||||
this.httpServer!.on("error", reject);
|
this.httpServer!.on("error", reject);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue