Add test coverage

This commit is contained in:
Minoru Mizutani 2025-04-29 13:31:22 +09:00
parent f3a59f6eae
commit 4907f8d895
No known key found for this signature in database
3 changed files with 174 additions and 16 deletions

19
deno.lock generated
View file

@ -4,8 +4,10 @@
"npm:@modelcontextprotocol/sdk@*": "1.10.2_express@5.1.0_zod@3.24.3",
"npm:@modelcontextprotocol/sdk@^1.9.0": "1.10.2_express@5.1.0_zod@3.24.3",
"npm:@types/express@5": "5.0.1",
"npm:@types/node@*": "22.12.0",
"npm:@types/node@^22.13.10": "22.15.2",
"npm:@types/react@^19.0.12": "19.1.2",
"npm:express@*": "4.21.2",
"npm:express@^4.21.2": "4.21.2",
"npm:open@^10.1.0": "10.1.1",
"npm:prettier@^3.5.3": "3.5.3",
@ -1407,21 +1409,6 @@
"workspace": {
"dependencies": [
"npm:@modelcontextprotocol/sdk@*"
],
"packageJson": {
"dependencies": [
"npm:@modelcontextprotocol/sdk@^1.9.0",
"npm:@types/express@5",
"npm:@types/node@^22.13.10",
"npm:@types/react@^19.0.12",
"npm:express@^4.21.2",
"npm:open@^10.1.0",
"npm:prettier@^3.5.3",
"npm:react@19",
"npm:tsup@^8.4.0",
"npm:tsx@^4.19.3",
"npm:typescript@^5.8.2"
]
}
]
}
}

134
tests/coordination_test.ts Normal file
View file

@ -0,0 +1,134 @@
import {
assertEquals,
assertRejects,
} from "std/assert/mod.ts";
import { describe, it, afterEach, beforeEach } from "std/testing/bdd.ts";
import { assertSpyCalls, spy, stub } from "std/testing/mock.ts";
import {
isPidRunning,
isLockValid,
waitForAuthentication,
} from "../src/lib/coordination.ts";
/**
* Basic tests for the coordination module
*/
describe("coordination", () => {
describe("isPidRunning", () => {
it("returns false when Deno.Command throws an error", async () => {
const originalCommand = Deno.Command;
try {
// @ts-ignore - Replace Deno.Command with a function that throws
Deno.Command = () => {
throw new Error("Test error");
};
const result = await isPidRunning(1234);
assertEquals(result, false);
} finally {
// Restore original Command
Deno.Command = originalCommand;
}
});
if (Deno.build.os === "linux" || Deno.build.os === "darwin") {
it("checks if a process is running on non-Windows by using kill command", async () => {
// Just verify it runs without error - actual functionality depends on the OS
const result = await isPidRunning(Deno.pid);
// Our own process should be running
assertEquals(typeof result, "boolean");
});
} else if (Deno.build.os === "windows") {
it("checks if a process is running on Windows by using tasklist command", async () => {
// Just verify it runs without error - actual functionality depends on the OS
const result = await isPidRunning(Deno.pid);
// Our own process should be running
assertEquals(typeof result, "boolean");
});
}
});
describe("isLockValid", () => {
const mockLockData = {
pid: 1234, // A likely non-existent PID
port: 8000,
timestamp: Date.now() - (31 * 60 * 1000), // Expired (31 minutes old)
};
it("returns false for expired lockfile", async () => {
const result = await isLockValid(mockLockData);
assertEquals(result, false);
});
it("returns false for non-existent process", async () => {
// Find a PID that's unlikely to exist
let testPid = 999999;
while (await isPidRunning(testPid)) {
testPid += 1000;
}
const validTimestampData = {
...mockLockData,
pid: testPid,
timestamp: Date.now(), // Not expired
};
const result = await isLockValid(validTimestampData);
assertEquals(result, false);
});
});
describe("waitForAuthentication", () => {
let fetchStub: ReturnType<typeof stub>;
let setTimeoutStub: ReturnType<typeof stub>;
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>;
});
});
afterEach(() => {
// Clean up
if (fetchStub) fetchStub.restore();
setTimeoutStub.restore();
});
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 }))
);
const result = await waitForAuthentication(8000);
assertEquals(result, true);
});
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 }))
);
const result = await waitForAuthentication(8000);
assertEquals(result, false);
});
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"))
);
const result = await waitForAuthentication(8000);
assertEquals(result, false);
});
});
});

37
tests/test-utils.ts Normal file
View file

@ -0,0 +1,37 @@
/**
* Test utilities for mcp-remote-deno tests
*/
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
/**
* A mock server for testing
*/
export class MockServer {
/**
* The HTTP server instance
*/
server: Partial<Server>;
/**
* A function that returns the auth code
*/
waitForAuthCode: () => Promise<string>;
/**
* Creates a new MockServer
* @param port The port the server is listening on
*/
constructor(port = 8000) {
this.server = {
address: () => ({
port,
address: "127.0.0.1",
family: "IPv4"
} as AddressInfo),
// Add other server properties as needed
};
this.waitForAuthCode = () => Promise.resolve("mock-auth-code");
}
}