Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 1m17s
181 lines
5.9 KiB
TypeScript
181 lines
5.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import supertest from 'supertest';
|
|
import { createTestApp } from '../tests/utils/createTestApp';
|
|
import { createMockUserProfile } from '../tests/utils/mockFactories';
|
|
|
|
// Mock dependencies required by admin.routes.ts
|
|
vi.mock('../services/db/index.db', () => ({
|
|
adminRepo: {},
|
|
flyerRepo: {},
|
|
recipeRepo: {},
|
|
userRepo: {},
|
|
personalizationRepo: {},
|
|
notificationRepo: {},
|
|
}));
|
|
|
|
vi.mock('../services/backgroundJobService', () => ({
|
|
backgroundJobService: {
|
|
runDailyDealCheck: vi.fn(),
|
|
triggerAnalyticsReport: vi.fn(),
|
|
triggerWeeklyAnalyticsReport: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../services/queueService.server', () => ({
|
|
flyerQueue: { add: vi.fn(), getJob: vi.fn() },
|
|
emailQueue: { add: vi.fn(), getJob: vi.fn() },
|
|
analyticsQueue: { add: vi.fn(), getJob: vi.fn() },
|
|
cleanupQueue: { add: vi.fn(), getJob: vi.fn() },
|
|
weeklyAnalyticsQueue: { add: vi.fn(), getJob: vi.fn() },
|
|
}));
|
|
|
|
vi.mock('../services/geocodingService.server', () => ({
|
|
geocodingService: { clearGeocodeCache: vi.fn() },
|
|
}));
|
|
|
|
vi.mock('../services/cacheService.server', () => ({
|
|
cacheService: {
|
|
invalidateFlyers: vi.fn(),
|
|
invalidateBrands: vi.fn(),
|
|
invalidateStats: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../services/logger.server', async () => {
|
|
const { mockLogger, createMockLogger } = await import('../tests/utils/mockLogger');
|
|
return {
|
|
logger: mockLogger,
|
|
createScopedLogger: vi.fn(() => createMockLogger()),
|
|
};
|
|
});
|
|
|
|
vi.mock('@bull-board/api');
|
|
vi.mock('@bull-board/api/bullMQAdapter');
|
|
vi.mock('@bull-board/express', () => ({
|
|
ExpressAdapter: class {
|
|
setBasePath() {}
|
|
getRouter() {
|
|
return (req: any, res: any, next: any) => next();
|
|
}
|
|
},
|
|
}));
|
|
|
|
vi.mock('node:fs/promises');
|
|
vi.mock('../services/queues.server');
|
|
vi.mock('../services/workers.server');
|
|
vi.mock('../services/monitoringService.server');
|
|
vi.mock('../services/userService');
|
|
vi.mock('../services/brandService');
|
|
vi.mock('../services/receiptService.server');
|
|
vi.mock('../services/aiService.server');
|
|
vi.mock('../config/env', () => ({
|
|
config: {
|
|
database: { host: 'localhost', port: 5432, user: 'test', password: 'test', name: 'test' },
|
|
redis: { url: 'redis://localhost:6379' },
|
|
auth: { jwtSecret: 'test-secret' },
|
|
server: { port: 3000, host: 'localhost' },
|
|
},
|
|
isAiConfigured: vi.fn().mockReturnValue(false),
|
|
parseConfig: vi.fn(),
|
|
}));
|
|
|
|
// Mock Passport to allow admin access
|
|
// Note: admin.routes.ts imports from '../config/passport', so we mock that path
|
|
vi.mock('../config/passport', () => ({
|
|
default: {
|
|
authenticate: vi.fn(() => (req: any, res: any, next: any) => {
|
|
req.user = createMockUserProfile({ role: 'admin' });
|
|
next();
|
|
}),
|
|
},
|
|
isAdmin: (req: any, res: any, next: any) => next(),
|
|
}));
|
|
|
|
import adminRouter from './admin.routes';
|
|
import { cacheService } from '../services/cacheService.server';
|
|
import { mockLogger } from '../tests/utils/mockLogger';
|
|
|
|
describe('Admin Routes Rate Limiting', () => {
|
|
const app = createTestApp({ router: adminRouter, basePath: '/api/admin' });
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('Trigger Rate Limiting', () => {
|
|
it('should block requests to /trigger/daily-deal-check after exceeding limit', async () => {
|
|
const limit = 30; // Matches adminTriggerLimiter config
|
|
|
|
// Make requests up to the limit
|
|
for (let i = 0; i < limit; i++) {
|
|
await supertest(app)
|
|
.post('/api/admin/trigger/daily-deal-check')
|
|
.set('X-Test-Rate-Limit-Enable', 'true');
|
|
}
|
|
|
|
// The next request should be blocked
|
|
const response = await supertest(app)
|
|
.post('/api/admin/trigger/daily-deal-check')
|
|
.set('X-Test-Rate-Limit-Enable', 'true');
|
|
|
|
expect(response.status).toBe(429);
|
|
expect(response.text).toContain('Too many administrative triggers');
|
|
});
|
|
});
|
|
|
|
describe('Upload Rate Limiting', () => {
|
|
it('should block requests to /brands/:id/logo after exceeding limit', async () => {
|
|
const limit = 20; // Matches adminUploadLimiter config
|
|
const brandId = 1;
|
|
|
|
// Make requests up to the limit
|
|
// Note: We don't need to attach a file to test the rate limiter, as it runs before multer
|
|
for (let i = 0; i < limit; i++) {
|
|
await supertest(app)
|
|
.post(`/api/admin/brands/${brandId}/logo`)
|
|
.set('X-Test-Rate-Limit-Enable', 'true');
|
|
}
|
|
|
|
const response = await supertest(app)
|
|
.post(`/api/admin/brands/${brandId}/logo`)
|
|
.set('X-Test-Rate-Limit-Enable', 'true');
|
|
|
|
expect(response.status).toBe(429);
|
|
expect(response.text).toContain('Too many file uploads');
|
|
});
|
|
});
|
|
|
|
describe('POST /system/clear-cache', () => {
|
|
it('should return 200 and clear the cache successfully', async () => {
|
|
vi.mocked(cacheService.invalidateFlyers).mockResolvedValue(5);
|
|
vi.mocked(cacheService.invalidateBrands).mockResolvedValue(3);
|
|
vi.mocked(cacheService.invalidateStats).mockResolvedValue(2);
|
|
|
|
const response = await supertest(app).post('/api/admin/system/clear-cache');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.message).toContain('Successfully cleared the application cache');
|
|
expect(response.body.data.message).toContain('10 keys were removed');
|
|
expect(response.body.data.details).toEqual({
|
|
flyers: 5,
|
|
brands: 3,
|
|
stats: 2,
|
|
});
|
|
});
|
|
|
|
it('should return 500 if cache clear fails', async () => {
|
|
const cacheError = new Error('Redis connection failed');
|
|
vi.mocked(cacheService.invalidateFlyers).mockRejectedValue(cacheError);
|
|
|
|
const response = await supertest(app).post('/api/admin/system/clear-cache');
|
|
|
|
expect(response.status).toBe(500);
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
{ error: cacheError },
|
|
'[Admin] Failed to clear application cache.',
|
|
);
|
|
});
|
|
});
|
|
});
|