Files
josh f500db971b
build-and-push / build-and-push (push) Successful in 1m26s
Initial commit: Infrastructure host tracking app
Fastify + node:sqlite single-process app with vanilla JS UI for
looking up hosts by hardware ID, hostname, or asset ID. Includes
per-host network interface tracking, sites/rooms/server-types CRUD,
Docker packaging, and a Gitea Actions workflow that runs tests then
builds and pushes to gitea.thewrightserver.net/josh/infrastructure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 17:05:50 -04:00

177 lines
5.5 KiB
JavaScript

import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { newApp, seedFixtures, seedHost } from './helpers.js';
const validIface = (host_id, name = 'eth0') => ({
host_id,
name,
mac_address: 'aa:bb:cc:dd:ee:ff',
ip_address: '10.0.0.5',
subnet: '10.0.0.0/24',
link_speed: '1000/full',
});
test('interfaces: create, list, get, update, delete', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const host = await seedHost(app, fx, 'A');
const create = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: validIface(host.id, 'eth0'),
});
assert.equal(create.statusCode, 201);
const iface = JSON.parse(create.body);
assert.equal(iface.name, 'eth0');
assert.equal(iface.host_id, host.id);
assert.equal(iface.ip_address, '10.0.0.5');
const list = await app.inject({
method: 'GET', url: `/api/interfaces?host_id=${host.id}`,
});
assert.equal(list.statusCode, 200);
assert.equal(JSON.parse(list.body).length, 1);
const get = await app.inject({ method: 'GET', url: `/api/interfaces/${iface.id}` });
assert.equal(get.statusCode, 200);
assert.equal(JSON.parse(get.body).name, 'eth0');
const upd = await app.inject({
method: 'PUT', url: `/api/interfaces/${iface.id}`,
payload: { ...validIface(host.id, 'eth0'), ip_address: '10.0.0.99' },
});
assert.equal(upd.statusCode, 200);
assert.equal(JSON.parse(upd.body).ip_address, '10.0.0.99');
const del = await app.inject({ method: 'DELETE', url: `/api/interfaces/${iface.id}` });
assert.equal(del.statusCode, 204);
const after404 = await app.inject({ method: 'GET', url: `/api/interfaces/${iface.id}` });
assert.equal(after404.statusCode, 404);
});
test('interfaces: optional fields may be empty strings', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const host = await seedHost(app, fx, 'B');
const r = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth0' },
});
assert.equal(r.statusCode, 201);
const iface = JSON.parse(r.body);
assert.equal(iface.mac_address, '');
assert.equal(iface.ip_address, '');
assert.equal(iface.subnet, '');
assert.equal(iface.link_speed, '');
});
test('interfaces: duplicate (host_id, name) returns 409', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const host = await seedHost(app, fx, 'C');
const a = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth0' },
});
assert.equal(a.statusCode, 201);
const dup = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth0' },
});
assert.equal(dup.statusCode, 409);
assert.match(JSON.parse(dup.body).error, /already exists/);
});
test('interfaces: same name allowed on different hosts', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const h1 = await seedHost(app, fx, 'D1');
const h2 = await seedHost(app, fx, 'D2');
const a = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: h1.id, name: 'eth0' },
});
const b = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: h2.id, name: 'eth0' },
});
assert.equal(a.statusCode, 201);
assert.equal(b.statusCode, 201);
});
test('interfaces: missing host FK returns 409', async () => {
const app = await newApp();
after(() => app.close());
const r = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: 9999, name: 'eth0' },
});
assert.equal(r.statusCode, 409);
});
test('interfaces: invalid formats return 400 with details', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const host = await seedHost(app, fx, 'E');
const cases = [
{ mac_address: 'zz:zz:zz:zz:zz:zz' },
{ ip_address: '999.0.0.1' },
{ subnet: '10.0.0.0/99' },
{ link_speed: '1000/weird' },
];
for (const extra of cases) {
const r = await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth0', ...extra },
});
assert.equal(r.statusCode, 400, `expected 400 for ${JSON.stringify(extra)}`);
const body = JSON.parse(r.body);
assert.equal(body.error, 'validation failed');
assert.ok(Array.isArray(body.details) && body.details.length > 0);
}
});
test('interfaces: deleting the host cascades', async () => {
const app = await newApp();
after(() => app.close());
const fx = await seedFixtures(app);
const host = await seedHost(app, fx, 'F');
await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth0' },
});
await app.inject({
method: 'POST', url: '/api/interfaces',
payload: { host_id: host.id, name: 'eth1' },
});
const del = await app.inject({ method: 'DELETE', url: `/api/hosts/${host.id}` });
assert.equal(del.statusCode, 204);
const list = await app.inject({
method: 'GET', url: `/api/interfaces?host_id=${host.id}`,
});
assert.equal(list.statusCode, 200);
assert.equal(JSON.parse(list.body).length, 0);
});
test('interfaces: list requires host_id', async () => {
const app = await newApp();
after(() => app.close());
const r = await app.inject({ method: 'GET', url: '/api/interfaces' });
assert.equal(r.statusCode, 400);
});