Enhance utility functions and tests: Introduce a helper function for logging message identifiers, update findAvailablePort to handle server instances, and improve test coverage for DenoHttpServer and file operations. Refactor tests to use spies for mocking Deno file system operations.
This commit is contained in:
parent
7299c69a27
commit
e41f8eacf0
4 changed files with 445 additions and 96 deletions
|
@ -74,31 +74,45 @@ describe("mcp-auth-config", () => {
|
|||
});
|
||||
|
||||
describe("ensureConfigDir", () => {
|
||||
it("creates directory when it doesn't exist", async () => {
|
||||
// Basic test without spies
|
||||
await ensureConfigDir();
|
||||
// If it doesn't throw, we're good
|
||||
assertEquals(true, true);
|
||||
let mkdirSpy: ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
let originalMkdir: typeof Deno.mkdir;
|
||||
|
||||
beforeEach(() => {
|
||||
originalMkdir = Deno.mkdir;
|
||||
// Mock mkdir to avoid actual file system operations
|
||||
mkdirSpy = spy((_path: string | URL, _options?: Deno.MkdirOptions) => {
|
||||
return Promise.resolve();
|
||||
}) as unknown as ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
Deno.mkdir = mkdirSpy as unknown as typeof Deno.mkdir;
|
||||
});
|
||||
});
|
||||
|
||||
describe("lockfile functions", () => {
|
||||
const testHash = "testhash123";
|
||||
const testPort = 12345;
|
||||
const testPid = 67890;
|
||||
afterEach(() => {
|
||||
// Restore original mkdir
|
||||
Deno.mkdir = originalMkdir;
|
||||
});
|
||||
|
||||
it("can create and check lockfiles", async () => {
|
||||
// Just test basic functionality without spies
|
||||
await createLockfile(testHash, testPid, testPort);
|
||||
it("creates directory when it doesn't exist", async () => {
|
||||
await ensureConfigDir();
|
||||
|
||||
const lockfile = await checkLockfile(testHash);
|
||||
// Check that mkdir was called with the correct dir
|
||||
assertSpyCalls(mkdirSpy, 1);
|
||||
const configDir = getConfigDir();
|
||||
assertEquals(mkdirSpy.calls[0].args[0], configDir);
|
||||
assertEquals(mkdirSpy.calls[0].args[1], { recursive: true });
|
||||
});
|
||||
|
||||
// Only check that data is correctly returned, not implementation details
|
||||
assertEquals(lockfile?.pid, testPid);
|
||||
assertEquals(lockfile?.port, testPort);
|
||||
it("handles errors when creating directories", async () => {
|
||||
// Instead of restoring, assign a new spy directly
|
||||
Deno.mkdir = spy((_path: string | URL, _options?: Deno.MkdirOptions) => {
|
||||
throw new Error("Test mkdir error");
|
||||
}) as unknown as typeof Deno.mkdir;
|
||||
|
||||
// Clean up
|
||||
await deleteLockfile(testHash);
|
||||
// Should throw when mkdir fails
|
||||
await assertRejects(
|
||||
() => ensureConfigDir(),
|
||||
Error,
|
||||
"Test mkdir error"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -107,20 +121,236 @@ describe("mcp-auth-config", () => {
|
|||
const testFilename = "test-fileops.json";
|
||||
const testData = { key: "value" };
|
||||
|
||||
// Mock Deno file operations
|
||||
let writeTextFileSpy: ReturnType<typeof spy<typeof Deno.writeTextFile>>;
|
||||
let readTextFileSpy: ReturnType<typeof spy<typeof Deno.readTextFile>>;
|
||||
let removeSpy: ReturnType<typeof spy<typeof Deno.remove>>;
|
||||
let mkdirSpy: ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
|
||||
// Store original Deno functions
|
||||
const originalWriteTextFile = Deno.writeTextFile;
|
||||
const originalReadTextFile = Deno.readTextFile;
|
||||
const originalRemove = Deno.remove;
|
||||
const originalMkdir = Deno.mkdir;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup mocks to avoid filesystem operations
|
||||
mkdirSpy = spy((_path: string | URL, _options?: Deno.MkdirOptions) => {
|
||||
return Promise.resolve();
|
||||
}) as unknown as ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
Deno.mkdir = mkdirSpy as unknown as typeof Deno.mkdir;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
removeSpy = spy((_path: string | URL) => {
|
||||
return Promise.resolve();
|
||||
}) as unknown as ReturnType<typeof spy<typeof Deno.remove>>;
|
||||
Deno.remove = removeSpy as unknown as typeof Deno.remove;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original functions
|
||||
Deno.mkdir = originalMkdir;
|
||||
Deno.writeTextFile = originalWriteTextFile;
|
||||
Deno.readTextFile = originalReadTextFile;
|
||||
Deno.remove = originalRemove;
|
||||
});
|
||||
|
||||
it("writes and reads JSON files", async () => {
|
||||
await writeJsonFile(testHash, testFilename, testData);
|
||||
|
||||
// Verify writeTextFile was called with correct path and data
|
||||
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));
|
||||
|
||||
// Define a schema for parsing the JSON
|
||||
const parseFunc = {
|
||||
parseAsync: (data: unknown) => {
|
||||
return Promise.resolve(data);
|
||||
return Promise.resolve(data as Record<string, string>);
|
||||
},
|
||||
};
|
||||
|
||||
// Read the file back
|
||||
const result = await readJsonFile(testHash, testFilename, parseFunc);
|
||||
assertEquals((result as any)?.key, testData.key);
|
||||
|
||||
// Clean up
|
||||
// Verify readTextFile was called
|
||||
assertSpyCalls(readTextFileSpy, 1);
|
||||
assertEquals(readTextFileSpy.calls[0].args[0], expectedPath);
|
||||
|
||||
// Check the parsed result
|
||||
assertEquals(result?.key, testData.key);
|
||||
|
||||
// Clean up (delete file)
|
||||
await deleteConfigFile(testHash, testFilename);
|
||||
|
||||
// Verify remove was called
|
||||
assertSpyCalls(removeSpy, 1);
|
||||
assertEquals(removeSpy.calls[0].args[0], expectedPath);
|
||||
});
|
||||
|
||||
it("handles file not found when reading JSON", async () => {
|
||||
// Create a new spy directly instead of restoring
|
||||
Deno.readTextFile = spy((_path: string | URL) => {
|
||||
throw new Deno.errors.NotFound();
|
||||
}) as unknown as typeof Deno.readTextFile;
|
||||
|
||||
const parseFunc = {
|
||||
parseAsync: (data: unknown) => {
|
||||
return Promise.resolve(data as Record<string, string>);
|
||||
},
|
||||
};
|
||||
|
||||
// Should return undefined when file not found
|
||||
const result = await readJsonFile(testHash, testFilename, parseFunc);
|
||||
assertEquals(result, undefined);
|
||||
});
|
||||
|
||||
it("writes and reads text files", async () => {
|
||||
const testText = "test text content";
|
||||
|
||||
await writeTextFile(testHash, testFilename, testText);
|
||||
|
||||
// Verify writeTextFile was called
|
||||
assertSpyCalls(writeTextFileSpy, 1);
|
||||
const expectedPath = getConfigFilePath(testHash, testFilename);
|
||||
assertEquals(writeTextFileSpy.calls[0].args[0], expectedPath);
|
||||
assertEquals(writeTextFileSpy.calls[0].args[1], testText);
|
||||
|
||||
// Assign a new spy directly instead of restoring
|
||||
Deno.readTextFile = spy((_path: string | URL) => {
|
||||
return Promise.resolve(testText);
|
||||
}) as unknown as typeof Deno.readTextFile;
|
||||
|
||||
// Read the text back
|
||||
const result = await readTextFile(testHash, testFilename);
|
||||
|
||||
// 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(result, testText);
|
||||
});
|
||||
|
||||
it("handles errors when reading text files", async () => {
|
||||
// Assign a new spy directly that throws an error
|
||||
Deno.readTextFile = spy((_path: string | URL) => {
|
||||
throw new Error("Read error");
|
||||
}) as unknown as typeof Deno.readTextFile;
|
||||
|
||||
// Should throw with custom error message
|
||||
await assertRejects(
|
||||
() => readTextFile(testHash, testFilename, "Custom error message"),
|
||||
Error,
|
||||
"Custom error message"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("lockfile functions", () => {
|
||||
const testHash = "testhash123";
|
||||
const testPort = 12345;
|
||||
const testPid = 67890;
|
||||
|
||||
let writeTextFileSpy: ReturnType<typeof spy<typeof Deno.writeTextFile>>;
|
||||
let readTextFileSpy: ReturnType<typeof spy<typeof Deno.readTextFile>>;
|
||||
let removeSpy: ReturnType<typeof spy<typeof Deno.remove>>;
|
||||
let mkdirSpy: ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
|
||||
const originalWriteTextFile = Deno.writeTextFile;
|
||||
const originalReadTextFile = Deno.readTextFile;
|
||||
const originalRemove = Deno.remove;
|
||||
const originalMkdir = Deno.mkdir;
|
||||
|
||||
const mockLockData = {
|
||||
pid: testPid,
|
||||
port: testPort,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSpy = spy((_path: string | URL, _options?: Deno.MkdirOptions) => {
|
||||
return Promise.resolve();
|
||||
}) as unknown as ReturnType<typeof spy<typeof Deno.mkdir>>;
|
||||
Deno.mkdir = mkdirSpy as unknown as typeof Deno.mkdir;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
removeSpy = spy((_path: string | URL) => {
|
||||
return Promise.resolve();
|
||||
}) as unknown as ReturnType<typeof spy<typeof Deno.remove>>;
|
||||
Deno.remove = removeSpy as unknown as typeof Deno.remove;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Deno.mkdir = originalMkdir;
|
||||
Deno.writeTextFile = originalWriteTextFile;
|
||||
Deno.readTextFile = originalReadTextFile;
|
||||
Deno.remove = originalRemove;
|
||||
});
|
||||
|
||||
it("creates lockfile with correct data", async () => {
|
||||
await createLockfile(testHash, testPid, testPort);
|
||||
|
||||
// Verify writeTextFile was called
|
||||
assertSpyCalls(writeTextFileSpy, 1);
|
||||
const expectedPath = getConfigFilePath(testHash, "lock.json");
|
||||
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);
|
||||
assertEquals(writtenData.pid, testPid);
|
||||
assertEquals(writtenData.port, testPort);
|
||||
assertEquals(typeof writtenData.timestamp, "number");
|
||||
});
|
||||
|
||||
it("can read lockfile data", async () => {
|
||||
const lockfile = await checkLockfile(testHash);
|
||||
|
||||
// Verify readTextFile was called
|
||||
assertSpyCalls(readTextFileSpy, 1);
|
||||
const expectedPath = getConfigFilePath(testHash, "lock.json");
|
||||
assertEquals(readTextFileSpy.calls[0].args[0], expectedPath);
|
||||
|
||||
// Verify the returned data
|
||||
assertEquals(lockfile?.pid, mockLockData.pid);
|
||||
assertEquals(lockfile?.port, mockLockData.port);
|
||||
assertEquals(lockfile?.timestamp, mockLockData.timestamp);
|
||||
});
|
||||
|
||||
it("returns null when lockfile doesn't exist", async () => {
|
||||
// Create a new spy that throws NotFound
|
||||
Deno.readTextFile = spy((_path: string | URL) => {
|
||||
throw new Deno.errors.NotFound();
|
||||
}) as unknown as typeof Deno.readTextFile;
|
||||
|
||||
const lockfile = await checkLockfile(testHash);
|
||||
assertEquals(lockfile, null);
|
||||
});
|
||||
|
||||
it("deletes lockfile", async () => {
|
||||
await deleteLockfile(testHash);
|
||||
|
||||
// Verify remove was called
|
||||
assertSpyCalls(removeSpy, 1);
|
||||
const expectedPath = getConfigFilePath(testHash, "lock.json");
|
||||
assertEquals(removeSpy.calls[0].args[0], expectedPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue