UNPKG

mcp-think-tank

Version:

Structured thinking and knowledge management tool for Model Context Protocol

205 lines (204 loc) 8.69 kB
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); }); }); });