Skip to content

Commit cd8f555

Browse files
committed
test(plugin-approvals): assert the consumers' envelopes before the producer's write shape
Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s Co-authored-by: Claude <noreply@anthropic.com>
1 parent 668785f commit cd8f555

1 file changed

Lines changed: 20 additions & 14 deletions

File tree

‎packages/plugins/plugin-approvals/src/status-mirror-envelope.integration.test.ts‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -272,15 +272,16 @@ describe("the status mirror hands a runAs:'user' cascade the person's own reques
272272
expect('permissions' in write, 'the write carries no permission sets of its own').toBe(false);
273273
}
274274

275+
/** The consumers first (what an author's automation sees), then the producer fact that explains it. */
275276
async function expectPersonInOrg(seen: { write: any; hook: any; flow: any }, userId: string, org: string) {
276-
expectAttributedWrite(seen.write, userId, org);
277277
const expected = await requestEnvelope(userId, org);
278278
expect(seen.hook.userId).toBe(userId);
279279
expect(seen.hook.isSystem).toBe(false);
280280
expect(envelopeOf(seen.hook), "the runAs:'user' hook").toEqual(expected);
281281
expect(seen.flow.userId).toBe(userId);
282282
expect(envelopeOf(seen.flow), "the runAs:'user' record-change flow").toEqual(expected);
283-
return expected;
283+
expectAttributedWrite(seen.write, userId, org);
284+
return seen;
284285
}
285286

286287
it('open, through the approval node: the submitter in the record organisation, and no role held only in another organisation', async () => {
@@ -289,38 +290,42 @@ describe("the status mirror hands a runAs:'user' cascade the person's own reques
289290
const requestId = await openNode('d_open', 'submitter', ['approver']);
290291
const [request] = await ql.find('sys_approval_request', { where: { id: requestId }, context: SYS });
291292
expect(request.organization_id, 'fixture premise: the request is stamped with the record organisation').toBe(ORG_A);
292-
const expected = await expectPersonInOrg(await cascade(before), 'submitter', ORG_A);
293+
const { hook, flow } = await expectPersonInOrg(await cascade(before), 'submitter', ORG_A);
293294
// The person whose memberships differ: what A grants, never what B does.
294-
expect(expected.positions).not.toContain(ROLE_ONLY_IN_B);
295-
expect(expected.org_user_ids).toEqual(['approver', 'peer', 'submitter']);
295+
expect(hook.positions).not.toContain(ROLE_ONLY_IN_B);
296+
expect(flow.positions).not.toContain(ROLE_ONLY_IN_B);
297+
expect(sorted(hook.org_user_ids)).toEqual(['approver', 'peer', 'submitter']);
296298
});
297299

298300
it('decide approve: the approver carries the grant they hold beyond the baseline, and their organisation peers', async () => {
299301
await seedDeal('d_approve');
300302
const requestId = await openNode('d_approve', 'submitter', ['approver']);
301303
const before = mark();
302304
await svc.decide(requestId, { decision: 'approve', actorId: 'approver' } as any, asPerson('approver'));
303-
const expected = await expectPersonInOrg(await cascade(before), 'approver', ORG_A);
304-
expect(expected.permissions).toContain(EXTRA_SET);
305-
expect(expected.org_user_ids).toEqual(['approver', 'peer', 'submitter']);
305+
const { hook, flow } = await expectPersonInOrg(await cascade(before), 'approver', ORG_A);
306+
// The grant beyond the baseline is the approver's, so the automation that runs as them holds it.
307+
expect(hook.permissions).toContain(EXTRA_SET);
308+
expect(flow.permissions).toContain(EXTRA_SET);
309+
expect(sorted(hook.org_user_ids)).toEqual(['approver', 'peer', 'submitter']);
306310
});
307311

308312
it('decide reject: the approver, in the record organisation', async () => {
309313
await seedDeal('d_reject');
310314
const requestId = await openNode('d_reject', 'submitter', ['approver']);
311315
const before = mark();
312316
await svc.decide(requestId, { decision: 'reject', actorId: 'approver' } as any, asPerson('approver'));
313-
const expected = await expectPersonInOrg(await cascade(before), 'approver', ORG_A);
314-
expect(expected.permissions).toContain(EXTRA_SET);
317+
const { hook } = await expectPersonInOrg(await cascade(before), 'approver', ORG_A);
318+
expect(hook.permissions).toContain(EXTRA_SET);
315319
});
316320

317321
it('recall: the submitter, in the record organisation', async () => {
318322
await seedDeal('d_recall');
319323
const requestId = await openNode('d_recall', 'submitter', ['approver']);
320324
const before = mark();
321325
await svc.recall(requestId, { actorId: 'submitter' } as any, asPerson('submitter'));
322-
const expected = await expectPersonInOrg(await cascade(before), 'submitter', ORG_A);
323-
expect(expected.positions).not.toContain(ROLE_ONLY_IN_B);
326+
const { hook, flow } = await expectPersonInOrg(await cascade(before), 'submitter', ORG_A);
327+
expect(hook.positions).not.toContain(ROLE_ONLY_IN_B);
328+
expect(flow.positions).not.toContain(ROLE_ONLY_IN_B);
324329
});
325330

326331
it('send back for revision: the approver, in the record organisation', async () => {
@@ -357,9 +362,10 @@ describe("the status mirror hands a runAs:'user' cascade the person's own reques
357362
const requestId = await openNode('d_xorg', 'submitter', ['xorg']);
358363
const before = mark();
359364
await svc.decide(requestId, { decision: 'approve', actorId: 'xorg' } as any, asPerson('xorg'));
360-
const expected = await expectPersonInOrg(await cascade(before), 'xorg', ORG_A);
365+
const { hook, flow } = await expectPersonInOrg(await cascade(before), 'xorg', ORG_A);
361366
// The record organisation's members (and the approver), never their own organisation's.
362-
expect(expected.org_user_ids).toEqual(['approver', 'peer', 'submitter', 'xorg']);
367+
expect(sorted(hook.org_user_ids)).toEqual(['approver', 'peer', 'submitter', 'xorg']);
368+
expect(sorted(flow.org_user_ids)).toEqual(['approver', 'peer', 'submitter', 'xorg']);
363369
});
364370

365371
it("the organisation is the record's live one: a record re-parented while its request was pending", async () => {

0 commit comments

Comments
 (0)