import { describe, expect, it } from 'vitest' import { DERIVED_BLOCK_BEGIN, DERIVED_BLOCK_END, } from '../../derive-lookup-grants/block.js' import { parseRbacRows } from '../../derive-lookup-grants/rbac-rows.js' import { FLOOR_BLOCK_BEGIN, FLOOR_BLOCK_END, extractFloorBlock, extractFloorRowLines, renderFloorBlock, spliceFloorBlock, stripFloorBlock, } from '../block.js' import { derivePermissionFloor, type FloorSources } from '../derive.js' const sources: FloorSources = { sectionsOf: () => ['opportunites'], resourcesOf: () => [], } function freshBlock(): string { const core = derivePermissionFloor('CRM', 'PIPELINE', sources) return renderFloorBlock(core.rows, core.totals.floorPaths) } const HUMAN_MATRIX = ` # RBAC — CRM / PIPELINE | Acteur | Permission (\`module.section[.resource].action\`) | Portée | |--------|------------------------------------------|--------| | BA-001-AC-001 (Commercial) | \`pipeline.opportunites.read\` | toutes | ` describe('renderFloorBlock', () => { it('renders markers, one row per node, and the no-grant footer', () => { const block = freshBlock() expect(block.startsWith(FLOOR_BLOCK_BEGIN)).toBe(true) expect(block.endsWith(FLOOR_BLOCK_END)).toBe(true) expect(block).toContain('| `crm` | application | `access` |') expect(block).toContain('| `crm.pipeline` | module | `access` |') expect(block).toContain( '| `crm.pipeline.opportunites` | section | `access` `lookup` `read` `create` `update` `delete` `execute` |', ) expect(block).toContain('seedées sans grant') expect(extractFloorRowLines(block)).toHaveLength(3) }) it('floor rows are INVISIBLE to the human-row parser (no BA- actor code)', () => { const spliced = spliceFloorBlock(HUMAN_MATRIX, freshBlock())! const rows = parseRbacRows(spliced) expect(rows).toHaveLength(1) expect(rows[0].path).toBe('pipeline.opportunites.read') }) }) describe('spliceFloorBlock', () => { it('appends at EOF when no markers exist, then splices idempotently', () => { const first = spliceFloorBlock(HUMAN_MATRIX, freshBlock())! expect(first).toContain(FLOOR_BLOCK_BEGIN) // Re-splicing the identical block → null (no rewrite, idempotent). expect(spliceFloorBlock(first, freshBlock())).toBeNull() // A changed block replaces in place without duplicating markers. const changed = freshBlock().replace('| `crm` | application', '| `crm2` | application') const second = spliceFloorBlock(first, changed)! expect(second.match(new RegExp(FLOOR_BLOCK_BEGIN, 'g'))).toHaveLength(1) expect(second).toContain('`crm2`') expect(second).toContain('BA-001-AC-001') // human rows untouched }) it('coexists with the ba:rbac-derived-lookups block — each splice targets its own markers', () => { const withLookups = `${HUMAN_MATRIX} ${DERIVED_BLOCK_BEGIN} | BA-001-AC-001 (Commercial) | \`crm.referentiel.clients.lookup\` | toutes | FK X | ${DERIVED_BLOCK_END} ` const spliced = spliceFloorBlock(withLookups, freshBlock())! expect(spliced).toContain(DERIVED_BLOCK_BEGIN) expect(spliced).toContain('crm.referentiel.clients.lookup') expect(spliced).toContain(FLOOR_BLOCK_BEGIN) // Stripping the floor block leaves the lookups block intact. const stripped = stripFloorBlock(spliced) expect(stripped).not.toContain(FLOOR_BLOCK_BEGIN) expect(stripped).toContain(DERIVED_BLOCK_BEGIN) expect(extractFloorBlock(stripped)).toBeNull() }) })