fixed-cache-routes.test.ts•4.69 kB
/**
* Fixed Cache Routes Tests
*
* Tests the cache routes handlers with proper TypeScript typing and
* a more direct testing approach that avoids router mocking issues.
*/
import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { Request, Response } from 'express';
import * as cacheModule from '../../utils/cache';
// Mock the cache service
jest.mock('../../utils/cache', () => ({
cacheService: {
getStats: jest.fn(),
clear: jest.fn(),
deleteByPrefix: jest.fn()
}
}));
// We need to use dynamic import for cache handlers to avoid circular dependency issues
// and ensure our mocks are set up before importing the handlers
import { statsHandler, clearAllHandler, clearTypeHandler } from '../../routes/cache.handlers';
describe('Cache Routes', () => {
// Setup typed mocks
let req: Partial<Request>;
let res: {
status: jest.Mock;
json: jest.Mock;
};
let next: jest.Mock;
beforeEach(() => {
// Reset all mocks
jest.clearAllMocks();
// Setup request and response objects with proper types
req = {
params: {},
query: {}
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
next = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('GET /stats', () => {
test('returns cache statistics', () => {
// Arrange
const mockStats = {
hits: 10,
misses: 5,
keys: 15
};
(cacheModule.cacheService.getStats as jest.Mock).mockReturnValue(mockStats);
// Act
statsHandler(req as Request, res as any);
// Assert
expect(cacheModule.cacheService.getStats).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: expect.objectContaining({
hits: 10,
misses: 5,
keys: 15,
uptime: expect.any(Number)
})
});
});
test('handles errors', () => {
// Arrange
(cacheModule.cacheService.getStats as jest.Mock).mockImplementation(() => {
throw new Error('Cache error');
});
// Act
statsHandler(req as Request, res as any);
// Assert
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Cache error'
});
});
});
describe('DELETE /clear', () => {
test('clears all cache entries', () => {
// Arrange
(cacheModule.cacheService.clear as jest.Mock).mockReturnValue(10); // 10 entries cleared
// Act
clearAllHandler(req as Request, res as any);
// Assert
expect(cacheModule.cacheService.clear).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
message: 'Cache cleared successfully'
});
});
test('handles errors', () => {
// Arrange
(cacheModule.cacheService.clear as jest.Mock).mockImplementation(() => {
throw new Error('Clear cache error');
});
// Act
clearAllHandler(req as Request, res as any);
// Assert
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Clear cache error'
});
});
});
describe('DELETE /clear/:type', () => {
test('clears cache entries by prefix', () => {
// Arrange
// Make sure our mock returns 5 (entries cleared)
(cacheModule.cacheService.deleteByPrefix as jest.Mock).mockReturnValue(5);
req.params = { type: 'top' }; // 'top' is one of the allowed types
// Act
clearTypeHandler(req as Request, res as any);
// Assert
expect(cacheModule.cacheService.deleteByPrefix).toHaveBeenCalledWith('top_');
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
message: 'Cleared 5 cache entries for type: top'
});
});
test('handles invalid cache type', () => {
// Arrange
req.params = { type: 'invalid' }; // 'invalid' is not in the allowed list ['top', 'all', 'similar', 'uuid', 'sources']
// Act
clearTypeHandler(req as Request, res as any);
// Assert
expect(cacheModule.cacheService.deleteByPrefix).not.toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Invalid cache type: invalid'
});
});
});
});