Files
stack/packages/bus/tests/project-remotes.test.mjs

53 lines
4.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { Store } from '../src/store.mjs';
import { Broker } from '../src/broker.mjs';
test('only the owner Console reads and writes', t => {
const root = mkdtempSync(join(tmpdir(), 'project-remote-bus-')), store = new Store(root), calls = [];
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: { pm: { definition: 'pm', authority: { withinRole: [], crossRole: [] } }, cto: { definition: 'cto', authority: { withinRole: [], crossRole: [] } } },
projects: { stack: { root } } };
const broker = new Broker({ store, businesses: { demo: business }, organization: (...args) => { calls.push(args); return { project: 'stack', revision: 'a'.repeat(64), remote: null, firstSaveAllowed: true }; } });
const owner = broker.bindConsole({ business: 'demo', human: 'jason', subject: { provider: 'oidc', iss: 'https://id.test', sub: 'owner' } });
const cli = broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true });
const reader = broker.bindReader({ business: 'demo' }), tester = broker.bindTester({ business: 'demo' });
const get = { verb: 'project.remote.get.console', args: { project: 'stack' } };
assert.equal(broker.request(owner, get).project, 'stack');
for (const cap of [cli, reader, tester]) assert.throws(() => broker.request(cap, get), /human-required|read-only|tester-refused/);
assert.throws(() => broker.request(owner, { ...get, args: { project: 'stack', business: 'other' } }), /invalid-request/);
assert.equal(broker.request(owner, { verb: 'project.remote.set.console', args: { project: 'stack', revision: 'a'.repeat(64), remote: 'https://git.example/a/b' } }).project, 'stack');
assert.equal(calls.length, 2);
assert.throws(() => broker.bindConsole({ business: 'demo', human: 'other', subject: { provider: 'oidc', iss: 'https://id.test', sub: 'owner' } }), /human-required/);
});
test('socket refuses tester, viewer, collaborator, agent and CLI human for both remote verbs', async t => {
const { serve } = await import('../src/server.mjs');
const { Client } = await import('../src/client.mjs');
const root = mkdtempSync(join(tmpdir(), 'remote-socket-')), store = new Store(root);
const access = { accessVersion: 1, revision: 1, users: [], grants: [
{ id: 'g-1', user: 'viewer', business: 'demo', projects: ['*'], instances: ['pm'], level: 'viewer' },
{ id: 'g-2', user: 'collab', business: 'demo', projects: ['*'], instances: ['pm'], level: 'collaborator' },
] };
const business = { id: 'demo', human: 'jason', vars: { 'bus.tester': true }, arbiters: { technical: 'pm', delivery: 'pm' },
roles: { pm: { definition: 'pm', authority: { withinRole: [], crossRole: [] } } }, projects: { stack: { root } } };
const broker = new Broker({ store, businesses: { demo: business }, access: () => access, organization: () => { throw Error('unexpected writer'); } });
const caps = {
human: broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }),
tester: broker.bindTester({ business: 'demo' }),
viewer: broker.bindUser({ business: 'demo', user: 'viewer' }),
collaborator: broker.bindUser({ business: 'demo', user: 'collab' }),
agent: broker.bindLaunch({ business: 'demo', role: 'pm', run: 'remote-test', harness: 'pi' }),
};
const path = join(root, 'bus.sock'), server = await serve({ broker, path });
t.after(async () => { await server.close(); store.close(); rmSync(root, { recursive: true, force: true }); });
for (const [who, cap] of Object.entries(caps)) for (const [verb, args] of [
['project.remote.get.console', { project: 'stack' }],
['project.remote.set.console', { project: 'stack', revision: 'a'.repeat(64), remote: 'https://git.example/a/b' }],
]) await assert.rejects(new Client({ path, cap }).call(verb, args), e => ['human-required', 'read-only', 'tester-refused', 'access-refused'].includes(e.code), `${who} ${verb}`);
});