Fix tests, permissions

This commit is contained in:
Minoru Mizutani 2025-04-29 11:30:51 +09:00
parent 8be8e20efa
commit baedc1f32a
No known key found for this signature in database
8 changed files with 74 additions and 36 deletions

View file

@ -14,13 +14,13 @@
] ]
}, },
"tasks": { "tasks": {
"proxy:start": "deno run --allow-net --allow-env --allow-read --allow-run --allow-sys --allow-ffi src/proxy.ts", "proxy:start": "deno run --allow-env --allow-read --allow-sys --allow-run=open --allow-write=\"$HOME/.mcp-auth/mcp-remote-deno-1.0.0\" --allow-net=0.0.0.0,localhost src/proxy.ts",
"proxy:watch": "deno run --watch --allow-net --allow-env --allow-read --allow-run --allow-sys --allow-ffi src/proxy.ts", "proxy:watch": "deno run --watch --allow-env --allow-read --allow-sys --allow-run=open --allow-write=\"$HOME/.mcp-auth/mcp-remote-deno-1.0.0\" --allow-net=0.0.0.0,localhost src/proxy.ts",
"client:start": "deno run --allow-net --allow-env --allow-read --allow-run --allow-sys --allow-ffi src/client.ts", "client:start": "deno run --allow-env --allow-read --allow-sys --allow-run=open --allow-write=\"$HOME/.mcp-auth/mcp-remote-deno-1.0.0\" --allow-net=0.0.0.0,localhost src/client.ts",
"client:watch": "deno run --watch --allow-net --allow-env --allow-read --allow-run --allow-sys --allow-ffi src/client.ts", "client:watch": "deno run --watch --allow-env --allow-read --allow-sys --allow-run=open --allow-write=\"$HOME/.mcp-auth/mcp-remote-deno-1.0.0\" --allow-net=0.0.0.0,localhost src/client.ts",
"test": "deno test --allow-net --allow-env --allow-read --allow-run --allow-sys tests/", "test": "deno test --allow-net --allow-env --allow-read tests/",
"test:watch": "deno test --watch --allow-net --allow-env --allow-read --allow-run --allow-sys tests/", "test:watch": "deno test --watch --allow-net --allow-env --allow-read tests/",
"test:coverage": "deno test --coverage=coverage --allow-net --allow-env --allow-read --allow-run --allow-sys tests/ && deno coverage coverage" "test:coverage": "deno test --coverage=coverage --allow-net --allow-env --allow-read tests/ && deno coverage coverage"
}, },
"imports": { "imports": {
"std/": "https://deno.land/std@0.224.0/", "std/": "https://deno.land/std@0.224.0/",

1
deno.lock generated
View file

@ -1394,6 +1394,7 @@
"https://deno.land/std@0.224.0/data_structures/comparators.ts": "17dfa68bf1550edadbfdd453a06f9819290bcb534c9945b5cec4b30242cff475", "https://deno.land/std@0.224.0/data_structures/comparators.ts": "17dfa68bf1550edadbfdd453a06f9819290bcb534c9945b5cec4b30242cff475",
"https://deno.land/std@0.224.0/data_structures/red_black_tree.ts": "2222be0c46842fc932e2c8589a66dced9e6eae180914807c5c55d1aa4c8c1b9b", "https://deno.land/std@0.224.0/data_structures/red_black_tree.ts": "2222be0c46842fc932e2c8589a66dced9e6eae180914807c5c55d1aa4c8c1b9b",
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
"https://deno.land/std@0.224.0/http/server.ts": "f9313804bf6467a1704f45f76cb6cd0a3396a3b31c316035e6a4c2035d1ea514",
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",

View file

@ -159,7 +159,7 @@ export async function coordinateAuth(
log("Authentication completed by another instance"); log("Authentication completed by another instance");
// Setup a dummy server - the client will use tokens directly from disk // Setup a dummy server - the client will use tokens directly from disk
const dummyServer = express().listen(0); // Listen on any available port const dummyServer = express().listen(0, "localhost"); // Listen on any available port on localhost only
// This shouldn't actually be called in normal operation, but provide it for API compatibility // This shouldn't actually be called in normal operation, but provide it for API compatibility
const dummyWaitForAuthCode = () => { const dummyWaitForAuthCode = () => {

View file

@ -48,12 +48,25 @@ export class DenoHttpServer {
/** /**
* Start the server listening on the specified port * Start the server listening on the specified port
* @param port The port to listen on * @param port The port to listen on
* @param hostname Optional hostname to bind to
* @param callback Optional callback when server is ready * @param callback Optional callback when server is ready
*/ */
listen(port: number, 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') {
callbackFn = hostname;
hostnameStr = undefined;
} else {
hostnameStr = hostname;
}
this.server = Deno.serve({ this.server = Deno.serve({
port, port,
onListen: callback ? () => callback() : undefined, hostname: hostnameStr,
onListen: callbackFn ? () => callbackFn() : undefined,
handler: async (request: Request) => { handler: async (request: Request) => {
const url = new URL(request.url); const url = new URL(request.url);
const path = url.pathname; const path = url.pathname;
@ -73,6 +86,7 @@ export class DenoHttpServer {
// This is needed to maintain API compatibility // This is needed to maintain API compatibility
return { return {
close: () => this.close(), close: () => this.close(),
address: () => ({ port }),
} as unknown as Server; } as unknown as Server;
} }

View file

@ -98,7 +98,7 @@ export function getConfigDir(): string {
const baseConfigDir = Deno.env.get("MCP_REMOTE_CONFIG_DIR") || const baseConfigDir = Deno.env.get("MCP_REMOTE_CONFIG_DIR") ||
path.join(os.homedir(), ".mcp-auth"); path.join(os.homedir(), ".mcp-auth");
// Add a version subdirectory so we don't need to worry about backwards/forwards compatibility yet // Add a version subdirectory so we don't need to worry about backwards/forwards compatibility yet
return path.join(baseConfigDir, `mcp-remote-${MCP_REMOTE_VERSION}`); return path.join(baseConfigDir, `mcp-remote-deno-${MCP_REMOTE_VERSION}`);
} }
/** /**

View file

@ -247,7 +247,7 @@ export function setupOAuthCallbackServerWithLongPoll(
options.events.emit("auth-code-received", code); options.events.emit("auth-code-received", code);
}); });
const server = app.listen(options.port, () => { const server = app.listen(options.port, "localhost", () => {
log(`OAuth callback server running at http://127.0.0.1:${options.port}`); log(`OAuth callback server running at http://127.0.0.1:${options.port}`);
}); });
@ -281,7 +281,7 @@ export function findAvailablePort(
server.on("error", (err: NodeJS.ErrnoException) => { server.on("error", (err: NodeJS.ErrnoException) => {
if (err.code === "EADDRINUSE") { if (err.code === "EADDRINUSE") {
// If preferred port is in use, get a random port // If preferred port is in use, get a random port
server.listen(0); server.listen({ port: 0, hostname: "localhost" });
} else { } else {
reject(err); reject(err);
} }
@ -295,7 +295,7 @@ export function findAvailablePort(
}); });
// Try preferred port first, or get a random port // Try preferred port first, or get a random port
server.listen(preferredPort || 0); server.listen({ port: preferredPort || 0, hostname: "localhost" });
}); });
} }

View file

@ -1,13 +1,19 @@
import { assertEquals } from "std/assert/mod.ts";
import { describe, it, beforeEach, afterEach } from "std/testing/bdd.ts"; import { describe, it, beforeEach, afterEach } from "std/testing/bdd.ts";
import { DenoHttpServer, ResponseBuilder } from "../src/lib/deno-http-server.ts"; import { DenoHttpServer, ResponseBuilder } from "../src/lib/deno-http-server.ts";
import { assertEquals } from "std/assert/mod.ts";
describe("DenoHttpServer", () => { describe("DenoHttpServer", () => {
let server: DenoHttpServer; let server: DenoHttpServer;
let serverInstance: ReturnType<DenoHttpServer["listen"]>; let serverInstance: ReturnType<DenoHttpServer["listen"]>;
// Define testPort at the describe level so it's available to all tests
const testPort = 9876;
beforeEach(() => { beforeEach(() => {
server = new DenoHttpServer(); server = new DenoHttpServer();
// Start the server on a random available port
serverInstance = server.listen(testPort, "localhost", () => {
// Server started
});
}); });
afterEach(async () => { afterEach(async () => {
@ -29,12 +35,6 @@ describe("DenoHttpServer", () => {
res.status(200).send("OK"); res.status(200).send("OK");
}); });
// Start the server on a random available port
const testPort = 9876;
serverInstance = server.listen(testPort, () => {
// Server started
});
// Send a request to the server // 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(); const text = await response.text();
@ -47,17 +47,43 @@ describe("DenoHttpServer", () => {
assertEquals(reqQuery.param, "value"); assertEquals(reqQuery.param, "value");
}); });
it("returns 404 for non-existent routes", async () => { it("should handle 404 for non-existent routes", async () => {
const server = new DenoHttpServer();
const localTestPort = 9877;
let serverInstance!: ReturnType<DenoHttpServer["listen"]>;
try {
// Start the server on a random available port // Start the server on a random available port
const testPort = 9877; serverInstance = server.listen(localTestPort, "localhost");
serverInstance = server.listen(testPort);
// Send a request to a non-existent route // Send a request to a non-existent route
const response = await fetch(`http://localhost:${testPort}/non-existent`); const response = await fetch(`http://localhost:${localTestPort}/non-existent`);
// Verify the response // Verify the response
assertEquals(response.status, 404); assertEquals(response.status, 404);
await response.body?.cancel(); // Consume the body to prevent leaks await response.body?.cancel(); // Consume the body to prevent leaks
} finally {
if (serverInstance) {
server.close();
}
}
});
it("should listen without callback", () => {
const server = new DenoHttpServer();
const localTestPort = 9878;
let serverInstance!: ReturnType<DenoHttpServer["listen"]>;
try {
serverInstance = server.listen(localTestPort, "localhost");
// Use the port property directly - we know our implementation returns an object with port
const port = (serverInstance as unknown as { port: number }).port;
assertEquals(port, localTestPort);
} finally {
if (serverInstance) {
server.close();
}
}
}); });
}); });

View file

@ -1,11 +1,8 @@
import { import {
assertEquals, assertEquals,
assertMatch,
assertStringIncludes, assertStringIncludes,
} from "std/assert/mod.ts"; } from "std/assert/mod.ts";
import { describe, it, beforeEach, afterEach } from "std/testing/bdd.ts"; import { describe, it, afterEach } from "std/testing/bdd.ts";
import { assertSpyCalls, spy, stub } from "std/testing/mock.ts";
import { FakeTime } from "std/testing/time.ts";
import { import {
getConfigDir, getConfigDir,
getConfigFilePath, getConfigFilePath,
@ -35,7 +32,7 @@ describe("mcp-auth-config", () => {
const configDir = getConfigDir(); const configDir = getConfigDir();
assertStringIncludes(configDir, customDir); assertStringIncludes(configDir, customDir);
assertStringIncludes(configDir, `mcp-remote-${MCP_REMOTE_VERSION}`); assertStringIncludes(configDir, `mcp-remote-deno-${MCP_REMOTE_VERSION}`);
}); });
it("falls back to ~/.mcp-auth if environment variable is not set", () => { it("falls back to ~/.mcp-auth if environment variable is not set", () => {
@ -48,7 +45,7 @@ describe("mcp-auth-config", () => {
const configDir = getConfigDir(); const configDir = getConfigDir();
assertStringIncludes(configDir, expectedBase); assertStringIncludes(configDir, expectedBase);
assertStringIncludes(configDir, `mcp-remote-${MCP_REMOTE_VERSION}`); assertStringIncludes(configDir, `mcp-remote-deno-${MCP_REMOTE_VERSION}`);
}); });
}); });