Files
stack/packages/webui/tests/helpers/archive-origin.mjs

81 lines
5.6 KiB
JavaScript

// #797 / #1839 Task 3: the owner's Console controls over the request journal, and the PM's question.
// The webui side is packages/webui/tests/request-http.test.mjs; this file drives the broker verbs
// those routes call, with a real capability for every caller.
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { Store } from '../../../bus/src/store.mjs';
import { Broker } from '../../../bus/src/broker.mjs';
import { createRequestStore } from '../../../bus/src/request-store.mjs';
import { createRequestLifecycle } from '../../../bus/src/request-lifecycle.mjs';
const hash = x => createHash('sha256').update(x).digest('hex');
const intent = 'Build a single-file timer.\nKeep these exact bytes.\n';
const SUBJECT = { provider: 'oidc', iss: 'https://id.test', sub: 'jason' };
function setup(t, overrides = {}) {
const root = mkdtempSync(join(tmpdir(), 'request-console-'));
const store = new Store(root);
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
const business = { id: 'demo', human: 'jason', vars: { 'bus.tester': true }, arbiters: { technical: 'cto', delivery: 'pm' },
roles: Object.fromEntries(['pm', 'cto', 'coder', 'otherpm', 'chat'].map(key => [key, {
definition: key === 'otherpm' ? 'pm' : key === 'chat' ? 'assistant' : key, authority: { withinRole: ['message.send'], crossRole: [] },
}])), projects: { factory: { root: join(root, 'factory') }, old: { root: join(root, 'old'), repo: join(root, 'old-code') },
library: { kind: 'library', root: join(root, 'library') }, unknown: { root: join(root, 'unknown') } } };
const plans = (state, args, source, existing) => ['autonomous', 'guided'].map(mode => ({
version: 1, business: state.business, request: state.request, project: args.project, name: args.name, chat_project: null, mode,
organization_revision: 'org-1', source: { project: args.source_project, commit: 'a'.repeat(40), remote: null, forks: [] },
code_root: existing ? business.projects[args.project].root : join(root, 'products/demo', args.project, 'code'),
records_root: existing ? business.projects[args.project].repo : join(root, 'projects/demo', args.project), tracker: { root: 1, project: args.tracker_project },
staff: { pm: state.pm, technical_reviewer: 'cto', coder: 'coder', reviewer: 'cto', chief: 'cto' },
intent: { text: source.text, sha256: hash(source.text), source_message: source.source_message, generated: false },
project_vars: {}, existing,
}));
const host = {
listProjects: () => [
{ business: 'demo', project: 'old', name: 'Old', tracker: { root: 1, project: 2 } },
{ business: 'demo', project: 'library', name: 'Library', tracker: { root: 1, project: 2 } },
{ business: 'foreign', project: 'old', name: 'Foreign', tracker: { root: 1, project: 2 } },
{ business: 'demo', project: 'unknown', name: 'Unknown', tracker: null },
],
preparePlans: (state, args, source) => plans(state, args, source, false),
prepareExisting: (state, args, source) => plans(state, args, source, true)[0],
admitPlan: () => ({ ready: true }),
};
let lifecycle, journal;
const b = new Broker({ store, businesses: { demo: business }, requestLifecycle: services => {
journal = createRequestStore(store); lifecycle = createRequestLifecycle({ journal, services: { ...host, ...overrides, ...services } }); return lifecycle;
} });
const caps = { human: b.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }),
reader: b.bindReader({ business: 'demo' }), tester: b.bindTester({ business: 'demo' }),
console: b.bindConsole({ business: 'demo', human: 'jason', subject: SUBJECT }) };
for (const role of ['pm', 'cto', 'coder', 'otherpm']) {
caps[role] = b.bindLaunch({ business: 'demo', role, run: role + '-run', harness: 'pi' });
b.request(caps[role], { verb: 'role.claim' });
}
const call = async (who, verb, args = {}) => b.request(caps[who], { verb, args });
const originProject=b.request(caps.human,{verb:'project.create',args:{name:'Origin chat'}});
const originThread=b.request(caps.human,{verb:'thread.create',args:{project:originProject.id,name:'pm',instance:'chat'}});
const source=b.request(caps.human,{verb:'thread.send',args:{thread:originThread.id,body:intent}});
caps.chat=b.bindLaunch({business:'demo',role:'chat',run:'chat-run',harness:'pi',thread:originThread.id});
b.request(caps.chat,{verb:'role.claim'});
b.request(caps.chat,{verb:'message.send',args:{to:'pm',body:intent,in_reply_to:source.id}});
const propose = state => ({ request: source.request, revision: state.revision, op: 'plan-1', source_project: 'factory', project: 'timer', name: 'Timer', tracker_project: 2 });
const existing = (state, op) => ({ request: state.request, revision: state.revision, op, source_project: 'old', project: 'old', name: 'Old', tracker_project: 2 });
return { root, b, store, call, source, propose, existing, business, get journal() { return journal; }, get lifecycle() { return lifecycle; } };
}
const n = (x, sql, ...p) => x.store.get(sql, ...p).n;
const get = (x, who = 'console') => x.call(who, 'request.get.console', { request: x.source.request });
async function atSelection(t) {
const x = setup(t), opened = await x.call('pm', 'request.open', { request: x.source.request });
x.state = await x.call('pm', 'request.propose', x.propose(opened));
return x;
}
async function atPicker(t) {
const x = await atSelection(t);
await x.call('human', 'decision.resolve', { id: x.state.selection.decision, choice: 'existing' });
x.state = await x.lifecycle.resume('demo', x.source.request);
return x;
}
export { atPicker };