705 lines
22 KiB
TypeScript
705 lines
22 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { HealthRouteService } from '@/lib/services/health/HealthRouteService';
|
|
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
|
import { isProductionEnvironment } from '@/lib/config/env';
|
|
|
|
// Mock the dependencies
|
|
vi.mock('@/lib/config/apiBaseUrl', () => ({
|
|
getWebsiteApiBaseUrl: () => 'https://api.example.com',
|
|
}));
|
|
|
|
vi.mock('@/lib/config/env', () => ({
|
|
isProductionEnvironment: () => false,
|
|
}));
|
|
|
|
describe('HealthRouteService', () => {
|
|
let service: HealthRouteService;
|
|
let originalFetch: typeof global.fetch;
|
|
let mockFetch: any;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
service = new HealthRouteService();
|
|
originalFetch = global.fetch;
|
|
mockFetch = vi.fn();
|
|
global.fetch = mockFetch as any;
|
|
});
|
|
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
});
|
|
|
|
describe('happy paths', () => {
|
|
it('should return ok status with timestamp when all dependencies are healthy', async () => {
|
|
// Mock successful responses for all dependencies
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
// Mock database and external service to be healthy
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('healthy');
|
|
expect(health.timestamp).toBeDefined();
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
expect(health.dependencies.externalService.status).toBe('healthy');
|
|
});
|
|
|
|
it('should return degraded status when external service is slow', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1500,
|
|
error: 'High latency',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
});
|
|
|
|
describe('failure modes', () => {
|
|
it('should handle API server errors gracefully', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 500,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.error).toContain('500');
|
|
});
|
|
|
|
it('should handle network errors gracefully', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('Network connection failed'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.error).toContain('Network connection failed');
|
|
});
|
|
|
|
it('should handle database connection failures', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'unhealthy',
|
|
latency: 100,
|
|
error: 'Connection timeout',
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.database.status).toBe('unhealthy');
|
|
});
|
|
|
|
it('should handle external service failures gracefully', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 200,
|
|
error: 'Service unavailable',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
|
|
it('should handle all dependencies failing', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('API unavailable'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'unhealthy',
|
|
latency: 100,
|
|
error: 'DB connection failed',
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 150,
|
|
error: 'External service timeout',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('unhealthy');
|
|
expect(health.dependencies.database.status).toBe('unhealthy');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
});
|
|
|
|
describe('retries', () => {
|
|
it('should retry on transient API failures', async () => {
|
|
// First call fails, second succeeds
|
|
mockFetch
|
|
.mockRejectedValueOnce(new Error('Network timeout'))
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('healthy');
|
|
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should retry database health check on transient failures', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
// Mock database to fail first, then succeed
|
|
const checkDatabaseHealthSpy = vi.spyOn(service as any, 'checkDatabaseHealth');
|
|
checkDatabaseHealthSpy
|
|
.mockRejectedValueOnce(new Error('Connection timeout'))
|
|
.mockResolvedValueOnce({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
});
|
|
|
|
it('should exhaust retries and return unhealthy after max attempts', async () => {
|
|
// Mock all retries to fail
|
|
mockFetch.mockRejectedValue(new Error('Persistent network error'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(mockFetch).toHaveBeenCalledTimes(3); // Max retries
|
|
});
|
|
|
|
it('should handle mixed retry scenarios', async () => {
|
|
// API succeeds on second attempt
|
|
mockFetch
|
|
.mockRejectedValueOnce(new Error('Timeout'))
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
// Database fails all attempts
|
|
const checkDatabaseHealthSpy = vi.spyOn(service as any, 'checkDatabaseHealth');
|
|
checkDatabaseHealthSpy.mockRejectedValue(new Error('DB connection failed'));
|
|
|
|
// External service succeeds
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy'); // Database failure makes overall unhealthy
|
|
expect(mockFetch).toHaveBeenCalledTimes(2); // API retried once
|
|
expect(checkDatabaseHealthSpy).toHaveBeenCalledTimes(3); // Database retried max times
|
|
});
|
|
});
|
|
|
|
describe('fallback logic', () => {
|
|
it('should continue with degraded status when external service fails', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 2000,
|
|
error: 'External service timeout',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
});
|
|
|
|
it('should handle partial failures without complete system failure', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1500,
|
|
error: 'High latency',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
// System should be degraded but not completely down
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
});
|
|
|
|
it('should provide fallback information in details', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1200,
|
|
error: 'External service degraded',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.dependencies.externalService.error).toBe('External service degraded');
|
|
});
|
|
});
|
|
|
|
describe('aggregation logic', () => {
|
|
it('should aggregate health status from multiple dependencies correctly', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 45,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 95,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// Verify all dependencies are checked
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
expect(health.dependencies.externalService.status).toBe('healthy');
|
|
|
|
// Verify latency aggregation (max of all latencies)
|
|
expect(health.dependencies.api.latency).toBeGreaterThan(0);
|
|
expect(health.dependencies.database.latency).toBeGreaterThan(0);
|
|
expect(health.dependencies.externalService.latency).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should correctly aggregate when one dependency is degraded', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1500,
|
|
error: 'Slow response',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// Aggregation should result in degraded status
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('healthy');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
|
|
it('should handle critical dependency failures in aggregation', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('API down'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// API failure should make overall status unhealthy
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('unhealthy');
|
|
});
|
|
|
|
it('should aggregate latency values correctly', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 150,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 200,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// Should take the maximum latency
|
|
expect(health.dependencies.api.latency).toBeGreaterThan(0);
|
|
expect(health.dependencies.database.latency).toBe(150);
|
|
expect(health.dependencies.externalService.latency).toBe(200);
|
|
});
|
|
});
|
|
|
|
describe('decision branches', () => {
|
|
it('should return healthy when all dependencies are healthy and fast', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap().status).toBe('healthy');
|
|
});
|
|
|
|
it('should return degraded when dependencies are healthy but slow', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 1200, // Exceeds threshold
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap().status).toBe('degraded');
|
|
});
|
|
|
|
it('should return unhealthy when critical dependencies fail', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('API unavailable'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap().status).toBe('unhealthy');
|
|
});
|
|
|
|
it('should handle different error types based on retryability', async () => {
|
|
// Test retryable error (timeout)
|
|
mockFetch.mockRejectedValueOnce(new Error('Connection timeout'));
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result1 = await service.getHealth();
|
|
expect(result1.isOk()).toBe(true);
|
|
expect(mockFetch).toHaveBeenCalledTimes(2); // Should retry
|
|
|
|
// Reset mocks
|
|
mockFetch.mockClear();
|
|
vi.clearAllMocks();
|
|
|
|
// Test non-retryable error (400)
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 400,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result2 = await service.getHealth();
|
|
expect(result2.isOk()).toBe(true);
|
|
expect(mockFetch).toHaveBeenCalledTimes(1); // Should not retry
|
|
});
|
|
|
|
it('should handle mixed dependency states correctly', async () => {
|
|
// API: healthy, Database: unhealthy, External: degraded
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'unhealthy',
|
|
latency: 100,
|
|
error: 'DB connection failed',
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1500,
|
|
error: 'Slow response',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// Database failure should make overall unhealthy
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('unhealthy');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
|
|
it('should handle edge case where all dependencies are degraded', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
status: 200,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 800,
|
|
error: 'Slow query',
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'degraded',
|
|
latency: 1200,
|
|
error: 'External timeout',
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
|
|
// All degraded should result in degraded overall
|
|
expect(health.status).toBe('degraded');
|
|
expect(health.dependencies.api.status).toBe('healthy');
|
|
expect(health.dependencies.database.status).toBe('degraded');
|
|
expect(health.dependencies.externalService.status).toBe('degraded');
|
|
});
|
|
|
|
it('should handle timeout aborts correctly', async () => {
|
|
// Mock fetch to simulate timeout
|
|
const abortError = new Error('The operation was aborted.');
|
|
abortError.name = 'AbortError';
|
|
mockFetch.mockRejectedValueOnce(abortError);
|
|
|
|
vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 50,
|
|
});
|
|
|
|
vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
|
|
status: 'healthy',
|
|
latency: 100,
|
|
});
|
|
|
|
const result = await service.getHealth();
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
const health = result.unwrap();
|
|
expect(health.status).toBe('unhealthy');
|
|
expect(health.dependencies.api.status).toBe('unhealthy');
|
|
});
|
|
});
|
|
});
|