Files
stack/packages/webui/tests/agent-levels-route.test.mjs

94 lines
7.6 KiB
JavaScript

// #1873 Phase A slice A1: GET /api/agents/levels, the read behind the Agents grid's level and source
// columns. Owner only, GET only; a bad root file, organization or project hides only itself, and
// no body carries a host path.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { fileURLToPath } from 'node:url';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { startServer } from '../src/serve.mjs';
import { createDevOwner } from '../src/dev-owner.mjs';
import { agentLevelsHTTP } from '../src/agent-levels-read.mjs';
import { checkRootAgents, loadRootAgents } from '../../organization/src/index.mjs';
import { close } from './fixture.mjs';
const rolesDir = fileURLToPath(new URL('../../../roles', import.meta.url));
const CTX = { configDir: '/home/owner/.config/mosaic-test', dataRoot: '/home/owner/.mosaic-test', instance: 'test' };
const DOCS = {
acme: { roles: { chief: { definition: 'chief-of-staff' }, writer: { definition: 'copywriter', vars: { model: 'sonnet-5.5' } } },
projects: { web: { root: '/srv/secret/web', agents: { checker: { definition: 'fact-checker' }, writer: { vars: { 'limits.network': 'none' } } } },
broken: { root: '/srv/secret/broken', agents: { ghost: { exclude: true } } } } },
bad: null,
};
const members = async () => [{ id: 'acme', doc: DOCS.acme }, { id: 'bad', invalid: true, reason: 'organization file is not valid JSON' }];
const root = () => ({ state: 'ok', ...checkRootAgents({ agentsVersion: 1, agents: { assistant: { definition: 'assistant' }, reviewer: { definition: 'reviewer', excludeOrgs: ['bad'] } } }, { rolesDir }) });
function call(options, method = 'GET') {
const res = { status: 0, body: null, writeHead(status) { this.status = status; }, end(text) { this.body = JSON.parse(text); } };
return agentLevelsHTTP({ method }, res, options).then(() => res);
}
test('the route refuses a write, a missing read, a non-owner and an unreadable registry', async () => {
const levels = { loadRoot: root, rolesDir, ctx: CTX };
assert.equal((await call({ owner: true, session: {}, organizations: { members }, levels }, 'POST')).status, 405);
assert.equal((await call({ owner: true, session: {}, organizations: null, levels })).status, 404);
assert.equal((await call({ owner: true, session: {}, organizations: { members }, levels: null })).status, 404);
const denied = await call({ owner: false, session: {}, organizations: { members }, levels });
assert.equal(denied.status, 403); assert.equal(denied.body.error, 'owner-only');
const down = await call({ owner: true, session: {}, organizations: { members: async () => { throw new Error('registry down'); } }, levels, log: () => {} });
assert.equal(down.status, 503); assert.doesNotMatch(JSON.stringify(down.body), /registry down/);
});
test('the owner reads every level; a bad project or organization hides only itself; no host path', async t => {
const levels = { loadRoot: root, rolesDir, ctx: CTX };
const server = await startServer({ port: 0, sso: createDevOwner(), organizations: { list: async () => [], resolve: async () => null, members }, agentLevels: levels, log: () => {} });
t.after(() => close(server));
const response = await fetch(`http://127.0.0.1:${server.address().port}/api/agents/levels`);
assert.equal(response.status, 200);
const text = await response.text(), body = JSON.parse(text);
assert.equal(body.root.state, 'ok'); assert.equal(body.root.assistant, 'assistant');
assert.deepEqual(body.root.agents.map(a => [a.name, a.excludeOrgs]), [['assistant', []], ['reviewer', ['bad']]]);
const acme = body.orgs.find(o => o.id === 'acme');
assert.equal(acme.state, 'ok');
assert.deepEqual(acme.base.agents.map(a => `${a.level}:${a.name}`), ['root:reviewer', 'org:chief', 'org:writer']);
assert.deepEqual(acme.projects.web.agents.map(a => `${a.level}:${a.name}`), ['root:reviewer', 'org:chief', 'org:writer', 'project:checker']);
assert.deepEqual(acme.projects.web.agents.find(a => a.name === 'writer'), { name: 'writer', definition: 'copywriter', level: 'org', model: 'sonnet-5.5', narrowed: ['limits.network'], overrides: [], scope: { kind: 'org', org: 'acme' } });
assert.equal(acme.projects.broken.state, 'invalid'); assert.match(acme.projects.broken.reason, /names no root or organization agent to exclude/);
assert.deepEqual(acme.defined.web.map(e => e.form).sort(), ['define', 'narrow']);
assert.deepEqual(body.orgs.find(o => o.id === 'bad'), { id: 'bad', state: 'invalid', reason: 'organization file is not valid JSON' });
// Only the model and the names of narrowed keys travel; never a root path or another var's value.
assert.doesNotMatch(text, /\/srv\/secret|"none"|"vars"/);
// Signed out: the hook fence refuses before the route runs.
const anon = await startServer({ port: 0, sso: { ...createDevOwner(), session: () => null }, organizations: { list: async () => [], resolve: async () => null, members }, agentLevels: levels, log: () => {} });
t.after(() => close(anon));
assert.notEqual((await fetch(`http://127.0.0.1:${anon.address().port}/api/agents/levels`, { redirect: 'manual' })).status, 200);
});
test('a refused root file hides the root level only, and says why without a host path', async () => {
const levels = { loadRoot: () => loadRootAgents('/nonexistent-dir-for-test', { rolesDir }), rolesDir, ctx: CTX };
// A missing file is no root agents, not a refusal.
const absent = await call({ owner: true, session: {}, organizations: { members }, levels });
assert.deepEqual(absent.body.root, { state: 'absent', agents: [], assistant: null });
const refusing = { ...levels, loadRoot: () => { const e = new Error(`root agent file must belong to uid 1000 (${CTX.configDir}/agents.json)`); e.name = 'BusinessError'; throw e; } };
const res = await call({ owner: true, session: {}, organizations: { members }, levels: refusing });
assert.equal(res.status, 200); assert.equal(res.body.root.state, 'invalid');
assert.match(res.body.root.reason, /must belong to uid 1000/); assert.doesNotMatch(res.body.root.reason, /\/home\/owner/);
assert.equal(res.body.orgs.find(o => o.id === 'acme').state, 'ok');
const crash = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => { throw new Error('raw parser bytes {"x"'); } } });
assert.equal(crash.body.root.reason, 'could not be read');
// A malformed file read through the real loader: the reason names the file, never the parser's quote of its bytes.
const dir = mkdtempSync(join(tmpdir(), 'a'));
writeFileSync(join(dir, 'agents.json'), '{"agentsVersion": 1, "agents": {"x": secret-marker-7731}}', { mode: 0o600 });
const bad = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => loadRootAgents(dir, { rolesDir }) } });
assert.equal(bad.body.root.state, 'invalid');
assert.equal(bad.body.root.reason, 'root agent file is not valid JSON (agents.json)');
assert.doesNotMatch(JSON.stringify(bad.body), /secret-marker|Unexpected token|position/);
// A var value the root file sets wrongly never reaches the body; the key does.
writeFileSync(join(dir, 'agents.json'), JSON.stringify({ agentsVersion: 1, agents: { x: { definition: 'reviewer', vars: { thinking: 'sk-secret-marker-7731' } } } }));
const badVar = await call({ owner: true, session: {}, organizations: { members }, levels: { ...levels, loadRoot: () => loadRootAgents(dir, { rolesDir }) } });
assert.equal(badVar.body.root.state, 'invalid');
assert.match(badVar.body.root.reason, /variable thinking must be one of/);
assert.doesNotMatch(JSON.stringify(badVar.body), /secret-marker/);
});