UNPKG

@sailboat-computer/event-bus

Version:

Standardized event bus for sailboat computer v3 with resilience features and offline capabilities

483 lines (396 loc) 15.3 kB
/** * Monitoring tests */ import { AlertManager, createAlertManager } from '../../src/monitoring'; import { AlertType, AlertSeverity, AlertSeverityMapping } from '../../src/types'; import { ExtendedEventBusMetrics } from '../../src/metrics'; describe('AlertManager', () => { let alertManager: AlertManager; beforeEach(() => { alertManager = createAlertManager(); }); describe('initialization', () => { it('should initialize with default values', () => { expect(alertManager).toBeDefined(); expect(alertManager.getActiveAlerts()).toEqual([]); expect(alertManager.getAlertHistory()).toEqual([]); }); it('should initialize with custom thresholds', () => { const customAlertManager = createAlertManager({ failedPublishesThreshold: 10, deadLetterQueueSizeThreshold: 20, reconnectionAttemptsThreshold: 5, processingTimeThreshold: 2000 }); expect(customAlertManager).toBeDefined(); }); }); describe('checkMetrics', () => { it('should create an alert when dead letter queue size exceeds threshold', () => { const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 0, processedEvents: 100, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 15, deadLetterQueueEvents: 15 }; alertManager.checkMetrics(metrics); const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(1); expect(activeAlerts[0].type).toBe(AlertType.DEAD_LETTER_QUEUE_SIZE); expect(activeAlerts[0].severity).toBe(AlertSeverityMapping.WARNING); expect(activeAlerts[0].message).toContain('Dead letter queue size'); expect(activeAlerts[0].details).toEqual({ deadLetterQueueSize: 15, deadLetterQueueEvents: 15 }); }); it('should create an alert when failed publishes exceed threshold', () => { const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, // 10% processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(metrics); const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(1); expect(activeAlerts[0].type).toBe(AlertType.FAILED_PUBLISHES); expect(activeAlerts[0].severity).toBe(AlertSeverityMapping.WARNING); expect(activeAlerts[0].message).toContain('Failed publishes rate'); }); it('should create an alert with ERROR severity when metrics exceed double the threshold', () => { const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 20, // 20% processedEvents: 80, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(metrics); const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(1); expect(activeAlerts[0].type).toBe(AlertType.FAILED_PUBLISHES); expect(activeAlerts[0].severity).toBe(AlertSeverityMapping.ERROR); }); it('should resolve an alert when metrics return to normal', () => { // First create an alert const highMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(highMetrics); // Verify alert was created expect(alertManager.getActiveAlerts().length).toBe(1); // Now check with normal metrics const normalMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 1, // 1% processedEvents: 99, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(normalMetrics); // Verify alert was resolved expect(alertManager.getActiveAlerts().length).toBe(0); // Check alert history const alertHistory = alertManager.getAlertHistory(); expect(alertHistory.length).toBe(1); expect(alertHistory[0].resolved).toBe(true); expect(alertHistory[0].resolvedAt).toBeDefined(); }); it('should create multiple alerts for different issues', () => { const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 5, bufferedEvents: 0, averageProcessingTime: 2000, deadLetterQueueSize: 15, deadLetterQueueEvents: 15 }; alertManager.checkMetrics(metrics); const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(4); // Failed publishes, reconnection attempts, processing time, dead letter queue }); }); describe('checkConnectionStatus', () => { it('should create an alert when disconnected', () => { alertManager.checkConnectionStatus(false); const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(1); expect(activeAlerts[0].type).toBe(AlertType.CONNECTION_STATUS); expect(activeAlerts[0].severity).toBe(AlertSeverityMapping.ERROR); expect(activeAlerts[0].message).toContain('disconnected'); }); it('should resolve the alert when reconnected', () => { // First disconnect alertManager.checkConnectionStatus(false); // Verify alert was created expect(alertManager.getActiveAlerts().length).toBe(1); // Now reconnect alertManager.checkConnectionStatus(true); // Verify alert was resolved expect(alertManager.getActiveAlerts().length).toBe(0); // Check alert history const alertHistory = alertManager.getAlertHistory(); expect(alertHistory.length).toBe(1); expect(alertHistory[0].resolved).toBe(true); }); }); describe('getActiveAlerts', () => { beforeEach(() => { // Create alerts by checking metrics const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 5, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 15, deadLetterQueueEvents: 15 }; alertManager.checkMetrics(metrics); }); it('should get all active alerts', () => { const activeAlerts = alertManager.getActiveAlerts(); expect(activeAlerts.length).toBe(3); // Failed publishes, reconnection attempts, dead letter queue }); it('should get active alerts by type', () => { const deadLetterQueueAlerts = alertManager.getActiveAlerts(AlertType.DEAD_LETTER_QUEUE_SIZE); expect(deadLetterQueueAlerts.length).toBe(1); expect(deadLetterQueueAlerts[0].type).toBe(AlertType.DEAD_LETTER_QUEUE_SIZE); }); it('should return empty array for non-existent alert type', () => { const processingTimeAlerts = alertManager.getActiveAlerts(AlertType.PROCESSING_TIME); expect(processingTimeAlerts.length).toBe(0); }); }); describe('getAlertHistory', () => { beforeEach(() => { // Create alerts by checking metrics const highMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 5, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 15, deadLetterQueueEvents: 15 }; alertManager.checkMetrics(highMetrics); // Resolve some alerts const normalMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 1, processedEvents: 99, activeSubscriptions: 5, reconnectionAttempts: 1, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 15, // Still high deadLetterQueueEvents: 15 }; alertManager.checkMetrics(normalMetrics); }); it('should get all alerts in history', () => { const alertHistory = alertManager.getAlertHistory(); expect(alertHistory.length).toBe(5); // 3 created + 2 resolved }); it('should get alert history with limit', () => { const limitedHistory = alertManager.getAlertHistory(2); expect(limitedHistory.length).toBe(2); }); it('should get alert history by type', () => { const failedPublishesAlerts = alertManager.getAlertHistory(undefined, AlertType.FAILED_PUBLISHES); expect(failedPublishesAlerts.length).toBe(2); // Created and resolved expect(failedPublishesAlerts[0].type).toBe(AlertType.FAILED_PUBLISHES); expect(failedPublishesAlerts[1].type).toBe(AlertType.FAILED_PUBLISHES); }); it('should get alert history by type with limit', () => { const limitedFailedPublishesAlerts = alertManager.getAlertHistory(1, AlertType.FAILED_PUBLISHES); expect(limitedFailedPublishesAlerts.length).toBe(1); }); }); describe('clearAlertHistory', () => { beforeEach(() => { // Create and resolve alerts const highMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(highMetrics); const normalMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 1, processedEvents: 99, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(normalMetrics); }); it('should clear alert history', () => { // Verify we have history expect(alertManager.getAlertHistory().length).toBe(2); alertManager.clearAlertHistory(); const alertHistory = alertManager.getAlertHistory(); expect(alertHistory.length).toBe(0); }); }); describe('registerHandler', () => { it('should register an alert handler', () => { const handler = jest.fn(); const handlerId = alertManager.registerHandler(handler); expect(handlerId).toBeDefined(); // Create an alert to trigger the handler const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(metrics); // Handler should be called expect(handler).toHaveBeenCalledTimes(1); }); it('should call handler for alert updates', () => { const handler = jest.fn(); alertManager.registerHandler(handler); // Create an alert const highMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(highMetrics); // Update the alert with higher severity const criticalMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 20, processedEvents: 80, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(criticalMetrics); // Handler should be called twice expect(handler).toHaveBeenCalledTimes(2); }); it('should call handler for alert resolution', () => { const handler = jest.fn(); alertManager.registerHandler(handler); // Create an alert const highMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(highMetrics); // Resolve the alert const normalMetrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 1, processedEvents: 99, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(normalMetrics); // Handler should be called twice expect(handler).toHaveBeenCalledTimes(2); }); }); describe('unregisterHandler', () => { it('should unregister an alert handler', () => { const handler = jest.fn(); const handlerId = alertManager.registerHandler(handler); // Unregister the handler alertManager.unregisterHandler(handlerId); // Create an alert const metrics: ExtendedEventBusMetrics = { publishedEvents: 100, failedPublishes: 10, processedEvents: 90, activeSubscriptions: 5, reconnectionAttempts: 0, bufferedEvents: 0, averageProcessingTime: 10, deadLetterQueueSize: 0, deadLetterQueueEvents: 0 }; alertManager.checkMetrics(metrics); // Handler should not be called expect(handler).not.toHaveBeenCalled(); }); it('should handle non-existent handler ID', () => { // Should not throw expect(() => { alertManager.unregisterHandler('non-existent-id'); }).not.toThrow(); }); }); });