Files
stack/packages/bus/tests/scoped-push.test.mjs

41 lines
3.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { scopedFixture } from '../../tasks/tests/scoped-fixture.mjs';
const A = 'a'.repeat(40);
const push = (f, task, project) => ({ task_ref: task.task_ref, project, branch: 'feat', ref: 'refs/heads/work', commit: A, previous: null });
test('checkPush binds a product before bytes and recordPush repeats the project check', async t => {
const f = await scopedFixture(t), p = await f.product('timer'), task = await f.create(p.args);
const b = f.bus.broker;
for (const project of ['stack', undefined, 'missing']) assert.throws(() => b.checkPush(f.caps.coder, task.task_ref, project), { code: 'task-project-refused' });
b.checkPush(f.caps.coder, task.task_ref, 'timer');
assert.throws(() => b.recordPush(f.caps.coder, push(f, task, 'stack')), { code: 'task-project-refused' });
b.recordPush(f.caps.coder, push(f, task, 'timer'));
const review = f.call('coder', 'review.request', { task_ref: task.task_ref, commit: A, to: 'reviewer' });
assert.equal(b.reviewFetch(f.caps.reviewer, { task_ref: task.task_ref, request: review.request }).project, 'timer');
await f.restart();
assert.throws(() => f.bus.checkPush(f.caps.coder, task.task_ref, 'stack'), { code: 'task-project-refused' });
f.bus.checkPush(f.caps.coder, task.task_ref, 'timer');
});
test('review, review export and remote export reject old cross-project evidence for a scoped task', async t => {
const f = await scopedFixture(t), p = await f.product('timer'), task = await f.create(p.args), b = f.bus.broker;
// Host-written historical evidence can predate scope admission; consumers must still check it.
const { DatabaseSync } = await import('node:sqlite');
const db = new DatabaseSync(f.f.dataRoot + '/bus/bus.sqlite');
db.prepare("INSERT INTO events(id,at,business,actor_role,actor_run,kind,subject,body) VALUES('old-push',?,'demo','coder','coder-run','action.allowed',?,?)").run(new Date().toISOString(), task.task_ref, JSON.stringify({ action: 'git.push.working', project: 'stack', commit: A, ref: 'refs/heads/work' }));
db.prepare("INSERT INTO events(id,at,business,actor_role,actor_run,kind,subject,body) VALUES('old-review',?,'demo','coder','coder-run','review.requested',?,?)").run(new Date().toISOString(), task.task_ref, JSON.stringify({ to: 'reviewer', project: 'stack', commit: A, ref: 'refs/heads/work' }));
db.close();
assert.throws(() => f.call('coder', 'review.request', { task_ref: task.task_ref, commit: A, to: 'reviewer' }), { code: 'task-project-refused' });
assert.throws(() => b.reviewFetch(f.caps.reviewer, { task_ref: task.task_ref, request: 'old-review' }), { code: 'task-project-refused' });
assert.throws(() => b.checkPublish(f.caps.coder, { task_ref: task.task_ref, project: 'stack', branch: 'feat' }), { code: 'task-project-refused' });
});
test('legacy recorded tasks still admit a push without inventing scope', async t => {
const f = await scopedFixture(t), task = { task_ref: 'vikunja:1/789' }, b = f.bus.broker;
b.recordTask({ cap: f.caps.pm, business: 'demo', events: [{ kind: 'task.created', subject: task.task_ref, body: { request: f.instruction(), requirement: 'REQ-TASK-1' } }] });
b.checkPush(f.caps.coder, task.task_ref);
b.recordPush(f.caps.coder, push(f, task, 'stack'));
assert.equal(f.call('coder', 'trail', { subject: task.task_ref }).find(e => e.body?.action === 'git.push.working').body.scope, undefined);
});