@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
text/typescript
/**
* 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();
});
});
});