/** * build-spec — deterministic rowScope derivation from the entity data scope. * Invariant under test: a scoped entity (own/assigned lists) can NEVER be * registered in global search without the mirroring RestrictTo — search must * never reveal a row the list hides. */ import { describe, expect, it } from 'vitest'; import { buildExtensionSearchSpec, type BuildExtensionSearchSpecInput } from '../build-spec.js'; function input(over: Partial = {}): BuildExtensionSearchSpecInput { return { appCode: 'Test', projectPath: '/tmp/p', entityNamespace: 'Test.Domain.Entities', sections: [ { code: 'liste', moduleCode: 'taches', appCode: 'todo', label: 'Tâches', route: '/todo/taches/liste' }, ], permissions: ['todo.taches.liste.read'], listScreens: [{ entityName: 'Task', sectionCode: 'liste' }], ...over, }; } describe('scaffold-extension-search / build-spec — rowScope derivation', () => { it('derives the RestrictTo from an own data scope (bypass = the sibling .read.all)', () => { const { spec, warnings } = buildExtensionSearchSpec(input({ dataScopes: { Task: { mode: 'own', ownerProperty: 'OwnerUserId', assignedProperty: 'AssignedToUserId' } }, })); expect(spec.entities[0].rowScope).toEqual({ bypassPermission: 'todo.taches.liste.read.all', ownerProperty: 'OwnerUserId', }); expect(warnings.some(w => w.includes('own-assigned'))).toBe(false); }); it('assigned mode mirrors the assigned column', () => { const { spec } = buildExtensionSearchSpec(input({ dataScopes: { Task: { mode: 'assigned', ownerProperty: 'OwnerUserId', assignedProperty: 'AssignedToUserId' } }, })); expect(spec.entities[0].rowScope!.ownerProperty).toBe('AssignedToUserId'); }); it('own-assigned mirrors the owner column and WARNS (search may show fewer rows, never more)', () => { const { spec, warnings } = buildExtensionSearchSpec(input({ dataScopes: { Task: { mode: 'own-assigned', ownerProperty: 'OwnerUserId', assignedProperty: 'AssignedToUserId' } }, })); expect(spec.entities[0].rowScope!.ownerProperty).toBe('OwnerUserId'); expect(warnings.some(w => w.includes('own-assigned'))).toBe(true); }); it('an explicit screen.rowScope always wins over the derived one', () => { const { spec } = buildExtensionSearchSpec(input({ listScreens: [{ entityName: 'Task', sectionCode: 'liste', rowScope: { bypassPermission: 'todo.taches.liste.assign', ownerProperty: 'HandledByUserId' }, }], dataScopes: { Task: { mode: 'own', ownerProperty: 'OwnerUserId', assignedProperty: 'AssignedToUserId' } }, })); expect(spec.entities[0].rowScope).toEqual({ bypassPermission: 'todo.taches.liste.assign', ownerProperty: 'HandledByUserId', }); }); it('no data scope → no rowScope (unscoped entities stay unrestricted)', () => { const { spec } = buildExtensionSearchSpec(input()); expect(spec.entities[0].rowScope).toBeUndefined(); }); });