import { describe, expect, it } from 'vitest' import { checkAgainstStates, type StateLike } from '../check.js' import type { AppFragment } from '../types.js' // --------------------------------------------------------------------------- // Literal fragment — what deriveRbacGrants emits for a small CRM app. // --------------------------------------------------------------------------- const FRAGMENT: AppFragment = { app: 'CRM', appCode: 'crm', actors: [ { code: 'commercial', name: 'Commercial', applicationCode: 'crm', baCode: 'BA-001-AC-001' }, { code: 'manager-commercial', name: 'Manager commercial', applicationCode: 'crm', baCode: 'BA-001-AC-002' }, ], permissions: [{ path: 'crm.pipeline.opportunites.read.all', roles: ['manager-commercial'] }], rolePermissions: [ { roleCode: 'commercial', permissionPath: 'crm.access' }, { roleCode: 'commercial', permissionPath: 'crm.pipeline.access' }, { roleCode: 'commercial', permissionPath: 'crm.pipeline.opportunites.access' }, { roleCode: 'commercial', permissionPath: 'crm.pipeline.opportunites.read' }, { roleCode: 'manager-commercial', permissionPath: 'crm.pipeline.opportunites.read.all' }, ], } const MATCHING_STATE: StateLike = { application: 'crm', roles: [ { code: 'commercial', name: 'Commercial' }, { code: 'manager-commercial', name: 'Manager commercial' }, ], rolePermissions: [...FRAGMENT.rolePermissions], } describe('checkAgainstStates — parity verdicts', () => { it('upToDate when the state carries exactly the derived grants', () => { const report = checkAgainstStates([FRAGMENT], [MATCHING_STATE]) expect(report.upToDate).toBe(true) expect(report.apps[0]).toMatchObject({ appCode: 'crm', stateFound: true, missingGrants: [], extraGrants: [], unmatchedActors: [], upToDate: true, }) }) it('missing-grant: a BA grant absent from the state (the lost-matrix-row class)', () => { const state: StateLike = { ...MATCHING_STATE, rolePermissions: MATCHING_STATE.rolePermissions.filter( (g) => g.permissionPath !== 'crm.pipeline.opportunites.read', ), } const report = checkAgainstStates([FRAGMENT], [state]) expect(report.upToDate).toBe(false) expect(report.apps[0].missingGrants).toEqual([ { roleCode: 'commercial', permissionPath: 'crm.pipeline.opportunites.read' }, ]) expect(report.apps[0].extraGrants).toEqual([]) }) it('extra-grant: a seeded grant no BA row explains (the invented-right class)', () => { const state: StateLike = { ...MATCHING_STATE, rolePermissions: [ ...MATCHING_STATE.rolePermissions, { roleCode: 'commercial', permissionPath: 'crm.pipeline.opportunites.delete' }, ], } const report = checkAgainstStates([FRAGMENT], [state]) expect(report.upToDate).toBe(false) expect(report.apps[0].extraGrants).toEqual([ { roleCode: 'commercial', permissionPath: 'crm.pipeline.opportunites.delete' }, ]) }) it('matches a LEGACY hand-coded role through the name leg and compares under ITS code', () => { const state: StateLike = { application: 'crm', roles: [ { code: 'com', name: 'Commercial' }, // legacy code ≠ slug { code: 'manager-commercial', name: 'Manager commercial' }, ], rolePermissions: MATCHING_STATE.rolePermissions.map((g) => g.roleCode === 'commercial' ? { ...g, roleCode: 'com' } : g, ), } const report = checkAgainstStates([FRAGMENT], [state]) expect(report.upToDate).toBe(true) }) it('an actor matching NO seeded role is reported and its grants surface as missing', () => { const state: StateLike = { application: 'crm', roles: [{ code: 'manager-commercial', name: 'Manager commercial' }], rolePermissions: MATCHING_STATE.rolePermissions.filter((g) => g.roleCode !== 'commercial'), } const report = checkAgainstStates([FRAGMENT], [state]) expect(report.apps[0].unmatchedActors).toEqual(['BA-001-AC-001 (Commercial)']) expect(report.apps[0].missingGrants.length).toBe(4) expect(report.apps[0].upToDate).toBe(false) }) it('missing state file: everything expected is missing, never silently ok', () => { const report = checkAgainstStates([FRAGMENT], []) expect(report.apps[0].stateFound).toBe(false) expect(report.apps[0].missingGrants).toEqual(FRAGMENT.rolePermissions) expect(report.apps[0].upToDate).toBe(false) }) it('an actor without grants that matches no role is NOT reported (nothing at stake)', () => { const fragment: AppFragment = { ...FRAGMENT, actors: [ ...FRAGMENT.actors, { code: 'auditeur', name: 'Auditeur externe', applicationCode: 'crm', baCode: 'BA-001-AC-009' }, ], } const report = checkAgainstStates([fragment], [MATCHING_STATE]) expect(report.apps[0].unmatchedActors).toEqual([]) expect(report.upToDate).toBe(true) }) })