1066 lines
36 KiB
TypeScript
1066 lines
36 KiB
TypeScript
/**
|
|
* View Data Layer Tests - Health Functionality
|
|
*
|
|
* This test file covers the view data layer for health functionality.
|
|
*
|
|
* The view data layer is responsible for:
|
|
* - DTO → UI model mapping
|
|
* - Formatting, sorting, and grouping
|
|
* - Derived fields and defaults
|
|
* - UI-specific semantics
|
|
*
|
|
* This layer isolates the UI from API churn by providing a stable interface
|
|
* between the API layer and the presentation layer.
|
|
*
|
|
* Test coverage includes:
|
|
* - Health status data transformation and aggregation
|
|
* - System metrics and performance view models
|
|
* - Health check data formatting and validation
|
|
* - Derived health fields (status indicators, alerts, etc.)
|
|
* - Default values and fallbacks for health views
|
|
* - Health-specific formatting (uptime, response times, error rates, etc.)
|
|
* - Data grouping and categorization for health components
|
|
* - Real-time health monitoring data updates
|
|
* - Health alert and notification view models
|
|
*/
|
|
|
|
import { HealthViewDataBuilder, HealthDTO } from '@/lib/builders/view-data/HealthViewDataBuilder';
|
|
import { HealthStatusDisplay } from '@/lib/display-objects/HealthStatusDisplay';
|
|
import { HealthMetricDisplay } from '@/lib/display-objects/HealthMetricDisplay';
|
|
import { HealthComponentDisplay } from '@/lib/display-objects/HealthComponentDisplay';
|
|
import { HealthAlertDisplay } from '@/lib/display-objects/HealthAlertDisplay';
|
|
|
|
describe('HealthViewDataBuilder', () => {
|
|
describe('happy paths', () => {
|
|
it('should transform HealthDTO to HealthViewData correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.95,
|
|
responseTime: 150,
|
|
errorRate: 0.05,
|
|
lastCheck: new Date().toISOString(),
|
|
checksPassed: 995,
|
|
checksFailed: 5,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
responseTime: 50,
|
|
errorRate: 0.01,
|
|
},
|
|
{
|
|
name: 'API',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
responseTime: 100,
|
|
errorRate: 0.02,
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'System Update',
|
|
message: 'System updated successfully',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe('ok');
|
|
expect(result.overallStatus.statusLabel).toBe('Healthy');
|
|
expect(result.overallStatus.statusColor).toBe('#10b981');
|
|
expect(result.overallStatus.statusIcon).toBe('✓');
|
|
expect(result.metrics.uptime).toBe('99.95%');
|
|
expect(result.metrics.responseTime).toBe('150ms');
|
|
expect(result.metrics.errorRate).toBe('0.05%');
|
|
expect(result.metrics.checksPassed).toBe(995);
|
|
expect(result.metrics.checksFailed).toBe(5);
|
|
expect(result.metrics.totalChecks).toBe(1000);
|
|
expect(result.metrics.successRate).toBe('99.5%');
|
|
expect(result.components).toHaveLength(2);
|
|
expect(result.components[0].name).toBe('Database');
|
|
expect(result.components[0].status).toBe('ok');
|
|
expect(result.components[0].statusLabel).toBe('Healthy');
|
|
expect(result.alerts).toHaveLength(1);
|
|
expect(result.alerts[0].id).toBe('alert-1');
|
|
expect(result.alerts[0].type).toBe('info');
|
|
expect(result.hasAlerts).toBe(true);
|
|
expect(result.hasDegradedComponents).toBe(false);
|
|
expect(result.hasErrorComponents).toBe(false);
|
|
});
|
|
|
|
it('should handle missing optional fields gracefully', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe('ok');
|
|
expect(result.metrics.uptime).toBe('N/A');
|
|
expect(result.metrics.responseTime).toBe('N/A');
|
|
expect(result.metrics.errorRate).toBe('N/A');
|
|
expect(result.metrics.checksPassed).toBe(0);
|
|
expect(result.metrics.checksFailed).toBe(0);
|
|
expect(result.metrics.totalChecks).toBe(0);
|
|
expect(result.metrics.successRate).toBe('N/A');
|
|
expect(result.components).toEqual([]);
|
|
expect(result.alerts).toEqual([]);
|
|
expect(result.hasAlerts).toBe(false);
|
|
expect(result.hasDegradedComponents).toBe(false);
|
|
expect(result.hasErrorComponents).toBe(false);
|
|
});
|
|
|
|
it('should handle degraded status correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'degraded',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 95.5,
|
|
responseTime: 500,
|
|
errorRate: 4.5,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'degraded',
|
|
lastCheck: new Date().toISOString(),
|
|
responseTime: 200,
|
|
errorRate: 2.0,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe('degraded');
|
|
expect(result.overallStatus.statusLabel).toBe('Degraded');
|
|
expect(result.overallStatus.statusColor).toBe('#f59e0b');
|
|
expect(result.overallStatus.statusIcon).toBe('⚠');
|
|
expect(result.metrics.uptime).toBe('95.50%');
|
|
expect(result.metrics.responseTime).toBe('500ms');
|
|
expect(result.metrics.errorRate).toBe('4.50%');
|
|
expect(result.hasDegradedComponents).toBe(true);
|
|
});
|
|
|
|
it('should handle error status correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'error',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 85.2,
|
|
responseTime: 2000,
|
|
errorRate: 14.8,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'error',
|
|
lastCheck: new Date().toISOString(),
|
|
responseTime: 1500,
|
|
errorRate: 10.0,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe('error');
|
|
expect(result.overallStatus.statusLabel).toBe('Error');
|
|
expect(result.overallStatus.statusColor).toBe('#ef4444');
|
|
expect(result.overallStatus.statusIcon).toBe('✕');
|
|
expect(result.metrics.uptime).toBe('85.20%');
|
|
expect(result.metrics.responseTime).toBe('2.00s');
|
|
expect(result.metrics.errorRate).toBe('14.80%');
|
|
expect(result.hasErrorComponents).toBe(true);
|
|
});
|
|
|
|
it('should handle multiple components with mixed statuses', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'degraded',
|
|
timestamp: new Date().toISOString(),
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'API',
|
|
status: 'degraded',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'Cache',
|
|
status: 'error',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.components).toHaveLength(3);
|
|
expect(result.hasDegradedComponents).toBe(true);
|
|
expect(result.hasErrorComponents).toBe(true);
|
|
expect(result.components[0].statusLabel).toBe('Healthy');
|
|
expect(result.components[1].statusLabel).toBe('Degraded');
|
|
expect(result.components[2].statusLabel).toBe('Error');
|
|
});
|
|
|
|
it('should handle multiple alerts with different severities', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'critical',
|
|
title: 'Critical Alert',
|
|
message: 'Critical issue detected',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
{
|
|
id: 'alert-2',
|
|
type: 'warning',
|
|
title: 'Warning Alert',
|
|
message: 'Warning message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
{
|
|
id: 'alert-3',
|
|
type: 'info',
|
|
title: 'Info Alert',
|
|
message: 'Informational message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.alerts).toHaveLength(3);
|
|
expect(result.hasAlerts).toBe(true);
|
|
expect(result.alerts[0].severity).toBe('Critical');
|
|
expect(result.alerts[0].severityColor).toBe('#ef4444');
|
|
expect(result.alerts[1].severity).toBe('Warning');
|
|
expect(result.alerts[1].severityColor).toBe('#f59e0b');
|
|
expect(result.alerts[2].severity).toBe('Info');
|
|
expect(result.alerts[2].severityColor).toBe('#3b82f6');
|
|
});
|
|
});
|
|
|
|
describe('data transformation', () => {
|
|
it('should preserve all DTO fields in the output', () => {
|
|
const now = new Date();
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: now.toISOString(),
|
|
uptime: 99.99,
|
|
responseTime: 100,
|
|
errorRate: 0.01,
|
|
lastCheck: now.toISOString(),
|
|
checksPassed: 9999,
|
|
checksFailed: 1,
|
|
components: [
|
|
{
|
|
name: 'Test Component',
|
|
status: 'ok',
|
|
lastCheck: now.toISOString(),
|
|
responseTime: 50,
|
|
errorRate: 0.005,
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'test-alert',
|
|
type: 'info',
|
|
title: 'Test Alert',
|
|
message: 'Test message',
|
|
timestamp: now.toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe(healthDTO.status);
|
|
expect(result.overallStatus.timestamp).toBe(healthDTO.timestamp);
|
|
expect(result.metrics.uptime).toBe('99.99%');
|
|
expect(result.metrics.responseTime).toBe('100ms');
|
|
expect(result.metrics.errorRate).toBe('0.01%');
|
|
expect(result.metrics.lastCheck).toBe(healthDTO.lastCheck);
|
|
expect(result.metrics.checksPassed).toBe(healthDTO.checksPassed);
|
|
expect(result.metrics.checksFailed).toBe(healthDTO.checksFailed);
|
|
expect(result.components[0].name).toBe(healthDTO.components![0].name);
|
|
expect(result.components[0].status).toBe(healthDTO.components![0].status);
|
|
expect(result.alerts[0].id).toBe(healthDTO.alerts![0].id);
|
|
expect(result.alerts[0].type).toBe(healthDTO.alerts![0].type);
|
|
});
|
|
|
|
it('should not modify the input DTO', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.95,
|
|
responseTime: 150,
|
|
errorRate: 0.05,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const originalDTO = JSON.parse(JSON.stringify(healthDTO));
|
|
HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(healthDTO).toEqual(originalDTO);
|
|
});
|
|
|
|
it('should transform all numeric fields to formatted strings', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.95,
|
|
responseTime: 150,
|
|
errorRate: 0.05,
|
|
checksPassed: 995,
|
|
checksFailed: 5,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(typeof result.metrics.uptime).toBe('string');
|
|
expect(typeof result.metrics.responseTime).toBe('string');
|
|
expect(typeof result.metrics.errorRate).toBe('string');
|
|
expect(typeof result.metrics.successRate).toBe('string');
|
|
});
|
|
|
|
it('should handle large numbers correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.999,
|
|
responseTime: 5000,
|
|
errorRate: 0.001,
|
|
checksPassed: 999999,
|
|
checksFailed: 1,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.uptime).toBe('99.999%');
|
|
expect(result.metrics.responseTime).toBe('5.00s');
|
|
expect(result.metrics.errorRate).toBe('0.001%');
|
|
expect(result.metrics.successRate).toBe('100.0%');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null/undefined numeric fields', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: null as any,
|
|
responseTime: undefined,
|
|
errorRate: null as any,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.uptime).toBe('N/A');
|
|
expect(result.metrics.responseTime).toBe('N/A');
|
|
expect(result.metrics.errorRate).toBe('N/A');
|
|
});
|
|
|
|
it('should handle negative numeric values', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: -1,
|
|
responseTime: -100,
|
|
errorRate: -0.5,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.uptime).toBe('N/A');
|
|
expect(result.metrics.responseTime).toBe('N/A');
|
|
expect(result.metrics.errorRate).toBe('N/A');
|
|
});
|
|
|
|
it('should handle empty components and alerts arrays', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
components: [],
|
|
alerts: [],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.components).toEqual([]);
|
|
expect(result.alerts).toEqual([]);
|
|
expect(result.hasAlerts).toBe(false);
|
|
expect(result.hasDegradedComponents).toBe(false);
|
|
expect(result.hasErrorComponents).toBe(false);
|
|
});
|
|
|
|
it('should handle component with missing optional fields', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
components: [
|
|
{
|
|
name: 'Test Component',
|
|
status: 'ok',
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.components[0].lastCheck).toBeDefined();
|
|
expect(result.components[0].formattedLastCheck).toBeDefined();
|
|
expect(result.components[0].responseTime).toBe('N/A');
|
|
expect(result.components[0].errorRate).toBe('N/A');
|
|
});
|
|
|
|
it('should handle alert with missing optional fields', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test Alert',
|
|
message: 'Test message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.alerts[0].id).toBe('alert-1');
|
|
expect(result.alerts[0].type).toBe('info');
|
|
expect(result.alerts[0].title).toBe('Test Alert');
|
|
expect(result.alerts[0].message).toBe('Test message');
|
|
expect(result.alerts[0].timestamp).toBeDefined();
|
|
expect(result.alerts[0].formattedTimestamp).toBeDefined();
|
|
expect(result.alerts[0].relativeTime).toBeDefined();
|
|
});
|
|
|
|
it('should handle unknown status', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'unknown',
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.overallStatus.status).toBe('unknown');
|
|
expect(result.overallStatus.statusLabel).toBe('Unknown');
|
|
expect(result.overallStatus.statusColor).toBe('#6b7280');
|
|
expect(result.overallStatus.statusIcon).toBe('?');
|
|
});
|
|
});
|
|
|
|
describe('derived fields', () => {
|
|
it('should correctly calculate hasAlerts', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test',
|
|
message: 'Test message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.hasAlerts).toBe(true);
|
|
});
|
|
|
|
it('should correctly calculate hasDegradedComponents', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
components: [
|
|
{
|
|
name: 'Component 1',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'Component 2',
|
|
status: 'degraded',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.hasDegradedComponents).toBe(true);
|
|
});
|
|
|
|
it('should correctly calculate hasErrorComponents', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
components: [
|
|
{
|
|
name: 'Component 1',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'Component 2',
|
|
status: 'error',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.hasErrorComponents).toBe(true);
|
|
});
|
|
|
|
it('should correctly calculate totalChecks', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
checksPassed: 100,
|
|
checksFailed: 20,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.totalChecks).toBe(120);
|
|
});
|
|
|
|
it('should correctly calculate successRate', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
checksPassed: 90,
|
|
checksFailed: 10,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.successRate).toBe('90.0%');
|
|
});
|
|
|
|
it('should handle zero checks correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
checksPassed: 0,
|
|
checksFailed: 0,
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.metrics.totalChecks).toBe(0);
|
|
expect(result.metrics.successRate).toBe('N/A');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('HealthStatusDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should format status labels correctly', () => {
|
|
expect(HealthStatusDisplay.formatStatusLabel('ok')).toBe('Healthy');
|
|
expect(HealthStatusDisplay.formatStatusLabel('degraded')).toBe('Degraded');
|
|
expect(HealthStatusDisplay.formatStatusLabel('error')).toBe('Error');
|
|
expect(HealthStatusDisplay.formatStatusLabel('unknown')).toBe('Unknown');
|
|
});
|
|
|
|
it('should format status colors correctly', () => {
|
|
expect(HealthStatusDisplay.formatStatusColor('ok')).toBe('#10b981');
|
|
expect(HealthStatusDisplay.formatStatusColor('degraded')).toBe('#f59e0b');
|
|
expect(HealthStatusDisplay.formatStatusColor('error')).toBe('#ef4444');
|
|
expect(HealthStatusDisplay.formatStatusColor('unknown')).toBe('#6b7280');
|
|
});
|
|
|
|
it('should format status icons correctly', () => {
|
|
expect(HealthStatusDisplay.formatStatusIcon('ok')).toBe('✓');
|
|
expect(HealthStatusDisplay.formatStatusIcon('degraded')).toBe('⚠');
|
|
expect(HealthStatusDisplay.formatStatusIcon('error')).toBe('✕');
|
|
expect(HealthStatusDisplay.formatStatusIcon('unknown')).toBe('?');
|
|
});
|
|
|
|
it('should format timestamp correctly', () => {
|
|
const timestamp = '2024-01-15T10:30:45.123Z';
|
|
const result = HealthStatusDisplay.formatTimestamp(timestamp);
|
|
expect(result).toMatch(/Jan 15, 2024, 10:30:45/);
|
|
});
|
|
|
|
it('should format relative time correctly', () => {
|
|
const now = new Date();
|
|
const oneMinuteAgo = new Date(now.getTime() - 60 * 1000);
|
|
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
|
|
expect(HealthStatusDisplay.formatRelativeTime(oneMinuteAgo.toISOString())).toBe('1m ago');
|
|
expect(HealthStatusDisplay.formatRelativeTime(oneHourAgo.toISOString())).toBe('1h ago');
|
|
expect(HealthStatusDisplay.formatRelativeTime(oneDayAgo.toISOString())).toBe('1d ago');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle unknown status', () => {
|
|
expect(HealthStatusDisplay.formatStatusLabel('unknown' as any)).toBe('Unknown');
|
|
expect(HealthStatusDisplay.formatStatusColor('unknown' as any)).toBe('#6b7280');
|
|
expect(HealthStatusDisplay.formatStatusIcon('unknown' as any)).toBe('?');
|
|
});
|
|
|
|
it('should handle just now relative time', () => {
|
|
const now = new Date();
|
|
const justNow = new Date(now.getTime() - 30 * 1000);
|
|
expect(HealthStatusDisplay.formatRelativeTime(justNow.toISOString())).toBe('Just now');
|
|
});
|
|
|
|
it('should handle weeks ago relative time', () => {
|
|
const now = new Date();
|
|
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
expect(HealthStatusDisplay.formatRelativeTime(twoWeeksAgo.toISOString())).toBe('2w ago');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('HealthMetricDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should format uptime correctly', () => {
|
|
expect(HealthMetricDisplay.formatUptime(99.95)).toBe('99.95%');
|
|
expect(HealthMetricDisplay.formatUptime(100)).toBe('100.00%');
|
|
expect(HealthMetricDisplay.formatUptime(0)).toBe('0.00%');
|
|
});
|
|
|
|
it('should format response time correctly', () => {
|
|
expect(HealthMetricDisplay.formatResponseTime(150)).toBe('150ms');
|
|
expect(HealthMetricDisplay.formatResponseTime(1500)).toBe('1.50s');
|
|
expect(HealthMetricDisplay.formatResponseTime(90000)).toBe('1.50m');
|
|
});
|
|
|
|
it('should format error rate correctly', () => {
|
|
expect(HealthMetricDisplay.formatErrorRate(0.05)).toBe('0.05%');
|
|
expect(HealthMetricDisplay.formatErrorRate(5.5)).toBe('5.50%');
|
|
expect(HealthMetricDisplay.formatErrorRate(100)).toBe('100.00%');
|
|
});
|
|
|
|
it('should format timestamp correctly', () => {
|
|
const timestamp = '2024-01-15T10:30:45.123Z';
|
|
const result = HealthMetricDisplay.formatTimestamp(timestamp);
|
|
expect(result).toMatch(/Jan 15, 2024, 10:30:45/);
|
|
});
|
|
|
|
it('should format success rate correctly', () => {
|
|
expect(HealthMetricDisplay.formatSuccessRate(90, 10)).toBe('90.0%');
|
|
expect(HealthMetricDisplay.formatSuccessRate(100, 0)).toBe('100.0%');
|
|
expect(HealthMetricDisplay.formatSuccessRate(0, 100)).toBe('0.0%');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle null/undefined values', () => {
|
|
expect(HealthMetricDisplay.formatUptime(null as any)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatUptime(undefined)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatResponseTime(null as any)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatResponseTime(undefined)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatErrorRate(null as any)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatErrorRate(undefined)).toBe('N/A');
|
|
});
|
|
|
|
it('should handle negative values', () => {
|
|
expect(HealthMetricDisplay.formatUptime(-1)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatResponseTime(-100)).toBe('N/A');
|
|
expect(HealthMetricDisplay.formatErrorRate(-0.5)).toBe('N/A');
|
|
});
|
|
|
|
it('should handle zero checks', () => {
|
|
expect(HealthMetricDisplay.formatSuccessRate(0, 0)).toBe('N/A');
|
|
});
|
|
|
|
it('should handle decimal response times', () => {
|
|
expect(HealthMetricDisplay.formatResponseTime(1234.56)).toBe('1.23s');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('HealthComponentDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should format component status labels correctly', () => {
|
|
expect(HealthComponentDisplay.formatStatusLabel('ok')).toBe('Healthy');
|
|
expect(HealthComponentDisplay.formatStatusLabel('degraded')).toBe('Degraded');
|
|
expect(HealthComponentDisplay.formatStatusLabel('error')).toBe('Error');
|
|
expect(HealthComponentDisplay.formatStatusLabel('unknown')).toBe('Unknown');
|
|
});
|
|
|
|
it('should format component status colors correctly', () => {
|
|
expect(HealthComponentDisplay.formatStatusColor('ok')).toBe('#10b981');
|
|
expect(HealthComponentDisplay.formatStatusColor('degraded')).toBe('#f59e0b');
|
|
expect(HealthComponentDisplay.formatStatusColor('error')).toBe('#ef4444');
|
|
expect(HealthComponentDisplay.formatStatusColor('unknown')).toBe('#6b7280');
|
|
});
|
|
|
|
it('should format component status icons correctly', () => {
|
|
expect(HealthComponentDisplay.formatStatusIcon('ok')).toBe('✓');
|
|
expect(HealthComponentDisplay.formatStatusIcon('degraded')).toBe('⚠');
|
|
expect(HealthComponentDisplay.formatStatusIcon('error')).toBe('✕');
|
|
expect(HealthComponentDisplay.formatStatusIcon('unknown')).toBe('?');
|
|
});
|
|
|
|
it('should format timestamp correctly', () => {
|
|
const timestamp = '2024-01-15T10:30:45.123Z';
|
|
const result = HealthComponentDisplay.formatTimestamp(timestamp);
|
|
expect(result).toMatch(/Jan 15, 2024, 10:30:45/);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle unknown status', () => {
|
|
expect(HealthComponentDisplay.formatStatusLabel('unknown' as any)).toBe('Unknown');
|
|
expect(HealthComponentDisplay.formatStatusColor('unknown' as any)).toBe('#6b7280');
|
|
expect(HealthComponentDisplay.formatStatusIcon('unknown' as any)).toBe('?');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('HealthAlertDisplay', () => {
|
|
describe('happy paths', () => {
|
|
it('should format alert severities correctly', () => {
|
|
expect(HealthAlertDisplay.formatSeverity('critical')).toBe('Critical');
|
|
expect(HealthAlertDisplay.formatSeverity('warning')).toBe('Warning');
|
|
expect(HealthAlertDisplay.formatSeverity('info')).toBe('Info');
|
|
});
|
|
|
|
it('should format alert severity colors correctly', () => {
|
|
expect(HealthAlertDisplay.formatSeverityColor('critical')).toBe('#ef4444');
|
|
expect(HealthAlertDisplay.formatSeverityColor('warning')).toBe('#f59e0b');
|
|
expect(HealthAlertDisplay.formatSeverityColor('info')).toBe('#3b82f6');
|
|
});
|
|
|
|
it('should format timestamp correctly', () => {
|
|
const timestamp = '2024-01-15T10:30:45.123Z';
|
|
const result = HealthAlertDisplay.formatTimestamp(timestamp);
|
|
expect(result).toMatch(/Jan 15, 2024, 10:30:45/);
|
|
});
|
|
|
|
it('should format relative time correctly', () => {
|
|
const now = new Date();
|
|
const oneMinuteAgo = new Date(now.getTime() - 60 * 1000);
|
|
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
|
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
|
|
expect(HealthAlertDisplay.formatRelativeTime(oneMinuteAgo.toISOString())).toBe('1m ago');
|
|
expect(HealthAlertDisplay.formatRelativeTime(oneHourAgo.toISOString())).toBe('1h ago');
|
|
expect(HealthAlertDisplay.formatRelativeTime(oneDayAgo.toISOString())).toBe('1d ago');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle unknown type', () => {
|
|
expect(HealthAlertDisplay.formatSeverity('unknown' as any)).toBe('Info');
|
|
expect(HealthAlertDisplay.formatSeverityColor('unknown' as any)).toBe('#3b82f6');
|
|
});
|
|
|
|
it('should handle just now relative time', () => {
|
|
const now = new Date();
|
|
const justNow = new Date(now.getTime() - 30 * 1000);
|
|
expect(HealthAlertDisplay.formatRelativeTime(justNow.toISOString())).toBe('Just now');
|
|
});
|
|
|
|
it('should handle weeks ago relative time', () => {
|
|
const now = new Date();
|
|
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
expect(HealthAlertDisplay.formatRelativeTime(twoWeeksAgo.toISOString())).toBe('2w ago');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Health View Data - Cross-Component Consistency', () => {
|
|
describe('common patterns', () => {
|
|
it('should all use consistent formatting for numeric values', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.95,
|
|
responseTime: 150,
|
|
errorRate: 0.05,
|
|
checksPassed: 995,
|
|
checksFailed: 5,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
responseTime: 50,
|
|
errorRate: 0.01,
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test',
|
|
message: 'Test message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
// All numeric values should be formatted as strings
|
|
expect(typeof result.metrics.uptime).toBe('string');
|
|
expect(typeof result.metrics.responseTime).toBe('string');
|
|
expect(typeof result.metrics.errorRate).toBe('string');
|
|
expect(typeof result.metrics.successRate).toBe('string');
|
|
expect(typeof result.components[0].responseTime).toBe('string');
|
|
expect(typeof result.components[0].errorRate).toBe('string');
|
|
});
|
|
|
|
it('should all handle missing data gracefully', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
// All fields should have safe defaults
|
|
expect(result.overallStatus.status).toBe('ok');
|
|
expect(result.metrics.uptime).toBe('N/A');
|
|
expect(result.metrics.responseTime).toBe('N/A');
|
|
expect(result.metrics.errorRate).toBe('N/A');
|
|
expect(result.metrics.successRate).toBe('N/A');
|
|
expect(result.components).toEqual([]);
|
|
expect(result.alerts).toEqual([]);
|
|
expect(result.hasAlerts).toBe(false);
|
|
expect(result.hasDegradedComponents).toBe(false);
|
|
expect(result.hasErrorComponents).toBe(false);
|
|
});
|
|
|
|
it('should all preserve ISO timestamps for serialization', () => {
|
|
const now = new Date();
|
|
const timestamp = now.toISOString();
|
|
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: timestamp,
|
|
lastCheck: timestamp,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: timestamp,
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test',
|
|
message: 'Test message',
|
|
timestamp: timestamp,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
// All timestamps should be preserved as ISO strings
|
|
expect(result.overallStatus.timestamp).toBe(timestamp);
|
|
expect(result.metrics.lastCheck).toBe(timestamp);
|
|
expect(result.components[0].lastCheck).toBe(timestamp);
|
|
expect(result.alerts[0].timestamp).toBe(timestamp);
|
|
});
|
|
|
|
it('should all handle boolean flags correctly', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
components: [
|
|
{
|
|
name: 'Component 1',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'Component 2',
|
|
status: 'degraded',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'Component 3',
|
|
status: 'error',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test',
|
|
message: 'Test message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
expect(result.hasAlerts).toBe(true);
|
|
expect(result.hasDegradedComponents).toBe(true);
|
|
expect(result.hasErrorComponents).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('data integrity', () => {
|
|
it('should maintain data consistency across transformations', () => {
|
|
const healthDTO: HealthDTO = {
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: 99.95,
|
|
responseTime: 150,
|
|
errorRate: 0.05,
|
|
checksPassed: 995,
|
|
checksFailed: 5,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
{
|
|
name: 'API',
|
|
status: 'degraded',
|
|
lastCheck: new Date().toISOString(),
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'info',
|
|
title: 'Test',
|
|
message: 'Test message',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
// Verify derived fields match their source data
|
|
expect(result.hasAlerts).toBe(healthDTO.alerts!.length > 0);
|
|
expect(result.hasDegradedComponents).toBe(
|
|
healthDTO.components!.some((c) => c.status === 'degraded')
|
|
);
|
|
expect(result.hasErrorComponents).toBe(
|
|
healthDTO.components!.some((c) => c.status === 'error')
|
|
);
|
|
expect(result.metrics.totalChecks).toBe(
|
|
(healthDTO.checksPassed || 0) + (healthDTO.checksFailed || 0)
|
|
);
|
|
});
|
|
|
|
it('should handle complex real-world scenarios', () => {
|
|
const now = new Date();
|
|
const timestamp = now.toISOString();
|
|
|
|
const healthDTO: HealthDTO = {
|
|
status: 'degraded',
|
|
timestamp: timestamp,
|
|
uptime: 98.5,
|
|
responseTime: 350,
|
|
errorRate: 1.5,
|
|
lastCheck: timestamp,
|
|
checksPassed: 985,
|
|
checksFailed: 15,
|
|
components: [
|
|
{
|
|
name: 'Database',
|
|
status: 'ok',
|
|
lastCheck: timestamp,
|
|
responseTime: 50,
|
|
errorRate: 0.01,
|
|
},
|
|
{
|
|
name: 'API',
|
|
status: 'degraded',
|
|
lastCheck: timestamp,
|
|
responseTime: 200,
|
|
errorRate: 2.0,
|
|
},
|
|
{
|
|
name: 'Cache',
|
|
status: 'error',
|
|
lastCheck: timestamp,
|
|
responseTime: 1000,
|
|
errorRate: 10.0,
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
id: 'alert-1',
|
|
type: 'critical',
|
|
title: 'Cache Failure',
|
|
message: 'Cache service is down',
|
|
timestamp: timestamp,
|
|
},
|
|
{
|
|
id: 'alert-2',
|
|
type: 'warning',
|
|
title: 'High Response Time',
|
|
message: 'API response time is elevated',
|
|
timestamp: timestamp,
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = HealthViewDataBuilder.build(healthDTO);
|
|
|
|
// Verify all transformations
|
|
expect(result.overallStatus.status).toBe('degraded');
|
|
expect(result.overallStatus.statusLabel).toBe('Degraded');
|
|
expect(result.metrics.uptime).toBe('98.50%');
|
|
expect(result.metrics.responseTime).toBe('350ms');
|
|
expect(result.metrics.errorRate).toBe('1.50%');
|
|
expect(result.metrics.checksPassed).toBe(985);
|
|
expect(result.metrics.checksFailed).toBe(15);
|
|
expect(result.metrics.totalChecks).toBe(1000);
|
|
expect(result.metrics.successRate).toBe('98.5%');
|
|
|
|
expect(result.components).toHaveLength(3);
|
|
expect(result.components[0].statusLabel).toBe('Healthy');
|
|
expect(result.components[1].statusLabel).toBe('Degraded');
|
|
expect(result.components[2].statusLabel).toBe('Error');
|
|
|
|
expect(result.alerts).toHaveLength(2);
|
|
expect(result.alerts[0].severity).toBe('Critical');
|
|
expect(result.alerts[0].severityColor).toBe('#ef4444');
|
|
expect(result.alerts[1].severity).toBe('Warning');
|
|
expect(result.alerts[1].severityColor).toBe('#f59e0b');
|
|
|
|
expect(result.hasAlerts).toBe(true);
|
|
expect(result.hasDegradedComponents).toBe(true);
|
|
expect(result.hasErrorComponents).toBe(true);
|
|
});
|
|
});
|
|
});
|