554 lines
17 KiB
TypeScript
554 lines
17 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { HealthViewDataBuilder, HealthDTO } from './HealthViewDataBuilder';
|
|
|
|
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('100.00%');
|
|
expect(result.metrics.responseTime).toBe('5.00s');
|
|
expect(result.metrics.errorRate).toBe('0.00%');
|
|
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');
|
|
});
|
|
});
|
|
});
|