87 lines
5.4 KiB
JavaScript
87 lines
5.4 KiB
JavaScript
// RR (#1873): the file writer refuses arbiters without their domain's right.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { organizationWriter } from '../src/organization.mjs';
|
|
import { loadRole, validateBusinessDocument } from '../src/index.mjs';
|
|
import { businessDoc, REPO_ROLES, rolesCopy, tmp, writeJson } from './helpers.mjs';
|
|
|
|
for (const [domain, instance, definition] of [['technical', 'cto', 'cto'], ['delivery', 'pm', 'pm']]) {
|
|
test(`RR: naming ${instance} without decision.resolve.${domain} refuses when written`, t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const rolesDir = rolesCopy(root), file = join(root, 'businesses/acme.json');
|
|
writeJson(file, businessDoc(root));
|
|
const writer = organizationWriter({ dir: root, rolesDir });
|
|
const { document, revision } = writer.read('acme'), before = readFileSync(file, 'utf8');
|
|
const roleFile = join(rolesDir, `${definition}.json`), role = JSON.parse(readFileSync(roleFile, 'utf8'));
|
|
role.authority.withinRole = role.authority.withinRole.filter(a => a !== `decision.resolve.${domain}`);
|
|
writeJson(roleFile, role);
|
|
assert.throws(() => writer.write('acme', document, { revision, actor: 'jason' }), error => {
|
|
assert.match(error.message, new RegExp(`arbiters\\.${domain}.*${instance}.*role definition ${definition}.*decision\\.resolve\\.${domain}`));
|
|
return true;
|
|
});
|
|
assert.equal(readFileSync(file, 'utf8'), before);
|
|
});
|
|
}
|
|
|
|
test('RR: a coder named as arbiter refuses when written', t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const file = join(root, 'businesses/acme.json'); writeJson(file, businessDoc(root));
|
|
const writer = organizationWriter({ dir: root, rolesDir: REPO_ROLES });
|
|
const { document, revision } = writer.read('acme'), before = readFileSync(file, 'utf8');
|
|
document.arbiters.delivery = 'coder';
|
|
assert.throws(() => writer.write('acme', document, { revision, actor: 'jason' }), /arbiters\.delivery.*coder.*role definition coder.*decision\.resolve\.delivery/);
|
|
assert.equal(readFileSync(file, 'utf8'), before);
|
|
});
|
|
|
|
test('RR: a cross-role resolve grant cannot qualify an arbiter', t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const rolesDir = rolesCopy(root), roleFile = join(rolesDir, 'cto.json');
|
|
const role = JSON.parse(readFileSync(roleFile, 'utf8'));
|
|
role.authority.withinRole = role.authority.withinRole.filter(a => a !== 'decision.resolve.technical');
|
|
role.authority.crossRole.push('decision.resolve.technical'); writeJson(roleFile, role);
|
|
assert.throws(() => validateBusinessDocument(businessDoc(root), join(root, 'businesses/acme.json'), { rolesDir }), /cto.*decision\.resolve\.technical/);
|
|
});
|
|
|
|
test('RR: a version 2 contract without its Authority section refuses', t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const rolesDir = rolesCopy(root);
|
|
writeFileSync(join(rolesDir, 'coder.md'), '# Role contract: coder\n\n## Duties\nRead only.\n');
|
|
assert.throws(() => loadRole(rolesDir, 'coder'), /coder.*## Authority/);
|
|
});
|
|
|
|
for (const [domain, instance] of [['technical', 'cto'], ['delivery', 'pm']]) {
|
|
for (const layer of ['agent', 'business', 'intersection']) {
|
|
test(`RR: ${layer} limits removing ${instance}'s ${domain} resolve right refuse when written`, t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const file = join(root, 'businesses/acme.json'); writeJson(file, businessDoc(root));
|
|
const writer = organizationWriter({ dir: root, rolesDir: REPO_ROLES });
|
|
const { document, revision } = writer.read('acme'), before = readFileSync(file, 'utf8');
|
|
const right = `decision.resolve.${domain}`;
|
|
const rights = ['message.send', 'decision.resolve.technical', 'decision.resolve.delivery'];
|
|
if (layer === 'agent') document.roles[instance].vars['limits.authority'] = ['message.send'];
|
|
else document.vars['limits.authority'] = rights.filter(value => value !== right);
|
|
if (layer === 'intersection') document.roles[instance].vars['limits.authority'] = rights;
|
|
assert.throws(() => writer.write('acme', document, { revision, actor: 'jason' }), error => {
|
|
assert.match(error.message, new RegExp(`arbiters\\.${domain}.*${instance}.*role definition ${instance}.*decision\\.resolve\\.${domain}`));
|
|
return true;
|
|
});
|
|
assert.equal(readFileSync(file, 'utf8'), before);
|
|
});
|
|
}
|
|
}
|
|
|
|
test('RR: narrowed arbiters retaining their domain rights can be written', t => {
|
|
const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const file = join(root, 'businesses/acme.json'); writeJson(file, businessDoc(root));
|
|
const writer = organizationWriter({ dir: root, rolesDir: REPO_ROLES });
|
|
const { document, revision } = writer.read('acme');
|
|
document.vars['limits.authority'] = ['message.send', 'decision.resolve.technical', 'decision.resolve.delivery'];
|
|
for (const [domain, instance] of [['technical', 'cto'], ['delivery', 'pm']]) {
|
|
document.roles[instance].vars['limits.authority'] = ['message.send', `decision.resolve.${domain}`];
|
|
}
|
|
writer.write('acme', document, { revision, actor: 'jason' });
|
|
assert.deepEqual(writer.read('acme').document.arbiters, document.arbiters);
|
|
});
|