fmt
This commit is contained in:
parent
615a6dead0
commit
a77dd1f8e1
9 changed files with 193 additions and 97 deletions
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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^¶m2=value2"]
|
||||
["/c", "start", '""', "https://example.com?param1=value1^¶m2=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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue