This commit is contained in:
Minoru Mizutani 2025-04-29 16:18:35 +09:00
parent 615a6dead0
commit a77dd1f8e1
No known key found for this signature in database
9 changed files with 193 additions and 97 deletions

View file

@ -13,14 +13,18 @@ interface RequestLike {
*/
export class DenoHttpServer {
private server: Deno.HttpServer | null = null;
private routes: Map<string, (req: Request) => Promise<Response> | Response> = new Map();
private routes: Map<string, (req: Request) => Promise<Response> | Response> =
new Map();
/**
* Register a GET route handler
* @param path The path to handle
* @param handler The handler function
*/
get(path: string, handler: (req: RequestLike, res: ResponseBuilder) => void): void {
get(
path: string,
handler: (req: RequestLike, res: ResponseBuilder) => void,
): void {
this.routes.set(path, async (request: Request) => {
const url = new URL(request.url);
const searchParams = url.searchParams;
@ -51,12 +55,16 @@ export class DenoHttpServer {
* @param hostname Optional hostname to bind to
* @param callback Optional callback when server is ready
*/
listen(port: number, hostname?: string | (() => void), callback?: () => void): Server {
listen(
port: number,
hostname?: string | (() => void),
callback?: () => void,
): Server {
// Handle optional hostname parameter
let hostnameStr: string | undefined;
let callbackFn = callback;
if (typeof hostname === 'function') {
if (typeof hostname === "function") {
callbackFn = hostname;
hostnameStr = undefined;
} else {
@ -79,7 +87,7 @@ export class DenoHttpServer {
// Route not found
return new Response("Not Found", { status: 404 });
}
},
});
// Return a dummy server object that mimics Node's HTTP server

View file

@ -94,7 +94,9 @@ export default async function open(
if (!success) {
const errorDetails = new TextDecoder().decode(stderr).trim();
const stdoutDetails = new TextDecoder().decode(stdout).trim();
let errorMessage = `Failed to open "${target}". Command "${command} ${args.join(" ")}" exited with code ${code}.`;
let errorMessage = `Failed to open "${target}". Command "${command} ${
args.join(" ")
}" exited with code ${code}.`;
if (errorDetails) errorMessage += `\nStderr: ${errorDetails}`;
if (stdoutDetails) errorMessage += `\nStdout: ${stdoutDetails}`; // Include stdout too
throw new Error(errorMessage);
@ -109,10 +111,11 @@ export default async function open(
// xdg-open often returns immediately. Add a small delay as a basic wait.
await delay(1000); // Wait 1 second (adjust as necessary)
}
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
throw new Error(`Failed to open "${target}": Command not found: ${command}`);
throw new Error(
`Failed to open "${target}": Command not found: ${command}`,
);
}
// Re-throw other errors or wrap them
throw error instanceof Error ? error : new Error(String(error));

View file

@ -20,17 +20,17 @@ export function log(str: string, ...rest: unknown[]) {
// Helper function to safely get a message identifier for logging
function getMessageIdentifier(message: unknown): string | number | undefined {
if (typeof message !== 'object' || message === null) return undefined;
if (typeof message !== "object" || message === null) return undefined;
// Check if it's a request or notification with a method
if ('method' in message && message.method !== undefined) {
if ("method" in message && message.method !== undefined) {
return String(message.method);
}
// Check if it's a response with an id
if ('id' in message && message.id !== undefined) {
if ("id" in message && message.id !== undefined) {
const id = message.id;
return typeof id === 'string' || typeof id === 'number' ? id : undefined;
return typeof id === "string" || typeof id === "number" ? id : undefined;
}
return undefined;
@ -295,8 +295,12 @@ export function findAvailablePort(
serverOrPort?: number | net.Server,
): Promise<number> {
// Handle if server parameter is a number (preferred port)
const preferredPort = typeof serverOrPort === "number" ? serverOrPort : undefined;
const serverToUse = typeof serverOrPort !== "number" ? (serverOrPort as net.Server) : net.createServer();
const preferredPort = typeof serverOrPort === "number"
? serverOrPort
: undefined;
const serverToUse = typeof serverOrPort !== "number"
? (serverOrPort as net.Server)
: net.createServer();
let hasResolved = false;
// Maximum number of port attempts before giving up

View file

@ -1,11 +1,9 @@
import {
assertEquals,
} from "std/assert/mod.ts";
import { describe, it, afterEach, beforeEach } from "std/testing/bdd.ts";
import { assertEquals } from "std/assert/mod.ts";
import { afterEach, beforeEach, describe, it } from "std/testing/bdd.ts";
import { stub } from "std/testing/mock.ts";
import {
isPidRunning,
isLockValid,
isPidRunning,
waitForAuthentication,
} from "../src/lib/coordination.ts";
@ -83,12 +81,16 @@ describe("coordination", () => {
beforeEach(() => {
// @ts-ignore - Required for testing
setTimeoutStub = stub(globalThis, "setTimeout", (callback: TimerHandler) => {
if (typeof callback === "function") {
callback();
}
return 1 as unknown as ReturnType<typeof setTimeout>;
});
setTimeoutStub = stub(
globalThis,
"setTimeout",
(callback: TimerHandler) => {
if (typeof callback === "function") {
callback();
}
return 1 as unknown as ReturnType<typeof setTimeout>;
},
);
});
afterEach(() => {
@ -100,8 +102,10 @@ describe("coordination", () => {
it("returns true when authentication completes", async () => {
// Mock fetch to simulate a successful authentication
// @ts-ignore - Required for testing
fetchStub = stub(globalThis, "fetch", () =>
Promise.resolve(new Response("Auth completed", { status: 200 }))
fetchStub = stub(
globalThis,
"fetch",
() => Promise.resolve(new Response("Auth completed", { status: 200 })),
);
const result = await waitForAuthentication(8000);
@ -111,8 +115,10 @@ describe("coordination", () => {
it("returns false for unexpected status", async () => {
// Mock fetch to simulate an error response
// @ts-ignore - Required for testing
fetchStub = stub(globalThis, "fetch", () =>
Promise.resolve(new Response("Error", { status: 500 }))
fetchStub = stub(
globalThis,
"fetch",
() => Promise.resolve(new Response("Error", { status: 500 })),
);
const result = await waitForAuthentication(8000);
@ -122,8 +128,10 @@ describe("coordination", () => {
it("returns false when fetch fails", async () => {
// Mock fetch to simulate a network error
// @ts-ignore - Required for testing
fetchStub = stub(globalThis, "fetch", () =>
Promise.reject(new Error("Network error"))
fetchStub = stub(
globalThis,
"fetch",
() => Promise.reject(new Error("Network error")),
);
const result = await waitForAuthentication(8000);

View file

@ -1,5 +1,8 @@
import { describe, it, beforeEach, afterEach } from "std/testing/bdd.ts";
import { DenoHttpServer, ResponseBuilder } from "../src/lib/deno-http-server.ts";
import { afterEach, beforeEach, describe, it } from "std/testing/bdd.ts";
import {
DenoHttpServer,
ResponseBuilder,
} from "../src/lib/deno-http-server.ts";
import { assertEquals } from "std/assert/mod.ts";
describe("DenoHttpServer", () => {
@ -36,7 +39,9 @@ describe("DenoHttpServer", () => {
});
// Send a request to the server
const response = await fetch(`http://localhost:${testPort}/test?param=value`);
const response = await fetch(
`http://localhost:${testPort}/test?param=value`,
);
const text = await response.text();
// Verify the response
@ -57,7 +62,9 @@ describe("DenoHttpServer", () => {
localServerInstance = server.listen(localTestPort, "localhost");
// Send a request to a non-existent route
const response = await fetch(`http://localhost:${localTestPort}/non-existent`);
const response = await fetch(
`http://localhost:${localTestPort}/non-existent`,
);
// Verify the response
assertEquals(response.status, 404);

View file

@ -1,6 +1,6 @@
import { assertEquals, assertRejects } from "std/assert/mod.ts";
import { describe, it, beforeEach, afterEach } from "std/testing/bdd.ts";
import { assertSpyCalls, spy, type Spy } from "std/testing/mock.ts";
import { afterEach, beforeEach, describe, it } from "std/testing/bdd.ts";
import { assertSpyCalls, type Spy, spy } from "std/testing/mock.ts";
import open from "../src/lib/deno-open.ts";
// Define the expected structure returned by the mocked Deno.Command
@ -38,7 +38,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -60,7 +62,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -76,14 +80,18 @@ describe("deno-open", () => {
it("throws error on command failure", async () => {
// Mock Deno.Command to return failure
const stderrOutput = new TextEncoder().encode("Command failed error message");
const stderrOutput = new TextEncoder().encode(
"Command failed error message",
);
const mockOutput = {
success: false,
code: 1,
stdout: new Uint8Array(),
stderr: stderrOutput,
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -105,7 +113,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -125,7 +135,7 @@ describe("deno-open", () => {
await assertRejects(
() => open(url, { os: "freebsd" }),
Error,
"Unsupported platform: freebsd"
"Unsupported platform: freebsd",
);
});
@ -137,7 +147,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -160,7 +172,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -183,7 +197,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -205,7 +221,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -216,7 +234,13 @@ describe("deno-open", () => {
// Verify the spy was called with correct arguments
assertSpyCalls(commandSpy, 1);
assertEquals(commandSpy.calls[0].args[0], "cmd");
assertEquals(commandSpy.calls[0].args[1]?.args, ["/c", "start", '""', "/wait", url]);
assertEquals(commandSpy.calls[0].args[1]?.args, [
"/c",
"start",
'""',
"/wait",
url,
]);
});
it("handles background option on macOS", async () => {
@ -227,7 +251,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -249,7 +275,9 @@ describe("deno-open", () => {
stdout: new Uint8Array(),
stderr: new Uint8Array(),
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -262,7 +290,7 @@ describe("deno-open", () => {
assertEquals(commandSpy.calls[0].args[0], "cmd");
assertEquals(
commandSpy.calls[0].args[1]?.args,
["/c", "start", '""', "https://example.com?param1=value1^&param2=value2"]
["/c", "start", '""', "https://example.com?param1=value1^&param2=value2"],
);
});
@ -279,7 +307,7 @@ describe("deno-open", () => {
await assertRejects(
() => open(url, { os: "darwin" }),
Error,
`Failed to open "${url}": Command not found: open`
`Failed to open "${url}": Command not found: open`,
);
assertSpyCalls(commandSpy, 1);
});
@ -294,7 +322,9 @@ describe("deno-open", () => {
stdout: stdoutOutput,
stderr: stderrOutput,
};
const mockCommandConstructor = () => ({ output: () => Promise.resolve(mockOutput) });
const mockCommandConstructor = () => ({
output: () => Promise.resolve(mockOutput),
});
commandSpy = spy(mockCommandConstructor);
(Deno.Command as unknown) = commandSpy;
@ -303,7 +333,7 @@ describe("deno-open", () => {
await assertRejects(
() => open(url, { os: "darwin" }),
Error,
`Failed to open "${url}". Command "open ${url}" exited with code 1.\nStderr: Error details\nStdout: Additional info`
`Failed to open "${url}". Command "open ${url}" exited with code 1.\nStderr: Error details\nStdout: Additional info`,
);
assertSpyCalls(commandSpy, 1);
});

View file

@ -1,5 +1,5 @@
import { assertEquals, assertExists } from "std/assert/mod.ts";
import { describe, it, beforeEach } from "std/testing/bdd.ts";
import { beforeEach, describe, it } from "std/testing/bdd.ts";
import { mcpProxy } from "../src/lib/utils.ts";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
@ -14,7 +14,7 @@ class MockTransport implements Transport {
public messages: unknown[] = [];
public errors: Error[] = [];
constructor(public name: string) { }
constructor(public name: string) {}
start(): Promise<void> {
// Mock start method - does nothing

View file

@ -1,20 +1,20 @@
import {
assertEquals,
assertStringIncludes,
assertRejects,
assertStringIncludes,
} from "std/assert/mod.ts";
import { describe, it, afterEach, beforeEach } from "std/testing/bdd.ts";
import { afterEach, beforeEach, describe, it } from "std/testing/bdd.ts";
import {
checkLockfile,
createLockfile,
deleteConfigFile,
deleteLockfile,
ensureConfigDir,
getConfigDir,
getConfigFilePath,
ensureConfigDir,
createLockfile,
checkLockfile,
deleteLockfile,
readJsonFile,
writeJsonFile,
deleteConfigFile,
readTextFile,
writeJsonFile,
writeTextFile,
} from "../src/lib/mcp-auth-config.ts";
import { MCP_REMOTE_VERSION } from "../src/lib/utils.ts";
@ -111,7 +111,7 @@ describe("mcp-auth-config", () => {
await assertRejects(
() => ensureConfigDir(),
Error,
"Test mkdir error"
"Test mkdir error",
);
});
});
@ -143,12 +143,14 @@ describe("mcp-auth-config", () => {
writeTextFileSpy = spy((_path: string | URL, _data: string) => {
return Promise.resolve();
}) as unknown as ReturnType<typeof spy<typeof Deno.writeTextFile>>;
Deno.writeTextFile = writeTextFileSpy as unknown as typeof Deno.writeTextFile;
Deno.writeTextFile =
writeTextFileSpy as unknown as typeof Deno.writeTextFile;
readTextFileSpy = spy((_path: string | URL) => {
return Promise.resolve(JSON.stringify(testData));
}) as unknown as ReturnType<typeof spy<typeof Deno.readTextFile>>;
Deno.readTextFile = readTextFileSpy as unknown as typeof Deno.readTextFile;
Deno.readTextFile =
readTextFileSpy as unknown as typeof Deno.readTextFile;
removeSpy = spy((_path: string | URL) => {
return Promise.resolve();
@ -171,7 +173,10 @@ describe("mcp-auth-config", () => {
assertSpyCalls(writeTextFileSpy, 1);
const expectedPath = getConfigFilePath(testHash, testFilename);
assertEquals(writeTextFileSpy.calls[0].args[0], expectedPath);
assertEquals(writeTextFileSpy.calls[0].args[1], JSON.stringify(testData, null, 2));
assertEquals(
writeTextFileSpy.calls[0].args[1],
JSON.stringify(testData, null, 2),
);
// Define a schema for parsing the JSON
const parseFunc = {
@ -236,7 +241,11 @@ describe("mcp-auth-config", () => {
// Verify readTextFile was called
assertSpyCalls(Deno.readTextFile as unknown as ReturnType<typeof spy>, 1);
assertEquals((Deno.readTextFile as unknown as ReturnType<typeof spy>).calls[0].args[0], expectedPath);
assertEquals(
(Deno.readTextFile as unknown as ReturnType<typeof spy>).calls[0]
.args[0],
expectedPath,
);
assertEquals(result, testText);
});
@ -250,7 +259,7 @@ describe("mcp-auth-config", () => {
await assertRejects(
() => readTextFile(testHash, testFilename, "Custom error message"),
Error,
"Custom error message"
"Custom error message",
);
});
});
@ -285,12 +294,14 @@ describe("mcp-auth-config", () => {
writeTextFileSpy = spy((_path: string | URL, _data: string) => {
return Promise.resolve();
}) as unknown as ReturnType<typeof spy<typeof Deno.writeTextFile>>;
Deno.writeTextFile = writeTextFileSpy as unknown as typeof Deno.writeTextFile;
Deno.writeTextFile =
writeTextFileSpy as unknown as typeof Deno.writeTextFile;
readTextFileSpy = spy((_path: string | URL) => {
return Promise.resolve(JSON.stringify(mockLockData));
}) as unknown as ReturnType<typeof spy<typeof Deno.readTextFile>>;
Deno.readTextFile = readTextFileSpy as unknown as typeof Deno.readTextFile;
Deno.readTextFile =
readTextFileSpy as unknown as typeof Deno.readTextFile;
removeSpy = spy((_path: string | URL) => {
return Promise.resolve();
@ -314,7 +325,9 @@ describe("mcp-auth-config", () => {
assertEquals(writeTextFileSpy.calls[0].args[0], expectedPath);
// Parse the written data and verify it contains our test values
const writtenData = JSON.parse(writeTextFileSpy.calls[0].args[1] as string);
const writtenData = JSON.parse(
writeTextFileSpy.calls[0].args[1] as string,
);
assertEquals(writtenData.pid, testPid);
assertEquals(writtenData.port, testPort);
assertEquals(typeof writtenData.timestamp, "number");

View file

@ -1,15 +1,15 @@
import { assertEquals, assertMatch, assertRejects } from "std/assert/mod.ts";
import {
AVAILABLE_PORT_START,
findAvailablePort,
getServerUrlHash,
log,
MCP_REMOTE_VERSION,
findAvailablePort,
setupSignalHandlers,
parseCommandLineArgs,
AVAILABLE_PORT_START,
setupSignalHandlers,
} from "../src/lib/utils.ts";
import { afterEach, beforeEach, describe, it } from "std/testing/bdd.ts";
import { assertSpyCalls, spy, type MethodSpy } from "std/testing/mock.ts";
import { assertSpyCalls, type MethodSpy, spy } from "std/testing/mock.ts";
import type net from "node:net";
import type process from "node:process";
@ -99,7 +99,11 @@ describe("utils", () => {
describe("findAvailablePort", () => {
let mockServer: MockServer;
let listenSpy: MethodSpy<MockServer, [port: number, callback: () => void], MockServer>;
let listenSpy: MethodSpy<
MockServer,
[port: number, callback: () => void],
MockServer
>;
let closeSpy: MethodSpy<MockServer, [callback: () => void], MockServer>;
beforeEach(() => {
@ -107,21 +111,21 @@ describe("utils", () => {
mockServer = {
listen: (_port: number, callback: () => void) => {
// Properly invoke callback
if (typeof callback === 'function') {
if (typeof callback === "function") {
callback();
}
return mockServer;
},
close: (callback: () => void) => {
// Properly invoke callback
if (typeof callback === 'function') {
if (typeof callback === "function") {
callback();
}
return mockServer;
},
on: (_event: string, _callback: () => void) => {
return mockServer;
}
},
};
// Create properly typed spies
@ -137,10 +141,12 @@ describe("utils", () => {
it("returns the first available port", async () => {
// Mock the server address method to return the expected port
(mockServer as unknown as { address(): { port: number } }).address = () => ({ port: AVAILABLE_PORT_START });
(mockServer as unknown as { address(): { port: number } }).address =
() => ({ port: AVAILABLE_PORT_START });
// Mock event handlers
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> = {};
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> =
{};
mockServer.on = (event: string, callback: () => void) => {
if (!eventHandlers[event]) {
eventHandlers[event] = [];
@ -169,11 +175,16 @@ describe("utils", () => {
it("increments port if initial port is unavailable", async () => {
// Mock the server address method to return the incremented port
(mockServer as unknown as { address(): { port: number } }).address = () => ({ port: AVAILABLE_PORT_START + 1 });
(mockServer as unknown as { address(): { port: number } }).address =
() => ({ port: AVAILABLE_PORT_START + 1 });
// Mock event handlers
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> = {};
mockServer.on = (event: string, callback: (...args: unknown[]) => void) => {
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> =
{};
mockServer.on = (
event: string,
callback: (...args: unknown[]) => void,
) => {
if (!eventHandlers[event]) {
eventHandlers[event] = [];
}
@ -188,7 +199,9 @@ describe("utils", () => {
if (callCount === 1) {
// First call should fail with EADDRINUSE
if (eventHandlers.error) {
const error = new Error("Address in use") as Error & { code?: string };
const error = new Error("Address in use") as Error & {
code?: string;
};
error.code = "EADDRINUSE";
for (const handler of eventHandlers.error) {
handler(error);
@ -215,8 +228,12 @@ describe("utils", () => {
it("throws after MAX_PORT_ATTEMPTS", async () => {
// Mock event handlers
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> = {};
mockServer.on = (event: string, callback: (...args: unknown[]) => void) => {
const eventHandlers: Record<string, Array<(...args: unknown[]) => void>> =
{};
mockServer.on = (
event: string,
callback: (...args: unknown[]) => void,
) => {
if (!eventHandlers[event]) {
eventHandlers[event] = [];
}
@ -227,7 +244,9 @@ describe("utils", () => {
// Always trigger error event with EADDRINUSE
mockServer.listen = (_port: number, _callback: () => void) => {
if (eventHandlers.error) {
const error = new Error("Address in use") as Error & { code?: string };
const error = new Error("Address in use") as Error & {
code?: string;
};
error.code = "EADDRINUSE";
for (const handler of eventHandlers.error) {
handler(error);
@ -239,7 +258,7 @@ describe("utils", () => {
await assertRejects(
() => findAvailablePort(mockServer as unknown as net.Server),
Error,
"Timeout finding available port"
"Timeout finding available port",
);
});
});
@ -255,7 +274,9 @@ describe("utils", () => {
originalFindAvailablePort = findAvailablePort;
// Mock findAvailablePort to avoid network access
(globalThis as unknown as GlobalWithFindPort).findAvailablePort = (port: number) => Promise.resolve(port);
(globalThis as unknown as GlobalWithFindPort).findAvailablePort = (
port: number,
) => Promise.resolve(port);
// Create a mock process object
globalThis.process = {
@ -269,7 +290,8 @@ describe("utils", () => {
afterEach(() => {
// Restore original process and findAvailablePort
globalThis.process = originalProcess;
(globalThis as unknown as GlobalWithFindPort).findAvailablePort = originalFindAvailablePort;
(globalThis as unknown as GlobalWithFindPort).findAvailablePort =
originalFindAvailablePort;
});
it("parses valid arguments", async () => {
@ -287,7 +309,8 @@ describe("utils", () => {
// Mock findAvailablePort specifically for this test
const mockFindPort = spy(() => Promise.resolve(3000));
// Replace the global findAvailablePort with our mock
(globalThis as unknown as GlobalWithFindPort).findAvailablePort = mockFindPort;
(globalThis as unknown as GlobalWithFindPort).findAvailablePort =
mockFindPort;
const args = ["https://example.com"];
const defaultPort = 3000;
@ -309,7 +332,7 @@ describe("utils", () => {
await parseCommandLineArgs(args, defaultPort, usage);
},
Error,
"Process exit called"
"Process exit called",
);
});
@ -322,7 +345,7 @@ describe("utils", () => {
async () => {
await parseCommandLineArgs(args, defaultPort, usage);
},
Error
Error,
);
});
@ -335,7 +358,7 @@ describe("utils", () => {
async () => {
await parseCommandLineArgs(args, defaultPort, usage);
},
Error
Error,
);
});
});