import { describe, test, expect, vi, beforeEach } from 'vitest' import type { MongoAbility } from '@casl/ability/dist/types/Ability' import type { AbilitiesType, AccessProfile, organizationState, } from '~/types/interfaces' import AbilityBuilder from '~/services/rights/abilityBuilder' import { ABILITIES } from '~/types/enum/enums' let ability: MongoAbility let accessProfile: AccessProfile let organizationProfile: organizationState let abilityBuilder: AbilityBuilder // Mock the content of the config yaml files // > This must be done in the global scope: https://vitest.dev/api/vi.html#vi-mock const doc = { abilities: { subject1: { action: ABILITIES.READ, conditions: [ { function: 'fct1', parameters: ['param1'], expectedResult: true, }, ], }, subject2: { action: ABILITIES.READ, conditions: { function: 'fct2', parameters: ['param2'], expectedResult: false, }, }, }, } vi.mock('yaml-import', () => { return { read: vi.fn((data: string) => doc), } }) beforeEach(() => { ability = vi.fn() as any as MongoAbility accessProfile = vi.fn() as any as AccessProfile organizationProfile = vi.fn() as any as organizationState abilityBuilder = new AbilityBuilder( ability, accessProfile, organizationProfile, ) }) describe('buildAbilities', () => { test('base call', () => { const roleAbilities: Array = [ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject2' }, ] const configAbilities: Array = [ { action: ABILITIES.READ, subject: 'subject3' }, { action: ABILITIES.READ, subject: 'subject4' }, ] const allAbilities: Array = roleAbilities.concat(configAbilities) abilityBuilder.buildAbilitiesFromRoles = vi.fn(() => roleAbilities) abilityBuilder.buildAbilitiesFromConfig = vi.fn(() => configAbilities) ability.update = vi.fn() const result = abilityBuilder.buildAbilities() expect(ability.update).toHaveBeenCalledTimes(2) expect(ability.update).toHaveBeenCalledWith(roleAbilities) expect(ability.update).toHaveBeenCalledWith(allAbilities) expect(abilityBuilder.buildAbilitiesFromRoles).toHaveBeenCalledOnce() expect(abilityBuilder.buildAbilitiesFromConfig).toHaveBeenCalledOnce() expect(result).toEqual(allAbilities) }) }) describe('buildAbilitiesFromRoles', () => { test('calls roleUtils', () => { accessProfile.roles = [ 'ROLE_EVENTS_VIEW', 'ROLE_COURSES', 'ROLE_TEACHER_CORE', 'ROLE_OTHER', ] const expected = [ { subject: 'events', action: 'read' }, { subject: 'courses', action: 'manage' }, { subject: 'other', action: 'manage' }, ] expect(abilityBuilder.buildAbilitiesFromRoles()).toEqual(expected) }) }) describe('buildAbilitiesFromConfig', () => { test('calls roleUtils', () => { abilityBuilder.hasConfigAbility = vi.fn(() => true) expect(abilityBuilder.buildAbilitiesFromConfig()).toEqual([ { action: 'read', subject: 'subject1', inverted: false }, { action: 'read', subject: 'subject2', inverted: false }, ]) }) }) describe('hasConfigAbility', () => { beforeEach(() => { accessProfile.isGuardian = true // @ts-ignore organizationProfile.isSchool = true // @ts-ignore organizationProfile.isCmf = false }) test('fulfill all conditions', () => { const conditions = [ { function: 'accessHasAnyProfile', parameters: ['guardian', 'payer'] }, { function: 'organizationIsSchool' }, ] expect(abilityBuilder.hasConfigAbility(conditions)).toBeTruthy() }) test('fulfill at least one condition', () => { const conditions = [ { function: 'accessHasAnyProfile', parameters: ['guardian', 'payer'] }, { function: 'organizationIsCmf' }, ] expect(abilityBuilder.hasConfigAbility(conditions)).toBeFalsy() }) test('fulfill none of the conditions', () => { const conditions = [{ function: 'organizationIsCmf' }] expect(abilityBuilder.hasConfigAbility(conditions)).toBeFalsy() }) }) describe('execAndValidateCondition', () => { test('accessHasAllRoleAbilities', () => { ability.can = vi.fn((action: string, subject: string) => { return ( action === 'read' && (subject === 'subject1' || subject === 'subject2') ) }) expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAllRoleAbilities', parameters: [ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject2' }, ], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAllRoleAbilities', parameters: [ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject3' }, ], }), ).toBeFalsy() }) test('accessHasAnyRoleAbility', () => { ability.can = vi.fn((action: string, subject: string) => { return action === 'read' && subject === 'subject1' }) expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAnyRoleAbility', parameters: [ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject2' }, ], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAnyRoleAbility', parameters: [{ action: ABILITIES.READ, subject: 'subject2' }], }), ).toBeFalsy() }) test('accessHasAnyProfile', () => { accessProfile.isMember = true accessProfile.isGuardian = true accessProfile.isPayer = true expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAnyProfile', parameters: ['guardian', 'payer'], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAnyProfile', parameters: ['guardian', 'caMember'], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'accessHasAnyProfile', parameters: ['caMember'], }), ).toBeFalsy() }) test('organizationHasAllModules', () => { // @ts-ignore organizationProfile.hasModule = vi.fn( (module: string) => module === 'module1' || module === 'module2', ) expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAllModules', parameters: ['module1', 'module2'], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAllModules', parameters: ['module1', 'module3'], }), ).toBeFalsy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAllModules', parameters: ['module3'], }), ).toBeFalsy() }) test('organizationHasAnyModule', () => { // @ts-ignore organizationProfile.hasModule = vi.fn( (module: string) => module === 'module1' || module === 'module2', ) expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAnyModule', parameters: ['module1', 'module2'], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAnyModule', parameters: ['module1', 'module3'], }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasAnyModule', parameters: ['module3'], }), ).toBeFalsy() }) test('organizationHasAnyModule', () => { // @ts-ignore accessProfile.isAdminAccount = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'accessIsAdminAccount', }), ).toBeTruthy() // @ts-ignore accessProfile.isAdminAccount = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'accessIsAdminAccount', }), ).toBeFalsy() }) test('organizationIsSchool', () => { // @ts-ignore organizationProfile.isSchool = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsSchool', }), ).toBeTruthy() // @ts-ignore organizationProfile.isSchool = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsSchool', }), ).toBeFalsy() }) test('organizationIsArtist', () => { // @ts-ignore organizationProfile.isArtist = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsArtist', }), ).toBeTruthy() // @ts-ignore organizationProfile.isArtist = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsArtist', }), ).toBeFalsy() }) test('organizationIsManagerProduct', () => { // @ts-ignore organizationProfile.isManagerProduct = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsManagerProduct', }), ).toBeTruthy() // @ts-ignore organizationProfile.isManagerProduct = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsManagerProduct', }), ).toBeFalsy() }) test('organizationHasChildren', () => { // @ts-ignore organizationProfile.hasChildren = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationHasChildren', }), ).toBeTruthy() // @ts-ignore organizationProfile.hasChildren = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationHasChildren', }), ).toBeFalsy() }) test('organizationIsAssociation', () => { // @ts-ignore organizationProfile.isAssociation = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsAssociation', }), ).toBeTruthy() // @ts-ignore organizationProfile.isAssociation = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsAssociation', }), ).toBeFalsy() }) test('organizationIsShowAdherentList', () => { // @ts-ignore organizationProfile.isShowAdherentList = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsShowAdherentList', }), ).toBeTruthy() // @ts-ignore organizationProfile.isShowAdherentList = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsShowAdherentList', }), ).toBeFalsy() }) test('organizationIsCmf', () => { // @ts-ignore organizationProfile.isCmf = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsCmf', }), ).toBeTruthy() // @ts-ignore organizationProfile.isCmf = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationIsCmf', }), ).toBeFalsy() }) test('organizationHasWebsite', () => { // @ts-ignore organizationProfile.getWebsite = true // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationHasWebsite', }), ).toBeTruthy() // @ts-ignore organizationProfile.getWebsite = false // @ts-ignore expect( abilityBuilder.execAndValidateCondition({ function: 'organizationHasWebsite', }), ).toBeFalsy() }) test('with expected result', () => { // @ts-ignore organizationProfile.getWebsite = true expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasWebsite', expectedResult: true, }), ).toBeTruthy() expect( // @ts-ignore abilityBuilder.execAndValidateCondition({ function: 'organizationHasWebsite', expectedResult: 'abc', }), ).toBeFalsy() }) test('invalid function', () => { expect( // @ts-ignore () => abilityBuilder.execAndValidateCondition({ function: 'invalid' }), ).toThrowError('unknown condition function : invalid') }) }) describe('hasRoleAbility', () => { beforeEach(() => { ability.can = vi.fn((action: string, subject: string) => { return action === 'read' && subject === 'a_subject' }) }) test('owned ability', () => { expect( abilityBuilder.hasRoleAbility({ action: ABILITIES.READ, subject: 'a_subject', }), ).toBeTruthy() }) test('not owned ability', () => { expect( abilityBuilder.hasRoleAbility({ action: ABILITIES.READ, subject: 'other_subject', }), ).toBeFalsy() }) }) describe('hasAllRoleAbilities', () => { beforeEach(() => { ability.can = vi.fn((action: string, subject: string) => { return ( action === 'read' && (subject === 'subject1' || subject === 'subject2') ) }) }) test('own all abilities', () => { const result = abilityBuilder.hasAllRoleAbilities([ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject2' }, ]) expect(result).toBeTruthy() }) test('own at least one ability', () => { const result = abilityBuilder.hasAllRoleAbilities([ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject3' }, ]) expect(result).toBeFalsy() }) test('own none of the abilities', () => { const result = abilityBuilder.hasAllRoleAbilities([ { action: ABILITIES.READ, subject: 'subject3' }, { action: ABILITIES.READ, subject: 'subject4' }, ]) expect(result).toBeFalsy() }) }) describe('hasAnyRoleAbility', () => { beforeEach(() => { ability.can = vi.fn((action: string, subject: string) => { return ( action === 'read' && (subject === 'subject1' || subject === 'subject2') ) }) }) test('has all abilities', () => { const result = abilityBuilder.hasAnyRoleAbility([ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject2' }, ]) expect(result).toBeTruthy() }) test('has at least one ability', () => { const result = abilityBuilder.hasAnyRoleAbility([ { action: ABILITIES.READ, subject: 'subject1' }, { action: ABILITIES.READ, subject: 'subject3' }, ]) expect(result).toBeTruthy() }) test('any none of the abilites', () => { const result = abilityBuilder.hasAnyRoleAbility([ { action: ABILITIES.READ, subject: 'subject3' }, { action: ABILITIES.READ, subject: 'subject4' }, ]) expect(result).toBeFalsy() }) }) describe('hasProfile', () => { test('owned profiles', () => { accessProfile.isAdmin = true accessProfile.isAdministratifManager = true accessProfile.isPedagogicManager = true accessProfile.isFinancialManager = true accessProfile.isCaMember = true accessProfile.isStudent = true accessProfile.isTeacher = true accessProfile.isMember = true accessProfile.isOther = true accessProfile.isGuardian = true accessProfile.isPayer = true expect(abilityBuilder.hasProfile('admin')).toBeTruthy() expect(abilityBuilder.hasProfile('administratifManager')).toBeTruthy() expect(abilityBuilder.hasProfile('pedagogicManager')).toBeTruthy() expect(abilityBuilder.hasProfile('financialManager')).toBeTruthy() expect(abilityBuilder.hasProfile('caMember')).toBeTruthy() expect(abilityBuilder.hasProfile('student')).toBeTruthy() expect(abilityBuilder.hasProfile('teacher')).toBeTruthy() expect(abilityBuilder.hasProfile('member')).toBeTruthy() expect(abilityBuilder.hasProfile('other')).toBeTruthy() expect(abilityBuilder.hasProfile('guardian')).toBeTruthy() expect(abilityBuilder.hasProfile('payor')).toBeTruthy() }) test('not owned profiles', () => { accessProfile.isAdmin = false accessProfile.isAdministratifManager = false accessProfile.isPedagogicManager = false accessProfile.isFinancialManager = false accessProfile.isCaMember = false accessProfile.isStudent = false accessProfile.isTeacher = false accessProfile.isMember = false accessProfile.isOther = false accessProfile.isGuardian = false accessProfile.isPayer = false expect(abilityBuilder.hasProfile('admin')).toBeFalsy() expect(abilityBuilder.hasProfile('administratifManager')).toBeFalsy() expect(abilityBuilder.hasProfile('pedagogicManager')).toBeFalsy() expect(abilityBuilder.hasProfile('financialManager')).toBeFalsy() expect(abilityBuilder.hasProfile('caMember')).toBeFalsy() expect(abilityBuilder.hasProfile('student')).toBeFalsy() expect(abilityBuilder.hasProfile('teacher')).toBeFalsy() expect(abilityBuilder.hasProfile('member')).toBeFalsy() expect(abilityBuilder.hasProfile('other')).toBeFalsy() expect(abilityBuilder.hasProfile('guardian')).toBeFalsy() expect(abilityBuilder.hasProfile('payor')).toBeFalsy() }) }) describe('hasAnyProfile', () => { beforeEach(() => { accessProfile.isMember = true accessProfile.isGuardian = true accessProfile.isPayer = true }) test('own all profiles', () => { expect( abilityBuilder.hasAnyProfile(['member', 'guardian', 'payor']), ).toBeTruthy() }) test('own at least one profile', () => { expect(abilityBuilder.hasAnyProfile(['member', 'caMember'])).toBeTruthy() }) test('own none of the profiles', () => { expect( abilityBuilder.hasAnyProfile(['caMember', 'isFinancialManager']), ).toBeFalsy() }) }) describe('hasAllProfiles', () => { beforeEach(() => { accessProfile.isMember = true accessProfile.isGuardian = true accessProfile.isPayer = true }) test('own all profiles', () => { expect( abilityBuilder.hasAllProfiles(['member', 'guardian', 'payor']), ).toBeTruthy() }) test('own only one of the profiles', () => { expect(abilityBuilder.hasAllProfiles(['member', 'caMember'])).toBeFalsy() }) test('own none of the profiles', () => { expect( abilityBuilder.hasAllProfiles(['caMember', 'isFinancialManager']), ).toBeFalsy() }) }) describe('hasRole', () => { beforeEach(() => { // @ts-ignore accessProfile.hasRole = vi.fn((role: string) => role === 'foo') }) test('has role', () => { expect(abilityBuilder.hasRole('foo')).toBeTruthy() }) test('has not role', () => { expect(abilityBuilder.hasRole('bar')).toBeFalsy() }) }) describe('hasAnyRole', () => { beforeEach(() => { // @ts-ignore accessProfile.hasRole = vi.fn( (role: string) => role === 'role1' || role === 'role2', ) }) test('own all roles', () => { expect(abilityBuilder.hasAnyRole(['role1', 'role2'])).toBeTruthy() }) test('own at least one role', () => { expect(abilityBuilder.hasAnyRole(['role1', 'role3'])).toBeTruthy() }) test('own none of the roles', () => { expect(abilityBuilder.hasAnyRole(['role3'])).toBeFalsy() }) }) describe('hasAllRoles', () => { beforeEach(() => { // @ts-ignore accessProfile.hasRole = vi.fn( (role: string) => role === 'role1' || role === 'role2', ) }) test('own all roles', () => { expect(abilityBuilder.hasAllRoles(['role1', 'role2'])).toBeTruthy() }) test('own at least one role', () => { expect(abilityBuilder.hasAllRoles(['role1', 'role3'])).toBeFalsy() }) test('own none of the roles', () => { expect(abilityBuilder.hasAllRoles(['role3'])).toBeFalsy() }) }) describe('hasModule', () => { beforeEach(() => { // @ts-ignore organizationProfile.hasModule = vi.fn((module: string) => module === 'foo') }) test('has module', () => { expect(abilityBuilder.hasModule('foo')).toBeTruthy() }) test('has not module', () => { expect(abilityBuilder.hasModule('bar')).toBeFalsy() }) }) describe('hasAnyModule', () => { beforeEach(() => { // @ts-ignore organizationProfile.hasModule = vi.fn( (Module: string) => Module === 'Module1' || Module === 'Module2', ) }) test('own all modules', () => { expect(abilityBuilder.hasAnyModule(['Module1', 'Module2'])).toBeTruthy() }) test('own at least one module', () => { expect(abilityBuilder.hasAnyModule(['Module1', 'Module3'])).toBeTruthy() }) test('own none of the modules', () => { expect(abilityBuilder.hasAnyModule(['Module3'])).toBeFalsy() }) }) describe('hasAllModules', () => { beforeEach(() => { // @ts-ignore organizationProfile.hasModule = vi.fn( (Module: string) => Module === 'Module1' || Module === 'Module2', ) }) test('own all modules', () => { expect(abilityBuilder.hasAllModules(['Module1', 'Module2'])).toBeTruthy() }) test('own at least one module', () => { expect(abilityBuilder.hasAllModules(['Module1', 'Module3'])).toBeFalsy() }) test('own none of the modules', () => { expect(abilityBuilder.hasAllModules(['Module3'])).toBeFalsy() }) })