Skip to content

Commit c1f0005

Browse files
committed
test(plugin-security): the grant uniqueness pins re-keyed to the plain organization key part (ADR-0131 D1)
Claude-Session: https://claude.ai/code/session_01NnXVu3vF2TKFLMi5Me4CUd Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3bb7ad6 commit c1f0005

1 file changed

Lines changed: 20 additions & 21 deletions

File tree

‎packages/plugins/plugin-security/src/grant-permission-set-name.test.ts‎

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
* as an undeclared field; a non-system write naming a set the security
88
* catalog does not hold is refused, and a grant that carries no name is
99
* refused as `required`. One grant per user, set and organization: the
10-
* declared unique index (`unique: 'organization'`, NULL-safe on the
11-
* organization) refuses a second, platform-wide grants included, on every
12-
* write path, so a revoke leaves no grant of that set in force.
10+
* declared unique index (`unique: 'organization'`) refuses a second on every
11+
* write path, so a revoke leaves no grant of that set in force. Its
12+
* organization key part is the plain column since ADR-0131 D1 (the tenant
13+
* column is NOT NULL), so on the pre-18 nullable column this harness models a
14+
* grant with no organization is exempt, like a NULL in any key part.
1315
*
1416
* Measured on a REAL `ObjectQL` engine over a real SQL driver with the REAL
1517
* `SecurityPlugin` started on it the way a kernel composition does, because
@@ -309,42 +311,39 @@ describe('one grant per user, set and organization — the declared unique \'org
309311
expect(await grantsOfSet(h, 'u_d1', 'qa_alpha')).toEqual([]);
310312
});
311313

312-
it('NEGATIVE — a second platform-wide grant (organization NULL) is refused 409 by the index itself, for every writer; the revoke leaves none in force', async () => {
314+
it('a second grant with no organization is NOT refused by the index on a pre-18 nullable column: the plain organization key part exempts it (ADR-0131 D1)', async () => {
313315
const h = await boot();
314316
await h.engine.insert('sys_user_permission_set', { id: 'ups_p1', user_id: 'u_p1', permission_set: 'qa_alpha', organization_id: null }, { context: ADMIN } as any);
315-
for (const context of [ADMIN, SYS]) {
316-
const err = await refusalOf(() => h.engine.insert(
317-
'sys_user_permission_set', { user_id: 'u_p1', permission_set: 'qa_alpha' }, { context } as any,
318-
));
319-
expect(duplicateShape(err)).toEqual(DUPLICATE);
320-
}
321-
expect((await grantsOfSet(h, 'u_p1', 'qa_alpha')).map((g) => g.id)).toEqual(['ups_p1']);
322-
323-
await h.engine.delete('sys_user_permission_set', { where: { id: 'ups_p1' }, context: ADMIN } as any);
324-
expect(await grantsOfSet(h, 'u_p1', 'qa_alpha')).toEqual([]);
317+
await knexOf(h)('sys_user_permission_set').insert({ id: 'ups_p2', user_id: 'u_p1', permission_set: 'qa_alpha', organization_id: null });
318+
expect((await grantsOfSet(h, 'u_p1', 'qa_alpha')).map((g) => g.id).sort()).toEqual(['ups_p1', 'ups_p2']);
325319
});
326320

327-
it('NEGATIVE — the index refuses a platform-wide duplicate on paths no hook sees: a two-row batch insert stores nothing, and raw SQL beneath the engine is refused', async () => {
321+
it('NEGATIVE — the index refuses an organization duplicate on paths no hook sees: a two-row batch insert stores nothing, and raw SQL beneath the engine is refused', async () => {
328322
const h = await boot();
323+
await h.engine.insert('sys_member', { id: 'mem_b1', user_id: 'u_b1', organization_id: ORG, role: 'member' }, { context: SYS } as any);
329324
const err = await refusalOf(() => h.engine.insert(
330325
'sys_user_permission_set',
331-
[{ user_id: 'u_b1', permission_set: 'qa_alpha' }, { user_id: 'u_b1', permission_set: 'qa_alpha' }],
326+
[
327+
{ user_id: 'u_b1', permission_set: 'qa_alpha', organization_id: ORG },
328+
{ user_id: 'u_b1', permission_set: 'qa_alpha', organization_id: ORG },
329+
],
332330
{ context: SYS } as any,
333331
));
334332
expect(duplicateShape(err)).toEqual(DUPLICATE);
335333
expect(await grantsOf(h, 'u_b1')).toEqual([]);
336334

337-
await knexOf(h)('sys_user_permission_set').insert({ id: 'raw_1', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: null });
335+
await knexOf(h)('sys_user_permission_set').insert({ id: 'raw_1', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: ORG });
338336
await expect(
339-
knexOf(h)('sys_user_permission_set').insert({ id: 'raw_2', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: null }),
337+
knexOf(h)('sys_user_permission_set').insert({ id: 'raw_2', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: ORG }),
340338
).rejects.toThrow(/UNIQUE constraint failed/);
341339
expect((await grantsOf(h, 'u_r1')).map((g) => g.id)).toEqual(['raw_1']);
342340
});
343341

344-
it('NEGATIVE — an update that makes a platform-wide duplicate (re-naming the set) is refused 409 and the row is untouched', async () => {
342+
it('NEGATIVE — an update that makes an organization duplicate (re-naming the set) is refused 409 and the row is untouched', async () => {
345343
const h = await boot();
346-
await h.engine.insert('sys_user_permission_set', { id: 'ups_u1', user_id: 'u_u1', permission_set: 'qa_alpha' }, { context: ADMIN } as any);
347-
await h.engine.insert('sys_user_permission_set', { id: 'ups_u2', user_id: 'u_u1', permission_set: 'qa_beta' }, { context: ADMIN } as any);
344+
await h.engine.insert('sys_member', { id: 'mem_u1', user_id: 'u_u1', organization_id: ORG, role: 'member' }, { context: SYS } as any);
345+
await h.engine.insert('sys_user_permission_set', { id: 'ups_u1', user_id: 'u_u1', permission_set: 'qa_alpha', organization_id: ORG }, { context: ADMIN } as any);
346+
await h.engine.insert('sys_user_permission_set', { id: 'ups_u2', user_id: 'u_u1', permission_set: 'qa_beta', organization_id: ORG }, { context: ADMIN } as any);
348347
const err = await refusalOf(() => h.engine.update(
349348
'sys_user_permission_set', { id: 'ups_u2', permission_set: 'qa_alpha' }, { context: ADMIN } as any,
350349
));

0 commit comments

Comments
 (0)