mcp-think-tank
Version:
Structured thinking and knowledge management tool for Model Context Protocol
205 lines (204 loc) • 8.69 kB
JavaScript
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { contentCache } from '../../src/tools/ContentCache.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import * as http from 'http';
import * as https from 'https';
// Mock fs and http/https modules
vi.mock('fs/promises');
vi.mock('node:fs/promises');
vi.mock('http');
vi.mock('https');
describe('ContentCache', () => {
beforeEach(() => {
// Reset the cache
contentCache.clear();
// Setup mocks
vi.spyOn(fs, 'readFile').mockImplementation(async () => Buffer.from('test content'));
vi.spyOn(fs, 'stat').mockImplementation(async () => ({
mtime: new Date(Date.now() - 1000) // 1 second ago
}));
// Mock http/https
const mockResponse = {
statusCode: 200,
on: vi.fn().mockImplementation(function (event, handler) {
if (event === 'data') {
handler(Buffer.from('response content'));
}
if (event === 'end') {
handler();
}
return this;
})
};
// Use any to bypass the complex type signature issues
vi.spyOn(http, 'get').mockImplementation(((url, options, callback) => {
// Handle different argument patterns
if (typeof options === 'function') {
callback = options;
options = undefined;
}
if (callback) {
callback(mockResponse);
}
return {
on: (event, handler) => { }
};
}));
// Use any to bypass the complex type signature issues
vi.spyOn(https, 'get').mockImplementation(((url, options, callback) => {
// Handle different argument patterns
if (typeof options === 'function') {
callback = options;
options = undefined;
}
if (callback) {
callback(mockResponse);
}
return {
on: (event, handler) => { }
};
}));
});
afterEach(() => {
vi.resetAllMocks();
});
describe('readFile', () => {
it('should read file content and cache it', async () => {
// Setup
const filePath = path.join(os.tmpdir(), 'test-file.txt');
const fileContent = Buffer.from('test content');
// First call - should read from file system
const result1 = await contentCache.readFile(filePath);
// Second call - should read from cache
const result2 = await contentCache.readFile(filePath);
// Verify
expect(fs.readFile).toHaveBeenCalledTimes(1);
expect(fs.readFile).toHaveBeenCalledWith(filePath);
expect(result1).toEqual(fileContent);
expect(result2).toEqual(fileContent);
// Get stats
const stats = contentCache.getStats();
expect(stats.hits).toBe(1); // Second call was a hit
expect(stats.misses).toBe(1); // First call was a miss
});
it('should re-read file if it has been modified', async () => {
// Use a different file path for this test
const filePath = path.join(os.tmpdir(), 'modified-file.txt');
const oldContent = Buffer.from('old content');
const newContent = Buffer.from('new content');
// Reset everything
vi.resetAllMocks();
contentCache.clear();
// Spy on readFile
const readFileSpy = vi.spyOn(fs, 'readFile')
.mockImplementation((path) => {
// For the first call, return old content, for the second call, return new content
if (readFileSpy.mock.calls.length === 1) {
return Promise.resolve(oldContent);
}
else {
return Promise.resolve(newContent);
}
});
// First mock stat with old time
vi.spyOn(fs, 'stat')
.mockImplementationOnce(() => Promise.resolve({
mtime: new Date(Date.now() - 1000) // 1 second ago
}));
// First call - should read old content
const result1 = await contentCache.readFile(filePath);
// Now mock stat to return newer time
vi.spyOn(fs, 'stat')
.mockImplementationOnce(() => Promise.resolve({
mtime: new Date(Date.now() + 1000) // 1 second in the future (newer than cache timestamp)
}));
// Second call - should re-read file with new content
const result2 = await contentCache.readFile(filePath);
// Verify
expect(result1).toEqual(oldContent);
expect(result2).toEqual(newContent);
// Get stats
const stats = contentCache.getStats();
expect(stats.hits).toBe(0); // No hits because file was modified
expect(stats.misses).toBe(2); // Both calls were misses
});
});
describe('fetchUrl', () => {
it('should fetch URL content and cache it', async () => {
// Setup
const url = 'https://example.com/test';
const urlContent = Buffer.from('response content');
// First call - should fetch from URL
const result1 = await contentCache.fetchUrl(url);
// Second call - should read from cache
const result2 = await contentCache.fetchUrl(url);
// Verify
expect(https.get).toHaveBeenCalledTimes(1);
expect(result1).toEqual(urlContent);
expect(result2).toEqual(urlContent);
// Get stats
const stats = contentCache.getStats();
expect(stats.hits).toBe(1); // Second call was a hit
expect(stats.misses).toBe(1); // First call was a miss
});
});
describe('getJson', () => {
it('should parse JSON from a file and cache the parsed result', async () => {
// Setup
const filePath = path.join(os.tmpdir(), 'test.json');
const jsonContent = Buffer.from('{"key":"value"}');
const expectedParsed = { key: 'value' };
vi.spyOn(fs, 'readFile').mockResolvedValue(jsonContent);
// First call - should read from file system and parse
const result1 = await contentCache.getJson(filePath);
// Second call - should use cached parsed result
const result2 = await contentCache.getJson(filePath);
// Verify
expect(fs.readFile).toHaveBeenCalledTimes(1);
expect(result1).toEqual(expectedParsed);
expect(result2).toEqual(expectedParsed);
});
it('should parse JSON from a URL and cache the parsed result', async () => {
// Setup
const url = 'https://example.com/data.json';
const jsonContent = Buffer.from('{"key":"value"}');
const expectedParsed = { key: 'value' };
const mockResponse = {
statusCode: 200,
on: vi.fn().mockImplementation(function (event, handler) {
if (event === 'data') {
handler(jsonContent);
}
if (event === 'end') {
handler();
}
return this;
})
};
// Use any to bypass the complex type signature issues
vi.spyOn(https, 'get').mockImplementation(((url, options, callback) => {
// Handle different argument patterns
if (typeof options === 'function') {
callback = options;
options = undefined;
}
if (callback) {
callback(mockResponse);
}
return {
on: (event, handler) => { }
};
}));
// First call - should fetch from URL and parse
const result1 = await contentCache.getJson(url, true);
// Second call - should use cached parsed result
const result2 = await contentCache.getJson(url, true);
// Verify
expect(https.get).toHaveBeenCalledTimes(1);
expect(result1).toEqual(expectedParsed);
expect(result2).toEqual(expectedParsed);
});
});
});