Files
stack/packages/webui/tests/chat-instances.test.mjs
Rocko 049214486b wip: publish unverified row 219 inspection candidate
Preserve the draft for inspection under sage-3113. Tests and mutants have not run. Old-test adaptations approved in mos-1316 and mos-1317 are recorded in OLD-TEST-CHANGES.md.
2026-10-23 05:37:43 -05:00

88 lines
4.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { request } from 'node:http';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { startServer } from '../src/serve.mjs';
import { startTesterSocket, TESTER_ORIGIN } from '../src/tester-socket.mjs';
import { close } from './fixture.mjs';
const path = '/api/chat/instances';
const preview = { host: 'chat.example', header: 'x-user', user: 'jason' };
const snapshot = {
revision: 'a'.repeat(64), plugins: { private: 'do-not-project' },
instances: {
assistant: { definition: 'assistant', vars: { model: 'private-model' } },
bean: { definition: 'companion', vars: { 'persona.name': 'Bean', 'persona.sha256': 'private-hash' } },
chet: { definition: 'continuity', vars: { 'persona.name': 'Bean', file: '/private/path' } },
pm: { definition: 'pm', vars: { 'persona.name': 'Hidden PM' } },
},
};
function get(server, { route = path, method = 'GET', headers = {} } = {}) {
const address = server.address(), unix = typeof address === 'string';
return new Promise((resolve, reject) => {
const req = request({ ...(unix ? { socketPath: address } : { host: '127.0.0.1', port: address.port }),
path: route, method, headers: { host: unix ? new URL(TESTER_ORIGIN).host : preview.host, ...headers } }, res => {
let text = '';
res.on('data', chunk => text += chunk);
res.on('end', () => resolve({ status: res.statusCode, text, value: JSON.parse(text) }));
});
req.on('error', reject); req.end();
});
}
test('C2-09a metadata uses the human listener bus, projects labels only and refuses unauthenticated reads', async t => {
let calls = 0;
const server = await startServer({ port: 0, preview, bus: { instances: async () => { calls++; return snapshot; } } });
t.after(() => close(server));
const refused = await get(server);
assert.equal(refused.status, 401);
assert.equal(calls, 0);
const answer = await get(server, { headers: { 'x-user': 'jason' } });
assert.equal(answer.status, 200);
assert.deepEqual(answer.value.rows, [{ instance: 'assistant', name: 'assistant' }, { instance: 'bean', name: 'Bean' }, { instance: 'chet', name: 'Bean' }]);
for (const row of answer.value.rows) assert.deepEqual(Object.keys(row).sort(), ['instance', 'name']);
assert.doesNotMatch(answer.text, /private|Hidden PM/);
assert.equal(calls, 1);
for (const options of [{ route: path + '?extra=1' }, { method: 'POST' }]) {
const result = await get(server, { ...options, headers: { 'x-user': 'jason' } });
assert.ok([400, 405].includes(result.status));
}
assert.equal(calls, 1, 'invalid metadata requests never call a bus');
});
test('C2-09a metadata uses the private tester listener own bus and no human transport', async t => {
const root = mkdtempSync(join(tmpdir(), 'chat219-')); t.after(() => rmSync(root, { recursive: true, force: true }));
let reads = 0;
const server = await startTesterSocket({ dataRoot: root, board: 'http://127.0.0.1:9',
bus: { instances: async () => { reads++; return { instances: { quill: { definition: 'proofreader', vars: { 'persona.name': 'Quill' } } } }; } } });
t.after(() => close(server));
const answer = await get(server);
assert.equal(answer.status, 200);
assert.deepEqual(answer.value.rows, [{ instance: 'quill', name: 'Quill' }]);
assert.equal(reads, 1);
});
test('C2-09a metadata refuses missing or malformed reads and never echoes untrusted fields', async t => {
for (const bus of [null, { instances: async () => ({ instances: [] }) }, { instances: async () => { throw new Error('private-path'); } }]) {
const server = await startServer({ port: 0, preview, bus }); t.after(() => close(server));
const answer = await get(server, { headers: { 'x-user': 'jason' } });
assert.ok(answer.status >= 400);
assert.doesNotMatch(answer.text, /private-path/);
}
});
test('C2-09a metadata stays behind the SSO session check and a GET never refreshes session activity', async t => {
let session = null, reads = 0;
const touches = [];
const sso = { publicHost: 'sso-chat.example', publicOrigin: 'https://sso-chat.example', formTargets: () => [],
session: (_req, { touch }) => { touches.push(touch); return session; } };
const server = await startServer({ port: 0, sso, bus: { instances: async () => { reads++; return snapshot; } } });
t.after(() => close(server));
const headers = { host: sso.publicHost };
assert.equal((await get(server, { headers })).status, 401);
assert.equal(reads, 0);
session = { name: 'jason' };
assert.equal((await get(server, { headers })).status, 200);
assert.equal(reads, 1);
assert.deepEqual(touches, [false, false]);
});