f500db971b
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>
129 lines
4.4 KiB
JavaScript
129 lines
4.4 KiB
JavaScript
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);
|
|
});
|