Initial commit: Infrastructure host tracking app
build-and-push / build-and-push (push) Successful in 1m26s
build-and-push / build-and-push (push) Successful in 1m26s
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>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { test, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { newApp, seedFixtures, newHostPayload } from './helpers.js';
|
||||
|
||||
test('hosts: create, get, search, update, delete', async (t) => {
|
||||
const app = await newApp();
|
||||
after(() => app.close());
|
||||
const fx = await seedFixtures(app);
|
||||
|
||||
// Create
|
||||
const create = await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: newHostPayload({ room_id: fx.room.id, server_type_id: fx.type.id }, 'A'),
|
||||
});
|
||||
assert.equal(create.statusCode, 201);
|
||||
const created = JSON.parse(create.body);
|
||||
assert.equal(created.hostname, 'host-A');
|
||||
assert.equal(created.site_name, 'HQ');
|
||||
assert.equal(created.room_name, 'Main');
|
||||
assert.equal(created.server_type, 'Web');
|
||||
|
||||
// Get one
|
||||
const get = await app.inject({ method: 'GET', url: `/api/hosts/${created.id}` });
|
||||
assert.equal(get.statusCode, 200);
|
||||
assert.equal(JSON.parse(get.body).hostname, 'host-A');
|
||||
|
||||
// 404 on missing
|
||||
const missing = await app.inject({ method: 'GET', url: '/api/hosts/9999' });
|
||||
assert.equal(missing.statusCode, 404);
|
||||
|
||||
// Lookup by hardware_id
|
||||
const byHwid = await app.inject({ method: 'GET', url: '/api/hosts/by-hardware-id/HW-A' });
|
||||
assert.equal(byHwid.statusCode, 200);
|
||||
assert.equal(JSON.parse(byHwid.body).id, created.id);
|
||||
|
||||
// 404 by hardware_id
|
||||
const missingHwid = await app.inject({ method: 'GET', url: '/api/hosts/by-hardware-id/nope' });
|
||||
assert.equal(missingHwid.statusCode, 404);
|
||||
|
||||
// Search by hostname / hardware_id / asset_id
|
||||
for (const q of ['host', 'HW-A', 'AST-A']) {
|
||||
const r = await app.inject({ method: 'GET', url: `/api/hosts?q=${encodeURIComponent(q)}` });
|
||||
assert.equal(r.statusCode, 200);
|
||||
const rows = JSON.parse(r.body);
|
||||
assert.equal(rows.length, 1);
|
||||
assert.equal(rows[0].hostname, 'host-A');
|
||||
}
|
||||
|
||||
// Search is case-insensitive
|
||||
const ci = await app.inject({ method: 'GET', url: '/api/hosts?q=HOST-a' });
|
||||
assert.equal(JSON.parse(ci.body).length, 1);
|
||||
|
||||
// Update
|
||||
const upd = await app.inject({
|
||||
method: 'PUT', url: `/api/hosts/${created.id}`,
|
||||
payload: { ...newHostPayload({ room_id: fx.room.id, server_type_id: fx.type.id }, 'A'), position: 'R5-U10' },
|
||||
});
|
||||
assert.equal(upd.statusCode, 200);
|
||||
assert.equal(JSON.parse(upd.body).position, 'R5-U10');
|
||||
|
||||
// Delete
|
||||
const del = await app.inject({ method: 'DELETE', url: `/api/hosts/${created.id}` });
|
||||
assert.equal(del.statusCode, 204);
|
||||
const afterDel = await app.inject({ method: 'GET', url: `/api/hosts/${created.id}` });
|
||||
assert.equal(afterDel.statusCode, 404);
|
||||
});
|
||||
|
||||
test('hosts: duplicate hardware_id returns 409', async (t) => {
|
||||
const app = await newApp();
|
||||
after(() => app.close());
|
||||
const fx = await seedFixtures(app);
|
||||
|
||||
const a = await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: newHostPayload({ room_id: fx.room.id, server_type_id: fx.type.id }, 'X'),
|
||||
});
|
||||
assert.equal(a.statusCode, 201);
|
||||
|
||||
const dup = await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: { ...newHostPayload({ room_id: fx.room.id, server_type_id: fx.type.id }, 'Y'), hardware_id: 'HW-X' },
|
||||
});
|
||||
assert.equal(dup.statusCode, 409);
|
||||
assert.match(JSON.parse(dup.body).error, /hardware_id/);
|
||||
});
|
||||
|
||||
test('hosts: invalid body returns 400 with details', async (t) => {
|
||||
const app = await newApp();
|
||||
after(() => app.close());
|
||||
|
||||
const r = await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: { hostname: '' },
|
||||
});
|
||||
assert.equal(r.statusCode, 400);
|
||||
const body = JSON.parse(r.body);
|
||||
assert.equal(body.error, 'validation failed');
|
||||
assert.ok(Array.isArray(body.details) && body.details.length > 0);
|
||||
});
|
||||
|
||||
test('hosts: missing FK returns 409', async (t) => {
|
||||
const app = await newApp();
|
||||
after(() => app.close());
|
||||
|
||||
const r = await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: {
|
||||
hardware_id: 'HW-1', hostname: 'h', asset_id: 'A',
|
||||
room_id: 999, position: '', server_type_id: 999,
|
||||
},
|
||||
});
|
||||
assert.equal(r.statusCode, 409);
|
||||
});
|
||||
|
||||
test('hosts: list caps at 200', async (t) => {
|
||||
const app = await newApp();
|
||||
after(() => app.close());
|
||||
const fx = await seedFixtures(app);
|
||||
|
||||
for (let i = 0; i < 205; i++) {
|
||||
await app.inject({
|
||||
method: 'POST', url: '/api/hosts',
|
||||
payload: newHostPayload({ room_id: fx.room.id, server_type_id: fx.type.id }, String(i).padStart(4, '0')),
|
||||
});
|
||||
}
|
||||
const r = await app.inject({ method: 'GET', url: '/api/hosts' });
|
||||
assert.equal(JSON.parse(r.body).length, 200);
|
||||
});
|
||||
Reference in New Issue
Block a user