import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import * as bus from '../src/broker.mjs'; import { Store } from '../src/store.mjs'; function fixture(t) { const root = mkdtempSync(join(tmpdir(), 'pt-')); const store = new Store(root); t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); }); const broker = new bus.Broker({ store, businesses: { acme: { id: 'acme', human: 'jason', roles: { pm: { authority: { withinRole: [], crossRole: [] } }, cto: { authority: { withinRole: [], crossRole: [] } }, }, arbiters: { technical: 'cto', delivery: 'pm' } }, } }); const human = broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true }); return { broker, human }; } test('person task vocabulary pins exactly the five frozen store verbs', () => { assert.deepEqual(bus.PERSON_TASK_VERBS, [ 'tasks.create', 'tasks.edit', 'tasks.archive', 'tasks.unarchive', 'tasks.delete', ]); assert.ok(Object.isFrozen(bus.PERSON_TASK_VERBS)); }); test('every person task handler is declared in the vocabulary, even if the dispatch guard regresses', () => { const source = readFileSync(new URL('../src/broker.mjs', import.meta.url), 'utf8'); const handlers = [...source.matchAll(/^\s*case\s+(['"])(tasks\.[^'"]+)\1\s*:/gm)].map(match => match[2]); assert.deepEqual(handlers.sort(), [...bus.PERSON_TASK_VERBS].sort()); }); test('every listed person task verb reaches its handler, while an unlisted verb is unknown', t => { const { broker, human } = fixture(t); assert.ok(Array.isArray(bus.PERSON_TASK_VERBS)); for (const verb of bus.PERSON_TASK_VERBS) { // Missing required fields reaches the handler's field validation, not unknown-verb. assert.throws(() => broker.request(human, { verb, args: {} }), { code: 'invalid-request' }, verb); } assert.throws(() => broker.request(human, { verb: 'tasks.foo', args: {} }), { code: 'unknown-verb' }); }); test('unknown person task verbs retain common argument and reader refusal precedence', t => { const { broker, human } = fixture(t); assert.throws(() => broker.request(human, { verb: 'tasks.foo', args: [] }), { code: 'invalid-request' }); const reader = broker.bindReader({ business: 'acme' }); assert.throws(() => broker.request(reader, { verb: 'tasks.foo' }), { code: 'read-only' }); for (const verb of ['tasks.foo', 'tasks.create']) { assert.throws(() => broker.request(reader, { verb }), { code: 'read-only' }, verb); } });