mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
Comments and served API descriptions here had grown references that only make sense to someone with our internal notes: relative paths that escape this tree, filenames and titles of documents nobody outside can open, quoted prose from them, and the name of a component that has no public surface — once in an OpenAPI description, which is published output rather than source. None of it was load-bearing. Every case restates as what the code actually requires, and every rewrite came out shorter: "in the words the host agent reports" for a component name, "republished as addresses are discovered" for a quoted phrase, "a size tier sets vCPU, RAM and the output geometry" for a sentence that had been carrying a path. Internal reasoning is now cited exactly one way, ref(d-NNNN) in a source comment, with the rule that the sentence must still stand if the marker is deleted. CLAUDE.md leads with it, because the previous version of this mistake was made by people who knew the repo was public and it still took ten occurrences to notice, so "be careful" is not a mechanism. Commit messages get the stricter rule and carry no references at all: a comment can be fixed by the next commit and a published message cannot be fixed at all. Git hooks now enforce both halves. The check caught a real one while being written: the CLAUDE.md table spelled out the paths it was prohibiting, which discloses them to exactly the reader it protects against. 138 tests, 0 fail.
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
import { afterAll, describe, expect, test } from 'bun:test';
|
||
|
||
import { Fixtures } from '@nestri/core/db/fixtures';
|
||
import { testDb } from '@nestri/core/db/test';
|
||
import { Identifier } from '@nestri/core/id';
|
||
import { Machine } from '@nestri/core/machine/index';
|
||
|
||
import { app } from '../app/index';
|
||
import './setup';
|
||
|
||
const sql = testDb();
|
||
|
||
const createdUserIds: string[] = [];
|
||
|
||
/**
|
||
* A registered host, with the secret kept — which registration returns exactly
|
||
* once, so a test that needs to authenticate as a machine has to hold onto it
|
||
* here rather than reading it back later.
|
||
*/
|
||
async function registeredHost(label: string) {
|
||
const owner = await Fixtures.owner(label);
|
||
createdUserIds.push(owner.userId);
|
||
const registered = await Machine.register({
|
||
id: Identifier.ascending('machine'),
|
||
ownerUserId: owner.userId,
|
||
teamId: owner.teamId,
|
||
label
|
||
});
|
||
return {
|
||
id: registered.id,
|
||
headers: {
|
||
'x-nestri-machine-id': registered.id,
|
||
'x-nestri-machine-secret': registered.secret
|
||
}
|
||
};
|
||
}
|
||
|
||
afterAll(async () => {
|
||
if (createdUserIds.length > 0) {
|
||
await sql`delete from "user" where id in ${sql(createdUserIds)}`;
|
||
createdUserIds.length = 0;
|
||
}
|
||
});
|
||
|
||
describe('POST /machine/heartbeat', () => {
|
||
test('a host beats and is told how often to beat again', async () => {
|
||
const host = await registeredHost('beat-ok');
|
||
|
||
const res = await app.request('/machine/heartbeat', {
|
||
method: 'POST',
|
||
headers: host.headers
|
||
});
|
||
expect(res.status).toBe(200);
|
||
|
||
const body = (await res.json()) as any;
|
||
// These two field names are what a host agent reads out of the reply. A
|
||
// rename on either side produces a host that beats, parses nothing, and
|
||
// reports success — so the names are the contract and this is the test
|
||
// that holds them.
|
||
expect(typeof body.data.lastSeen).toBe('string');
|
||
expect(body.data.intervalSeconds).toBe(Machine.HEARTBEAT_SECONDS);
|
||
expect(new Date(body.data.lastSeen).getTime()).not.toBeNaN();
|
||
});
|
||
|
||
test('the beat is what makes the host look online', async () => {
|
||
const host = await registeredHost('beat-online');
|
||
|
||
// Before any beat there is nothing to be online on the strength of.
|
||
expect(Machine.isOnline((await Machine.fromID(host.id))?.lastSeen ?? null)).toBe(false);
|
||
|
||
await app.request('/machine/heartbeat', { method: 'POST', headers: host.headers });
|
||
|
||
expect(Machine.isOnline((await Machine.fromID(host.id))?.lastSeen ?? null)).toBe(true);
|
||
});
|
||
|
||
test('wrong credentials are indistinguishable from none', async () => {
|
||
const host = await registeredHost('beat-wrongsecret');
|
||
|
||
const none = await app.request('/machine/heartbeat', { method: 'POST' });
|
||
const wrong = await app.request('/machine/heartbeat', {
|
||
method: 'POST',
|
||
headers: { ...host.headers, 'x-nestri-machine-secret': 'msk_wrong' }
|
||
});
|
||
|
||
// The middleware falls through to `public` on bad credentials rather
|
||
// than erroring, precisely so probing cannot tell an attacker which
|
||
// machine ids exist. Both therefore fail the same way, and asserting
|
||
// they are *identical* is the only way that property stays true.
|
||
expect(wrong.status).toBe(403);
|
||
expect(none.status).toBe(403);
|
||
expect(await wrong.json()).toEqual(await none.json());
|
||
|
||
// And the failed attempt left no trace of having been alive.
|
||
expect((await Machine.fromID(host.id))?.lastSeen).toBeNull();
|
||
});
|
||
|
||
test('a user session cannot beat on a host’s behalf', async () => {
|
||
// A box holds credentials but is not its owner, and the reverse holds
|
||
// too: `machineOnly` exists so a route written for a host cannot be
|
||
// driven by whoever owns it.
|
||
const res = await app.request('/machine/heartbeat', {
|
||
method: 'POST',
|
||
headers: { 'x-nestri-admin-token': 'test-admin-secret-42' }
|
||
});
|
||
expect(res.status).toBe(403);
|
||
});
|
||
});
|