Files
Void-Homelab/tests/api/health.test.js
2026-06-08 00:55:19 +10:00

68 lines
4.1 KiB
JavaScript

import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import request from 'supertest';
import { setup } from './helpers.js';
import { resetDb } from '../helpers/db.js';
import { migrateUp } from '../../lib/db/migrate.js';
import * as statusRepo from '../../lib/db/repos/service_status.js';
import * as services from '../../lib/db/repos/monitored_services.js';
let app, ownerHeaders;
beforeAll(async () => { ({ app, ownerHeaders } = await setup()); });
beforeEach(async () => {
await resetDb(); await migrateUp();
await services.create({ id: 'gitea', name: 'Gitea', category: 'infrastructure', host: 'ct105', url: 'http://192.168.1.223:3000', icon: 'gitea', check: { type: 'http' } });
await statusRepo.upsert({ service_id: 'gitea', status: 'ok', latency_ms: 10, detail: '200' });
});
describe('health api (DB-backed registry)', () => {
it('401 without auth', async () => expect((await request(app).get('/api/health/services')).status).toBe(401));
it('POST /check rejects anonymous', async () => expect((await request(app).post('/api/health/check')).status).toBe(401));
it('POST /discover rejects anonymous', async () => expect((await request(app).post('/api/health/discover')).status).toBe(401));
it('POST /services rejects anonymous', async () => expect((await request(app).post('/api/health/services')).status).toBe(401));
it('GET /services returns grouped counts + merged status', async () => {
const res = await request(app).get('/api/health/services').set(ownerHeaders);
expect(res.status).toBe(200);
const infra = res.body.find(g => g.category === 'infrastructure');
expect(infra.healthy).toBe(1);
expect(infra.services.find(s => s.id === 'gitea').status).toBe('ok');
});
it('GET /services includes external on every tile; POST round-trips it', async () => {
const res = await request(app).get('/api/health/services').set(ownerHeaders);
const all = res.body.flatMap(g => g.services);
expect(all.every(s => 'external' in s)).toBe(true); // key present (may be null)
expect(all.find(s => s.id === 'gitea').external).toBeNull(); // no domain set
await request(app).post('/api/health/services').set(ownerHeaders)
.send({ id: 'gramps', name: 'Gramps', category: 'infrastructure', url: 'http://192.168.1.99', external: 'https://gramps.hynesy.com' });
const res2 = await request(app).get('/api/health/services').set(ownerHeaders);
const gramps = res2.body.flatMap(g => g.services).find(s => s.id === 'gramps');
expect(gramps.external).toBe('https://gramps.hynesy.com');
});
it('POST /services adds a service that shows up in the band', async () => {
const create = await request(app).post('/api/health/services').set(ownerHeaders)
.send({ id: 'ollama', name: 'Ollama', category: 'agents', host: 'ct102', url: 'http://192.168.1.185:11434' });
expect(create.status).toBe(201);
const res = await request(app).get('/api/health/services').set(ownerHeaders);
expect(res.body.find(g => g.category === 'agents').services.some(s => s.id === 'ollama')).toBe(true);
});
it('PATCH disables a service (drops out of the band); DELETE removes it', async () => {
await request(app).patch('/api/health/services/gitea').set(ownerHeaders).send({ enabled: false });
let res = await request(app).get('/api/health/services').set(ownerHeaders);
expect(res.body.find(g => g.category === 'infrastructure')).toBeUndefined(); // no enabled infra now
const del = await request(app).delete('/api/health/services/gitea').set(ownerHeaders);
expect(del.status).toBe(204);
expect((await request(app).delete('/api/health/services/gitea').set(ownerHeaders)).status).toBe(404);
});
it('GET /services/discovered lists owner-only candidates', async () => {
await services.upsertDiscovered({ id: 'disc-x', name: 'Mystery', url: 'http://192.168.1.99:8000' });
expect((await request(app).get('/api/health/services/discovered')).status).toBe(401); // anon
const res = await request(app).get('/api/health/services/discovered').set(ownerHeaders);
expect(res.status).toBe(200);
expect(res.body.map(s => s.id)).toContain('disc-x');
});
});