mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-09 12:28:51 +00:00
feat(M48): continuous TLS health monitoring — endpoint state machine, shared tlsprobe, 8 API endpoints, GUI
Adds continuous TLS endpoint health monitoring that closes the deploy→verify→monitor loop. After M25 verifies a deployment succeeded once, M48 continuously confirms it stays healthy. Key components: - Shared `internal/tlsprobe/` package extracted from network scanner for reuse - Health status state machine: healthy → degraded (2 failures) → down (5 failures), plus cert_mismatch when served fingerprint differs from expected - 8th scheduler loop (60s tick, per-endpoint configurable intervals) - PostgreSQL migration 000011: endpoint_health_checks + endpoint_health_history tables - 8 REST API endpoints (CRUD, history, acknowledge, summary) - Health Monitor GUI page with summary bar, status table, create modal, auto-refresh - 38 new tests (5 tlsprobe + 11 domain + 10 service + 8 handler + 4 frontend) - All coverage thresholds maintained (service 68%, handler 83%, domain 87%, middleware 63%) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,14 @@ import {
|
||||
updateIssuer,
|
||||
updateTarget,
|
||||
getPolicy,
|
||||
listHealthChecks,
|
||||
getHealthCheck,
|
||||
createHealthCheck,
|
||||
updateHealthCheck,
|
||||
deleteHealthCheck,
|
||||
getHealthCheckHistory,
|
||||
acknowledgeHealthCheck,
|
||||
getHealthCheckSummary,
|
||||
} from './client';
|
||||
|
||||
// Mock global fetch
|
||||
@@ -1236,4 +1244,38 @@ describe('API Client', () => {
|
||||
expect(mockFetch.mock.calls[0][0]).toBe('/api/v1/policies/pol-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Health Checks (M48)', () => {
|
||||
it('listHealthChecks sends GET with optional filters', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockJsonResponse({ data: [], total: 0, page: 1, per_page: 50 }));
|
||||
const result = await listHealthChecks({ status: 'degraded' });
|
||||
expect(result.total).toBe(0);
|
||||
expect(mockFetch.mock.calls[0][0]).toContain('/api/v1/health-checks');
|
||||
expect(mockFetch.mock.calls[0][0]).toContain('status=degraded');
|
||||
});
|
||||
|
||||
it('getHealthCheck sends GET with health check ID', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockJsonResponse({ id: 'hc-1', endpoint: 'example.com:443' }));
|
||||
const result = await getHealthCheck('hc-1');
|
||||
expect(result.id).toBe('hc-1');
|
||||
expect(mockFetch.mock.calls[0][0]).toBe('/api/v1/health-checks/hc-1');
|
||||
});
|
||||
|
||||
it('createHealthCheck sends POST with data', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockJsonResponse({ id: 'hc-1', endpoint: 'example.com:443' }));
|
||||
const result = await createHealthCheck({ endpoint: 'example.com:443' });
|
||||
expect(result.id).toBe('hc-1');
|
||||
const [url, init] = mockFetch.mock.calls[0];
|
||||
expect(url).toContain('/api/v1/health-checks');
|
||||
expect(init.method).toBe('POST');
|
||||
});
|
||||
|
||||
it('getHealthCheckSummary sends GET to /health-checks/summary', async () => {
|
||||
mockFetch.mockReturnValueOnce(mockJsonResponse({ healthy: 5, degraded: 1, down: 0, cert_mismatch: 0, unknown: 2, total: 8 }));
|
||||
const result = await getHealthCheckSummary();
|
||||
expect(result.healthy).toBe(5);
|
||||
expect(result.total).toBe(8);
|
||||
expect(mockFetch.mock.calls[0][0]).toBe('/api/v1/health-checks/summary');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user