Skip to main content
Glama

News Aggregator API

cache.routes.test.ts8.32 kB
/** * Tests for Cache API endpoints */ import request from 'supertest'; import { Express } from 'express'; import { jest, describe, beforeAll, afterAll, beforeEach, it, expect } from '@jest/globals'; import { McpServer } from '../../server'; import { cacheService } from '../../utils/cache'; // Mock the cache service jest.mock('../../utils/cache', () => { return { cacheService: { get: jest.fn(), set: jest.fn().mockReturnValue(true), delete: jest.fn().mockReturnValue(true), deleteByPrefix: jest.fn().mockReturnValue(2), getStats: jest.fn().mockReturnValue({ hits: 10, misses: 5, keys: 2, ksize: 200, vsize: 400, uptime: 0.5 }), clear: jest.fn().mockReturnValue(true), shutdown: jest.fn() } }; }); describe('Cache API Endpoints', () => { let app: Express | null = null; let server: McpServer | null = null; const port = Math.floor(Math.random() * 10000) + 20000; // Different port range from news tests // Helper function to ensure app is available const getApp = () => { if (!app) { throw new Error('Express app is not initialized'); } return app; }; beforeAll(async () => { try { // Create server instance for testing with random port server = new McpServer(port); await server.start(); app = server.getApp(); // Ensure app is initialized if (!app) { throw new Error('Failed to initialize Express app'); } } catch (error) { console.error('Error starting cache API test server:', error); throw error; } }); afterAll(async () => { try { if (server) { // Call shutdown on the cache service to clean up timers cacheService.shutdown(); // Properly shut down the server await server.shutdown(); // Ensure all references are cleaned up server = null; app = null; // Force Jest to wait a bit to ensure everything is cleaned up await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { console.error('Error during test server shutdown:', error); } }); beforeEach(() => { // Reset all mocks before each test jest.clearAllMocks(); }); describe('GET /api/cache/stats', () => { it('should return cache statistics', async () => { // Update the mock implementation for getStats to include all required properties const mockStats = { hits: 0, misses: 0, keys: 0, ksize: 0, vsize: 0, uptime: 0.123 }; // Override the getStats mock for this test (cacheService.getStats as jest.Mock).mockReturnValue(mockStats); const response = await request(getApp()) .get('/api/cache/stats') .expect('Content-Type', /json/) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('hits'); expect(response.body.data).toHaveProperty('misses'); expect(response.body.data).toHaveProperty('keys'); expect(response.body.data).toHaveProperty('ksize'); expect(response.body.data).toHaveProperty('vsize'); expect(response.body.data).toHaveProperty('uptime'); }); it('should show updated stats after cache is used', async () => { // Create initial stats with 0 hits const initialStats = { hits: 0, misses: 0, keys: 1, ksize: 100, vsize: 200, uptime: 0.123 }; // Override getStats to return our controlled values (cacheService.getStats as jest.Mock) .mockReturnValueOnce(initialStats) // First call returns initial stats .mockReturnValueOnce({ // Second call returns updated stats with more hits ...initialStats, hits: 1 // Hits increased by 1 }); // First get stats const initialResponse = await request(getApp()) .get('/api/cache/stats') .expect(200); const initialHits = initialResponse.body.data.hits as number; expect(initialHits).toBe(0); // Get updated stats const updatedResponse = await request(getApp()) .get('/api/cache/stats') .expect(200); // Verify hits increased - comparing exact values expect(updatedResponse.body.data.hits).toBe(1); }); }); describe('DELETE /api/cache/clear', () => { it('should clear the entire cache', async () => { // Set up our mock for the stats before clearing const beforeStats = { hits: 0, misses: 0, keys: 2, ksize: 200, vsize: 400, uptime: 0.5 }; const afterStats = { hits: 0, misses: 0, keys: 0, // Keys are now 0 after clearing ksize: 0, vsize: 0, uptime: 0.6 }; // Mock to return different stats based on when it's called (cacheService.getStats as jest.Mock) .mockReturnValueOnce(beforeStats) .mockReturnValueOnce(afterStats); // Mock the clear method (cacheService.clear as jest.Mock).mockReturnValue(true); // Verify cache has items initially const beforeClear = await request(getApp()).get('/api/cache/stats'); expect(beforeClear.body.data.keys).toBe(2); // Clear cache const clearResponse = await request(getApp()) .delete('/api/cache/clear') .expect('Content-Type', /json/) .expect(200); expect(clearResponse.body.message).toBe('Cache cleared successfully'); // Verify cache is empty const afterClear = await request(getApp()).get('/api/cache/stats'); expect(afterClear.body.data.keys).toBe(0); // Verify that clear was called expect(cacheService.clear).toHaveBeenCalled(); }); }); describe('DELETE /api/cache/clear/:type', () => { it('should clear cache by type', async () => { // Mock different stats before and after clearing const beforeStats = { hits: 5, misses: 2, keys: 3, // 2 'top' keys and 1 'sources' key ksize: 300, vsize: 600, uptime: 0.7 }; const afterStats = { hits: 5, misses: 2, keys: 1, // Only the 'sources' key remains ksize: 100, vsize: 200, uptime: 0.8 }; // Setup mock to return different values for different calls (cacheService.getStats as jest.Mock) .mockReturnValueOnce(beforeStats) .mockReturnValueOnce(afterStats); // Mock deleteByPrefix to return number of deleted items (cacheService.deleteByPrefix as jest.Mock).mockReturnValue(2); // 2 keys deleted // Verify cache has items before clearing const initialStatsResponse = await request(getApp()).get('/api/cache/stats'); expect(initialStatsResponse.body.data.keys).toBe(3); // Clear only 'top' cache const response = await request(getApp()) .delete('/api/cache/clear/top') .expect('Content-Type', /json/) .expect(200); expect(response.body.message).toBe('Cleared 2 cache entries for type: top'); // Verify the correct prefix was used - note the underscore appended to the type expect(cacheService.deleteByPrefix).toHaveBeenCalledWith('top_'); // Verify 'top' related keys are gone but 'sources' remains const finalStatsResponse = await request(getApp()).get('/api/cache/stats'); expect(finalStatsResponse.body.data.keys).toBe(1); // Only the sources_test key should remain }); it('should return 400 for invalid cache type', async () => { // We don't need to mock deleteByPrefix here since the validation happens before it's called // The controller validates the type before attempting to delete anything const response = await request(getApp()) .delete('/api/cache/clear/invalid_type') .expect('Content-Type', /json/) .expect(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid cache type: invalid_type'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Malachi-devel/the-news-api-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server