Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions cli/__tests__/helpers/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { expect } from "vitest";
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)
*/
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)
*/
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);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions cli/__tests__/helpers/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "fs";
import path from "path";
import os from "os";
import crypto from "crypto";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import { getTestMcpServerCommand } from "./test-server-stdio.js";

/**
Expand Down
37 changes: 23 additions & 14 deletions cli/__tests__/helpers/test-server-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Request, Response } from "express";
import express from "express";
import { createServer as createHttpServer, Server as HttpServer } from "http";
import { createServer as createNetServer } from "net";
import { randomUUID } from "crypto";
import * as z from "zod/v4";
import type { ServerConfig } from "./test-fixtures.js";

Expand Down Expand Up @@ -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(
transport: "http" | "sse",
requestedPort?: number,
): Promise<number> {
const port = requestedPort
? await findAvailablePort(requestedPort)
: await findAvailablePort(transport === "http" ? 3001 : 3000);

this.url = `http://localhost:${port}`;
const port =
requestedPort !== undefined ? await findAvailablePort(requestedPort) : 0;

if (transport === "http") {
return this.startHttp(port);
const actualPort = await this.startHttp(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
} else {
return this.startSse(port);
const actualPort = await this.startSse(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
}
}

Expand All @@ -213,8 +216,10 @@ export class TestServerHttp {
// Create HTTP server
this.httpServer = createHttpServer(app);

// Create StreamableHTTP transport
this.transport = new StreamableHTTPServerTransport({});
// Create StreamableHTTP transport (stateful so it can handle multiple requests per session)
this.transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});

// Set up Express route to handle MCP requests
app.post("/mcp", async (req: Request, res: Response) => {
Expand Down Expand Up @@ -292,10 +297,12 @@ export class TestServerHttp {
// Connect transport to server
await this.mcpServer.connect(this.transport);

// Start listening
// Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => {
resolve(port);
const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
});
this.httpServer!.on("error", reject);
});
Expand Down Expand Up @@ -371,10 +378,12 @@ export class TestServerHttp {
// Note: SSE transport is created per request, so we don't store a single instance
this.transport = undefined;

// Start listening
// Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => {
resolve(port);
const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
});
this.httpServer!.on("error", reject);
});
Expand Down