Committed by Hermes autonomous QA sprint 2026-08-13. These files were modified during the Aug 12 sprint but never committed.
523 lines
22 KiB
JavaScript
523 lines
22 KiB
JavaScript
/**
|
|
* DC-083: Branch coverage tests for the new /system/health endpoint in routes/health.js.
|
|
*
|
|
* The endpoint at GET /api/system/health aggregates four checks (services, memory,
|
|
* diskSpace, incidents) into an overall status. It has many uncovered branches:
|
|
* - status === 'ok' / 'degraded' / 'down' in the services check
|
|
* - status === 'ok' / 'warning' in the memory check
|
|
* - status === 'ok' / 'warning' / 'critical' in the diskSpace check
|
|
* - status === 'ok' / 'degraded' in the incidents check
|
|
* - each check has a try/catch → unknown fallback
|
|
* - overall status computation (unhealthy / degraded / healthy)
|
|
*
|
|
* Also covers additional uncovered branches in the /health-checks/* endpoints:
|
|
* - unhealthy filter in /health-checks/status
|
|
* - incidents open/non-empty
|
|
* - incidents/history with pagination params
|
|
* - /health/probe with and without ?url
|
|
* - /health/services with array vs object services data, error paths
|
|
*/
|
|
const express = require('express');
|
|
const request = require('supertest');
|
|
|
|
// Minimal asyncHandler that catches errors
|
|
function asyncHandler(fn) {
|
|
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
|
}
|
|
|
|
// ---- Mocks (mirrors health.routes.test.js) ----
|
|
jest.mock('child_process', () => ({ execSync: jest.fn() }));
|
|
jest.mock('../../platform-paths', () => ({
|
|
caCertDir: '/mock/ca',
|
|
pkiRootCert: '/mock/pki/root.crt',
|
|
dataDir: '/mock/data',
|
|
}));
|
|
jest.mock('../../src/utilities/fs-helpers', () => ({ exists: jest.fn().mockResolvedValue(true) }));
|
|
jest.mock('../../src/utilities/url-resolver', () => ({
|
|
resolveServiceUrl: jest.fn((id) => `https://${id}.test`),
|
|
}));
|
|
jest.mock('../../src/utilities/pagination', () => ({
|
|
paginate: jest.fn((data, params) => ({ data, pagination: params ? { page: 1, limit: 10, total: data.length } : null })),
|
|
parsePaginationParams: jest.fn(() => null),
|
|
}));
|
|
|
|
const { exists } = require('../../src/utilities/fs-helpers');
|
|
const { resolveServiceUrl } = require('../../src/utilities/url-resolver');
|
|
const { execSync } = require('child_process');
|
|
const platformPaths = require('../../platform-paths');
|
|
|
|
function createApp(depsOverride = {}) {
|
|
const defaultDeps = {
|
|
fetchT: jest.fn().mockResolvedValue({ ok: true, status: 200, json: () => ({}) }),
|
|
SERVICES_FILE: '/tmp/services.json',
|
|
servicesStateManager: {
|
|
read: jest.fn().mockResolvedValue([]),
|
|
write: jest.fn().mockResolvedValue(),
|
|
update: jest.fn().mockResolvedValue([]),
|
|
},
|
|
siteConfig: { tld: 'sami' },
|
|
buildServiceUrl: jest.fn(id => `https://${id}.sami`),
|
|
asyncHandler,
|
|
logError: jest.fn(),
|
|
healthChecker: {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getServiceStats: jest.fn().mockReturnValue(null),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
},
|
|
};
|
|
const deps = { ...defaultDeps, ...depsOverride };
|
|
const healthRoutes = require('../../routes/health');
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use('/api', healthRoutes(deps));
|
|
app.use((err, req, res, next) => {
|
|
const status = err.statusCode || 500;
|
|
res.status(status).json({ success: false, error: err.message });
|
|
});
|
|
return { app, deps };
|
|
}
|
|
|
|
describe('System health endpoint (DC-083)', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
exists.mockResolvedValue(true);
|
|
execSync.mockReturnValue('notAfter=Dec 22 12:00:00 2034 GMT');
|
|
});
|
|
|
|
describe('GET /api/system/health', () => {
|
|
it('returns healthy overall when all checks pass', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: { status: 'up' },
|
|
svc2: { status: 'healthy' },
|
|
svc3: { status: 'online' },
|
|
}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
// disk: 40% used → ok. df output format: header line + data line.
|
|
// parts[0]='40%', parseInt → 40
|
|
execSync.mockReturnValue('Use% Size Avail\n 40% 100G 60G');
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.status).toBe('healthy');
|
|
expect(res.body.checks.services.status).toBe('ok');
|
|
expect(res.body.checks.services.healthy).toBe(3);
|
|
expect(res.body.checks.memory.status).toBe('ok');
|
|
expect(res.body.checks.diskSpace.status).toBe('ok');
|
|
expect(res.body.checks.incidents.status).toBe('ok');
|
|
});
|
|
|
|
it('returns degraded when some services are unhealthy (mixed)', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: { status: 'up' },
|
|
svc2: { status: 'down' },
|
|
}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.checks.services.status).toBe('degraded');
|
|
expect(res.body.checks.services.unhealthy).toBe(1);
|
|
expect(res.body.checks.services.unknown).toBe(0);
|
|
// Overall degraded because services degraded
|
|
expect(res.body.status).toBe('degraded');
|
|
});
|
|
|
|
it('returns down when ALL services are unhealthy', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: { status: 'down' },
|
|
svc2: { status: 'offline' },
|
|
}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.services.status).toBe('down');
|
|
// Overall unhealthy because services down
|
|
expect(res.body.status).toBe('unhealthy');
|
|
});
|
|
|
|
it('counts unknown status values (not up/down/healthy/etc.)', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: { state: 'starting' }, // unknown state value
|
|
svc2: { status: 'paused' }, // unknown status value
|
|
svc3: { }, // no status/state → unknown
|
|
}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.services.total).toBe(3);
|
|
expect(res.body.checks.services.healthy).toBe(0);
|
|
expect(res.body.checks.services.unhealthy).toBe(0);
|
|
expect(res.body.checks.services.unknown).toBe(3);
|
|
});
|
|
|
|
it('returns degraded when incidents are open', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([{ id: 'inc1' }, { id: 'inc2' }]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.incidents.status).toBe('degraded');
|
|
expect(res.body.checks.incidents.count).toBe(2);
|
|
expect(res.body.status).toBe('degraded');
|
|
});
|
|
|
|
it('returns warning when disk usage between 90-95%', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
execSync.mockReturnValue('Use% Size Avail\n 92% 100G 8G');
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.diskSpace.status).toBe('warning');
|
|
expect(res.body.checks.diskSpace.usedPercent).toBe(92);
|
|
expect(res.body.status).toBe('degraded');
|
|
});
|
|
|
|
it('returns critical when disk usage >= 95%', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
execSync.mockReturnValue('Use% Size Avail\n 97% 100G 3G');
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.diskSpace.status).toBe('critical');
|
|
expect(res.body.status).toBe('unhealthy');
|
|
});
|
|
|
|
it('falls back to unknown for services when getCurrentStatus throws', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockImplementation(() => { throw new Error('boom'); }),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.services.status).toBe('unknown');
|
|
// unknown → degraded overall
|
|
expect(res.body.status).toBe('degraded');
|
|
});
|
|
|
|
it('falls back to unknown for disk when execSync throws', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
execSync.mockImplementation(() => { throw new Error('df failed'); });
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.diskSpace.status).toBe('unknown');
|
|
});
|
|
|
|
it('falls back to unknown for incidents when getOpenIncidents throws', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getOpenIncidents: jest.fn().mockImplementation(() => { throw new Error('inc fail'); }),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.incidents.status).toBe('unknown');
|
|
expect(res.body.checks.incidents.count).toBe(0);
|
|
});
|
|
|
|
it('sets Cache-Control: no-store header', async () => {
|
|
const { app } = createApp();
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.headers['cache-control']).toBe('no-store');
|
|
});
|
|
|
|
it('includes uptime block with seconds and human-readable', async () => {
|
|
const { app } = createApp();
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.body.checks.uptime).toHaveProperty('seconds');
|
|
expect(res.body.checks.uptime).toHaveProperty('human');
|
|
expect(typeof res.body.checks.uptime.seconds).toBe('number');
|
|
});
|
|
|
|
it('handles empty df output (only header line) — no diskSpace block set to ok', async () => {
|
|
// df returns just one line → lines.length < 2 → diskSpace not assigned in try
|
|
// (stays undefined → overall status considers it). Actually the try block
|
|
// does NOT set diskSpace when lines.length < 2, so diskSpace is undefined
|
|
// and Object.values(checks) excludes it. Verify no crash.
|
|
execSync.mockReturnValue('Use% Size Avail');
|
|
const { app } = createApp();
|
|
const res = await request(app).get('/api/system/health');
|
|
expect(res.status).toBe(200);
|
|
});
|
|
});
|
|
|
|
// ---- Coverage for health-checks/status unhealthy filter ----
|
|
describe('GET /api/health-checks/status — unhealthy filter coverage', () => {
|
|
it('counts unhealthy services via various status/state tokens', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: { status: 'down' },
|
|
svc2: { state: 'unhealthy' },
|
|
svc3: { status: 'offline' },
|
|
svc4: { status: 'error' },
|
|
svc5: { status: 'up' },
|
|
}),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/health-checks/status');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.summary.unhealthy).toBe(4);
|
|
expect(res.body.summary.healthy).toBe(1);
|
|
expect(res.body.summary.unknown).toBe(0);
|
|
expect(res.body.summary.total).toBe(5);
|
|
});
|
|
|
|
it('handles null/undefined status entries', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({
|
|
svc1: null,
|
|
svc2: {},
|
|
svc3: { status: 'up' },
|
|
}),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getOpenIncidents: jest.fn().mockReturnValue([]),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/health-checks/status');
|
|
expect(res.status).toBe(200);
|
|
// null and {} are not healthy or unhealthy → unknown
|
|
expect(res.body.summary.unknown).toBe(2);
|
|
expect(res.body.summary.healthy).toBe(1);
|
|
});
|
|
});
|
|
|
|
// ---- Coverage for /health/probe ----
|
|
describe('GET /api/health/probe', () => {
|
|
it('returns 400 when url query param missing', async () => {
|
|
const { app } = createApp();
|
|
const res = await request(app).get('/api/health/probe');
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it('returns probe result when url provided and fetch succeeds', async () => {
|
|
const fetchT = jest.fn().mockResolvedValue({ ok: true, status: 200, json: () => ({}) });
|
|
const { app } = createApp({ fetchT });
|
|
const res = await request(app).get('/api/health/probe?url=https://example.com');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.status).toBe('healthy');
|
|
expect(res.body.statusCode).toBe(200);
|
|
});
|
|
|
|
it('returns unhealthy when probe fetch fails completely', async () => {
|
|
const fetchT = jest.fn().mockRejectedValue(new Error('timeout'));
|
|
const { app } = createApp({ fetchT });
|
|
const res = await request(app).get('/api/health/probe?url=https://down.example');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.status).toBe('unhealthy');
|
|
expect(res.body.reason).toBe('fetch failed');
|
|
});
|
|
|
|
it('marks status as unhealthy when statusCode >= 500', async () => {
|
|
const fetchT = jest.fn().mockResolvedValue({ ok: false, status: 503 });
|
|
const { app } = createApp({ fetchT });
|
|
const res = await request(app).get('/api/health/probe?url=https://500.example');
|
|
expect(res.body.status).toBe('unhealthy');
|
|
expect(res.body.statusCode).toBe(503);
|
|
});
|
|
|
|
it('marks status as healthy when statusCode is 401/403 (auth wall)', async () => {
|
|
const fetchT = jest.fn().mockResolvedValue({ ok: false, status: 401 });
|
|
const { app } = createApp({ fetchT });
|
|
const res = await request(app).get('/api/health/probe?url=https://auth.example');
|
|
expect(res.body.status).toBe('healthy');
|
|
expect(res.body.statusCode).toBe(401);
|
|
});
|
|
});
|
|
|
|
// ---- Coverage for /health/services with various service shapes ----
|
|
describe('GET /api/health/services — service shape branches', () => {
|
|
it('handles services as object with .services array', async () => {
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue({ services: [{ id: 'svc1', name: 'S1' }] }),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const fetchT = jest.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
const { app } = createApp({ servicesStateManager: stateManager, fetchT });
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.health).toHaveProperty('svc1');
|
|
});
|
|
|
|
it('uses service.name (lowercased) as id when service.id absent', async () => {
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ name: 'MyService' }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const fetchT = jest.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
const { app } = createApp({ servicesStateManager: stateManager, fetchT });
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.health).toHaveProperty('myservice');
|
|
});
|
|
|
|
it('skips services with no id and no name', async () => {
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ port: 8080 }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const { app } = createApp({ servicesStateManager: stateManager });
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.health).toEqual({});
|
|
});
|
|
|
|
it('marks service as unknown when URL resolves to null', async () => {
|
|
resolveServiceUrl.mockReturnValue(null);
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ id: 'novurl', name: 'No URL' }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const { app } = createApp({ servicesStateManager: stateManager });
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.body.health.novurl.status).toBe('unknown');
|
|
expect(res.body.health.novurl.reason).toMatch(/No URL/);
|
|
resolveServiceUrl.mockReturnValue('https://fallback.test');
|
|
});
|
|
|
|
it('uses pylon relay when direct check fails and pylon configured', async () => {
|
|
// Direct HEAD and GET both throw → falls through to pylon
|
|
const fetchT = jest.fn()
|
|
.mockRejectedValueOnce(new Error('HEAD fail')) // HEAD
|
|
.mockRejectedValueOnce(new Error('GET fail')) // GET (fallback in checkDirect)
|
|
.mockResolvedValueOnce({ // pylon probe
|
|
ok: true, status: 200,
|
|
json: () => ({ status: 'healthy', statusCode: 200, responseTime: 42 }),
|
|
});
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ id: 'svc1', name: 'S1' }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const { app } = createApp({
|
|
servicesStateManager: stateManager,
|
|
fetchT,
|
|
siteConfig: { tld: 'sami', pylon: { url: 'http://pylon.test', key: 'k' } },
|
|
});
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.body.health.svc1.via).toBe('pylon');
|
|
expect(res.body.health.svc1.status).toBe('healthy');
|
|
});
|
|
|
|
it('marks unhealthy when both direct and pylon fail (pylon configured)', async () => {
|
|
const fetchT = jest.fn()
|
|
.mockRejectedValueOnce(new Error('HEAD fail'))
|
|
.mockRejectedValueOnce(new Error('GET fail'))
|
|
.mockRejectedValueOnce(new Error('pylon fail'));
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ id: 'svc1', name: 'S1' }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const { app } = createApp({
|
|
servicesStateManager: stateManager,
|
|
fetchT,
|
|
siteConfig: { tld: 'sami', pylon: { url: 'http://pylon.test' } },
|
|
});
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.body.health.svc1.status).toBe('unhealthy');
|
|
expect(res.body.health.svc1.reason).toMatch(/direct \+ pylon/);
|
|
});
|
|
|
|
it('catches errors thrown by resolveServiceUrl and marks as error', async () => {
|
|
resolveServiceUrl.mockImplementation(() => { throw new Error('resolver exploded'); });
|
|
const stateManager = {
|
|
read: jest.fn().mockResolvedValue([{ id: 'svc1', name: 'S1' }]),
|
|
write: jest.fn(),
|
|
update: jest.fn(),
|
|
};
|
|
const { app } = createApp({ servicesStateManager: stateManager });
|
|
const res = await request(app).get('/api/health/services');
|
|
expect(res.body.health.svc1.status).toBe('error');
|
|
expect(res.body.health.svc1.reason).toMatch(/resolver exploded/);
|
|
resolveServiceUrl.mockReturnValue('https://fallback.test');
|
|
});
|
|
});
|
|
|
|
// ---- Coverage for /health-checks/incidents and history with pagination ----
|
|
describe('GET /api/health-checks/incidents — non-empty', () => {
|
|
it('returns incidents list', async () => {
|
|
const healthChecker = {
|
|
getCurrentStatus: jest.fn().mockReturnValue({}),
|
|
getServiceStats: jest.fn(),
|
|
configureService: jest.fn(),
|
|
removeService: jest.fn(),
|
|
getOpenIncidents: jest.fn().mockReturnValue([{ id: 'inc1', serviceId: 'svc1' }]),
|
|
getIncidentHistory: jest.fn().mockReturnValue([]),
|
|
};
|
|
const { app } = createApp({ healthChecker });
|
|
const res = await request(app).get('/api/health-checks/incidents');
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.incidents).toHaveLength(1);
|
|
});
|
|
});
|
|
});
|