import { describe, expect, it } from 'vitest' import { FLOOR_BY_GRAIN, PERMISSION_FLOOR_ACTIONS, } from '../../../../../lib/permission-actions.js' import { derivePermissionFloor, type FloorSources } from '../derive.js' function sources(tree: Record): FloorSources { return { sectionsOf: () => Object.keys(tree), resourcesOf: (_a, _m, section) => tree[section] ?? [], } } describe('derivePermissionFloor', () => { it('emits app + module + one row per section/resource, floor actions from the lib', () => { const core = derivePermissionFloor('CRM', 'PIPELINE', sources({ opportunites: ['archives'], devis: [] })) expect(core.rows.map((r) => r.nodePath)).toEqual([ 'crm', 'crm.pipeline', 'crm.pipeline.opportunites', 'crm.pipeline.opportunites.archives', 'crm.pipeline.devis', ]) expect(core.rows[0]).toMatchObject({ grain: 'application', actions: FLOOR_BY_GRAIN.application }) expect(core.rows[1]).toMatchObject({ grain: 'module', actions: FLOOR_BY_GRAIN.module }) expect(core.rows[2].actions).toEqual(PERMISSION_FLOOR_ACTIONS) expect(core.rows[3]).toMatchObject({ grain: 'resource', actions: PERMISSION_FLOOR_ACTIONS }) // 1 (app) + 1 (module) + 2×7 (sections) + 1×7 (resource) = 23 expect(core.totals).toEqual({ nodes: 5, sections: 2, resources: 1, floorPaths: 23 }) }) it('kebabizes UPPERCASE_SNAKE folder codes (HR_PORTAL → hr-portal)', () => { const core = derivePermissionFloor('HR_PORTAL', 'TIME_TRACKING', sources({ entries: [] })) expect(core.rows.map((r) => r.nodePath)).toEqual([ 'hr-portal', 'hr-portal.time-tracking', 'hr-portal.time-tracking.entries', ]) }) it('warns on reserved node codes (read/all break the scope-tier grammar)', () => { const core = derivePermissionFloor('CRM', 'PIPELINE', sources({ read: [], opportunites: ['all'] })) expect(core.warnings.some((w) => w.includes('section "read"') && w.includes('reserved'))).toBe(true) expect(core.warnings.some((w) => w.includes('resource "opportunites/all"') && w.includes('reserved'))).toBe(true) }) it('is deterministic', () => { const a = derivePermissionFloor('CRM', 'PIPELINE', sources({ devis: [], opportunites: ['archives'] })) const b = derivePermissionFloor('CRM', 'PIPELINE', sources({ devis: [], opportunites: ['archives'] })) expect(JSON.stringify(a)).toBe(JSON.stringify(b)) }) })