← back to Designer Wallcoverings
DW-Agents/__tests__/health/health-check.test.ts
183 lines
/**
* Health Check Tests
* Tests for /api/health endpoint and system health monitoring
*/
import axios from 'axios';
const BASE_URL = process.env.BASE_URL || 'http://localhost';
const MASTER_HUB_PORT = process.env.MASTER_HUB_PORT || '9800';
const HEALTH_ENDPOINT = `${BASE_URL}:${MASTER_HUB_PORT}/api/health`;
describe('Health Check Endpoint', () => {
describe('GET /api/health', () => {
it('should return 200 status code', async () => {
const response = await axios.get(HEALTH_ENDPOINT, {
validateStatus: () => true // Don't throw on non-2xx
});
expect(response.status).toBe(200);
});
it('should return valid JSON response', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
expect(response.headers['content-type']).toMatch(/application\/json/);
expect(response.data).toBeDefined();
});
it('should include status field', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
expect(response.data).toHaveProperty('status');
expect(['healthy', 'degraded', 'unhealthy']).toContain(response.data.status);
});
it('should include timestamp', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
expect(response.data).toHaveProperty('timestamp');
const timestamp = new Date(response.data.timestamp);
expect(timestamp.getTime()).not.toBeNaN();
// Timestamp should be recent (within last 5 seconds)
const now = new Date();
const diff = now.getTime() - timestamp.getTime();
expect(diff).toBeLessThan(5000);
});
it('should respond within 1 second', async () => {
const startTime = Date.now();
await axios.get(HEALTH_ENDPOINT);
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(1000);
});
it('should handle multiple concurrent requests', async () => {
const requests = Array(10).fill(null).map(() =>
axios.get(HEALTH_ENDPOINT)
);
const responses = await Promise.all(requests);
responses.forEach(response => {
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('status');
});
});
it('should include uptime information if available', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
// Uptime is optional but if present should be valid
if (response.data.uptime) {
expect(typeof response.data.uptime).toBe('number');
expect(response.data.uptime).toBeGreaterThan(0);
}
});
it('should include service information', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
// Service info is optional but recommended
if (response.data.service) {
expect(response.data.service).toHaveProperty('name');
expect(response.data.service).toHaveProperty('version');
}
});
});
describe('Health Check Reliability', () => {
it('should be idempotent - multiple calls return consistent results', async () => {
const response1 = await axios.get(HEALTH_ENDPOINT);
await new Promise(resolve => setTimeout(resolve, 100));
const response2 = await axios.get(HEALTH_ENDPOINT);
expect(response1.data.status).toBe(response2.data.status);
});
it('should handle invalid query parameters gracefully', async () => {
const response = await axios.get(`${HEALTH_ENDPOINT}?invalid=param`, {
validateStatus: () => true
});
// Should still return 200 and ignore invalid params
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('status');
});
it('should not expose sensitive information', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
const dataStr = JSON.stringify(response.data).toLowerCase();
// Should not contain common sensitive fields
expect(dataStr).not.toMatch(/password/);
expect(dataStr).not.toMatch(/secret/);
expect(dataStr).not.toMatch(/token/);
expect(dataStr).not.toMatch(/api[_-]?key/);
});
});
describe('Health Check Performance', () => {
it('should maintain performance under load', async () => {
const iterations = 50;
const durations: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = Date.now();
await axios.get(HEALTH_ENDPOINT);
durations.push(Date.now() - start);
}
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
const maxDuration = Math.max(...durations);
expect(avgDuration).toBeLessThan(500); // Average under 500ms
expect(maxDuration).toBeLessThan(2000); // Max under 2s
});
it('should handle rapid successive calls', async () => {
const promises = [];
// Fire 20 requests as fast as possible
for (let i = 0; i < 20; i++) {
promises.push(axios.get(HEALTH_ENDPOINT));
}
const responses = await Promise.all(promises);
// All should succeed
expect(responses.length).toBe(20);
responses.forEach(response => {
expect(response.status).toBe(200);
});
});
});
});
describe('System Health Monitoring', () => {
it('should detect when service becomes unhealthy', async () => {
// This is a placeholder for more sophisticated health detection
// In a real scenario, we'd test actual failure conditions
const response = await axios.get(HEALTH_ENDPOINT);
// For now, just verify the health check is working
expect(['healthy', 'degraded', 'unhealthy']).toContain(response.data.status);
});
it('should provide health check with appropriate cache headers', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
// Health checks should not be cached
const cacheControl = response.headers['cache-control'];
if (cacheControl) {
expect(cacheControl).toMatch(/no-cache|no-store|max-age=0/);
}
});
});