Skip to main content
Glama
incident-service.test.ts15.7 kB
import { IncidentService, createIncidentService } from '../../../src/services/incident-service.js'; import { MockDataAccessLayer } from '../../fixtures/mocks/mock-data-access.js'; import { MockCacheService } from '../../fixtures/mocks/mock-cache.js'; import { NotFoundError } from '../../../src/utils/errors.js'; describe('IncidentService', () => { let service: IncidentService; let mockDataAccess: MockDataAccessLayer; let mockCache: MockCacheService; beforeEach(() => { mockDataAccess = new MockDataAccessLayer(); mockCache = new MockCacheService(); service = new IncidentService(mockDataAccess as any, mockCache as any); }); afterEach(() => { jest.restoreAllMocks(); }); describe('getAllIncidents', () => { it('should retrieve all incidents successfully', async () => { const incidents = await service.getAllIncidents(); expect(incidents).toBeDefined(); expect(Array.isArray(incidents)).toBe(true); expect(incidents.length).toBe(2); expect(mockDataAccess.getCallCount('getIncidents')).toBe(1); }); it('should use cached data when available', async () => { // First call const incidents1 = await service.getAllIncidents(); expect(mockDataAccess.getCallCount('getIncidents')).toBe(1); // Second call - should use cache const incidents2 = await service.getAllIncidents(); expect(mockDataAccess.getCallCount('getIncidents')).toBe(1); // Still 1 expect(incidents2).toEqual(incidents1); }); it('should transform incidents with correct structure', async () => { const incidents = await service.getAllIncidents(); const incident = incidents[0]; expect(incident).toHaveProperty('id'); expect(incident).toHaveProperty('name'); expect(incident).toHaveProperty('status'); expect(incident).toHaveProperty('impact'); expect(incident).toHaveProperty('createdAt'); expect(incident).toHaveProperty('updatedAt'); expect(incident).toHaveProperty('shortlink'); expect(incident).toHaveProperty('updates'); expect(incident).toHaveProperty('affectedComponents'); }); it('should parse dates correctly', async () => { const incidents = await service.getAllIncidents(); const incident = incidents[0]; expect(incident.createdAt).toBeInstanceOf(Date); expect(incident.updatedAt).toBeInstanceOf(Date); }); it('should include incident updates', async () => { const incidents = await service.getAllIncidents(); const incident = incidents[0]; expect(incident.updates).toBeDefined(); expect(Array.isArray(incident.updates)).toBe(true); expect(incident.updates.length).toBeGreaterThan(0); }); }); describe('getUnresolvedIncidents', () => { it('should retrieve only unresolved incidents', async () => { const incidents = await service.getUnresolvedIncidents(); expect(incidents.length).toBe(1); expect(incidents[0].status).toBe('monitoring'); expect(incidents[0].id).toBe('incident-1'); }); it('should exclude resolved incidents', async () => { const incidents = await service.getUnresolvedIncidents(); const resolvedFound = incidents.some(i => i.status === 'resolved'); expect(resolvedFound).toBe(false); }); it('should use cached data when available', async () => { // First call await service.getUnresolvedIncidents(); expect(mockDataAccess.getCallCount('getUnresolvedIncidents')).toBe(1); // Second call - should use cache await service.getUnresolvedIncidents(); expect(mockDataAccess.getCallCount('getUnresolvedIncidents')).toBe(1); }); }); describe('getIncidentById', () => { it('should retrieve incident by ID successfully', async () => { const incident = await service.getIncidentById('incident-1'); expect(incident).toBeDefined(); expect(incident.id).toBe('incident-1'); expect(incident.name).toBe('XC Observability Metrics Delays'); }); it('should throw NotFoundError for non-existent ID', async () => { await expect(service.getIncidentById('non-existent-id')) .rejects .toThrow(NotFoundError); }); it('should throw NotFoundError with descriptive message', async () => { await expect(service.getIncidentById('missing')) .rejects .toThrow('Incident not found: missing'); }); }); describe('getIncidentsByStatus', () => { it('should filter incidents by monitoring status', async () => { const incidents = await service.getIncidentsByStatus('monitoring'); expect(incidents.length).toBe(1); expect(incidents[0].status).toBe('monitoring'); }); it('should filter incidents by resolved status', async () => { const incidents = await service.getIncidentsByStatus('resolved'); expect(incidents.length).toBe(1); expect(incidents[0].status).toBe('resolved'); }); it('should return empty array when no matches', async () => { const incidents = await service.getIncidentsByStatus('investigating'); expect(incidents).toEqual([]); }); }); describe('getIncidentsByImpact', () => { it('should filter incidents by minor impact', async () => { const incidents = await service.getIncidentsByImpact('minor'); expect(incidents.length).toBe(2); incidents.forEach(i => expect(i.impact).toBe('minor')); }); it('should return empty array for impacts with no incidents', async () => { const incidents = await service.getIncidentsByImpact('critical'); expect(incidents).toEqual([]); }); }); describe('getRecentIncidents', () => { it('should get incidents from last 7 days by default', async () => { const incidents = await service.getRecentIncidents(); expect(incidents).toBeDefined(); expect(Array.isArray(incidents)).toBe(true); // Both test incidents are recent expect(incidents.length).toBeGreaterThan(0); }); it('should filter by custom number of days', async () => { const incidents = await service.getRecentIncidents(30); expect(incidents).toBeDefined(); expect(incidents.length).toBeGreaterThan(0); }); it('should exclude old incidents', async () => { const incidents = await service.getRecentIncidents(0); // No incidents from today expect(incidents.length).toBe(0); }); }); describe('hasActiveIncidents', () => { it('should return true when unresolved incidents exist', async () => { const hasActive = await service.hasActiveIncidents(); expect(hasActive).toBe(true); }); it('should check unresolved incidents', async () => { await service.hasActiveIncidents(); expect(mockDataAccess.getCallCount('getUnresolvedIncidents')).toBeGreaterThan(0); }); }); describe('getAllMaintenances', () => { it('should retrieve all maintenance windows', async () => { const maintenances = await service.getAllMaintenances(); expect(maintenances).toBeDefined(); expect(Array.isArray(maintenances)).toBe(true); expect(maintenances.length).toBe(1); expect(mockDataAccess.getCallCount('getScheduledMaintenances')).toBe(1); }); it('should use cached data when available', async () => { // First call const maint1 = await service.getAllMaintenances(); expect(mockDataAccess.getCallCount('getScheduledMaintenances')).toBe(1); // Second call - should use cache const maint2 = await service.getAllMaintenances(); expect(mockDataAccess.getCallCount('getScheduledMaintenances')).toBe(1); expect(maint2).toEqual(maint1); }); it('should transform maintenances with correct structure', async () => { const maintenances = await service.getAllMaintenances(); const maintenance = maintenances[0]; expect(maintenance).toHaveProperty('id'); expect(maintenance).toHaveProperty('name'); expect(maintenance).toHaveProperty('status'); expect(maintenance).toHaveProperty('scheduledFor'); expect(maintenance).toHaveProperty('scheduledUntil'); expect(maintenance).toHaveProperty('updates'); }); it('should parse scheduling dates correctly', async () => { const maintenances = await service.getAllMaintenances(); const maintenance = maintenances[0]; expect(maintenance.scheduledFor).toBeInstanceOf(Date); expect(maintenance.scheduledUntil).toBeInstanceOf(Date); }); }); describe('getActiveMaintenances', () => { it('should retrieve only active maintenances', async () => { const maintenances = await service.getActiveMaintenances(); expect(maintenances).toBeDefined(); expect(Array.isArray(maintenances)).toBe(true); }); it('should use cached data when available', async () => { // First call await service.getActiveMaintenances(); expect(mockDataAccess.getCallCount('getActiveMaintenances')).toBe(1); // Second call - should use cache await service.getActiveMaintenances(); expect(mockDataAccess.getCallCount('getActiveMaintenances')).toBe(1); }); it('should filter by in_progress status', async () => { const maintenances = await service.getActiveMaintenances(); // No in_progress in fixtures expect(maintenances.length).toBe(0); }); }); describe('getUpcomingMaintenances', () => { it('should retrieve only scheduled maintenances', async () => { const maintenances = await service.getUpcomingMaintenances(); expect(maintenances).toBeDefined(); expect(Array.isArray(maintenances)).toBe(true); expect(maintenances.length).toBe(1); }); it('should use cached data when available', async () => { // First call await service.getUpcomingMaintenances(); expect(mockDataAccess.getCallCount('getUpcomingMaintenances')).toBe(1); // Second call - should use cache await service.getUpcomingMaintenances(); expect(mockDataAccess.getCallCount('getUpcomingMaintenances')).toBe(1); }); it('should filter by scheduled status', async () => { const maintenances = await service.getUpcomingMaintenances(); maintenances.forEach(m => expect(m.status).toBe('scheduled')); }); }); describe('getMaintenanceById', () => { it('should retrieve maintenance by ID successfully', async () => { const maintenance = await service.getMaintenanceById('maintenance-1'); expect(maintenance).toBeDefined(); expect(maintenance.id).toBe('maintenance-1'); expect(maintenance.name).toBe('Database Infrastructure Upgrade'); }); it('should throw NotFoundError for non-existent ID', async () => { await expect(service.getMaintenanceById('non-existent-id')) .rejects .toThrow(NotFoundError); }); it('should throw NotFoundError with descriptive message', async () => { await expect(service.getMaintenanceById('missing')) .rejects .toThrow('Maintenance not found: missing'); }); }); describe('hasActiveMaintenances', () => { it('should return false when no active maintenances', async () => { const hasActive = await service.hasActiveMaintenances(); expect(hasActive).toBe(false); }); it('should check active maintenances', async () => { await service.hasActiveMaintenances(); expect(mockDataAccess.getCallCount('getActiveMaintenances')).toBeGreaterThan(0); }); }); describe('invalidateIncidentsCache', () => { it('should invalidate incidents cache using pattern', async () => { // Populate caches await service.getAllIncidents(); await service.getUnresolvedIncidents(); expect(mockCache.has('all-incidents')).toBe(true); expect(mockCache.has('unresolved-incidents')).toBe(true); // Invalidate service.invalidateIncidentsCache(); expect(mockCache.has('all-incidents')).toBe(false); expect(mockCache.has('unresolved-incidents')).toBe(false); }); }); describe('invalidateMaintenancesCache', () => { it('should invalidate maintenances cache using pattern', async () => { // Populate caches await service.getAllMaintenances(); await service.getActiveMaintenances(); expect(mockCache.has('all-maintenances')).toBe(true); expect(mockCache.has('active-maintenances')).toBe(true); // Invalidate service.invalidateMaintenancesCache(); expect(mockCache.has('all-maintenances')).toBe(false); expect(mockCache.has('active-maintenances')).toBe(false); }); }); describe('invalidateAllCaches', () => { it('should invalidate both incidents and maintenances caches', async () => { // Populate all caches await service.getAllIncidents(); await service.getAllMaintenances(); expect(mockCache.has('all-incidents')).toBe(true); expect(mockCache.has('all-maintenances')).toBe(true); // Invalidate all service.invalidateAllCaches(); expect(mockCache.has('all-incidents')).toBe(false); expect(mockCache.has('all-maintenances')).toBe(false); }); }); describe('edge cases', () => { it('should handle incidents without affected components', async () => { const incidents = await service.getAllIncidents(); // Check that affectedComponents is always an array incidents.forEach(i => { expect(Array.isArray(i.affectedComponents)).toBe(true); }); }); it('should handle incidents without resolved date', async () => { const incidents = await service.getAllIncidents(); const unresolved = incidents.find(i => i.status !== 'resolved'); expect(unresolved?.resolvedAt).toBeUndefined(); }); it('should handle concurrent requests efficiently', async () => { const promises = [ service.getAllIncidents(), service.getAllIncidents(), service.getAllIncidents() ]; const results = await Promise.all(promises); // All should return same data expect(results[0]).toEqual(results[1]); expect(results[1]).toEqual(results[2]); }); it('should handle already transformed incidents (from scraper)', async () => { // Test the scraper fallback path where incidents are pre-transformed const scraperIncidents = [ { id: 'scraper-inc-1', name: 'Scraper Incident', status: 'investigating' as const, impact: 'major' as const, createdAt: new Date('2025-10-08T10:00:00.000Z'), updatedAt: new Date('2025-10-08T11:00:00.000Z'), shortlink: 'https://status.com/incidents/scraper-inc-1', updates: [], affectedComponents: [] } ]; jest.spyOn(mockDataAccess, 'getIncidents').mockResolvedValue(scraperIncidents as any); mockCache.clear(); const incidents = await service.getAllIncidents(); expect(incidents).toEqual(scraperIncidents); expect(incidents[0].id).toBe('scraper-inc-1'); }); }); describe('createIncidentService factory', () => { it('should create IncidentService instance', () => { const instance = createIncidentService(mockDataAccess as any, mockCache as any); expect(instance).toBeInstanceOf(IncidentService); }); it('should create functional service instance', async () => { const instance = createIncidentService(mockDataAccess as any, mockCache as any); const incidents = await instance.getAllIncidents(); expect(incidents).toBeDefined(); expect(Array.isArray(incidents)).toBe(true); }); }); });

Latest Blog Posts

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/robinmordasiewicz/f5cloudstatus-mcp'

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