const AccessControl = require ( '../src' ) . AccessControl ; const getValueByPath = require ( '../src' ) . getValueByPath ; function type ( o ) { return Object . prototype . toString . call ( o ) . match ( /\s(\w+)/i ) [ 1 ] . toLowerCase ( ) ; } function throwsAccessControlError ( fn , errMsg ? ) { throwsError ( fn , errMsg , AccessControl . Error . name ) ; } async function promiseThrowsError ( promise , errMsg ? ) { try { await promise ; fail ( 'should throw error' ) ; } catch ( err ) { if ( errMsg ) expect ( err . message ) . toContain ( errMsg ) ; } } function throwsError ( fn , errMsg ? , errName ? ) { expect ( fn ) . toThrow ( ) ; try { fn ( ) ; fail ( 'should throw error' ) ; } catch ( err ) { if ( errName ) expect ( err . constructor . name ) . toEqual ( errName ) ; if ( errMsg ) expect ( err . message ) . toContain ( errMsg ) ; } } describe ( 'Test Suite: Access Control' , function ( ) { let grantList = [ { role : 'admin' , resource : 'video' , action : 'create' , attributes : [ '*' ] } , { role : 'admin' , resource : 'video' , action : 'read' , attributes : [ '*' ] } , { role : 'admin' , resource : 'video' , action : 'update' , attributes : [ '*' ] } , { role : 'admin' , resource : 'video' , action : 'delete' , attributes : [ '*' ] } , { role : 'user' , resource : 'video' , action : 'create' , attributes : [ '*' ] } , { role : 'user' , resource : 'video' , action : 'read' , attributes : [ '*' ] } , { role : 'user' , resource : 'video' , action : 'update' , attributes : [ '*' ] } , { role : 'user' , resource : 'video' , action : 'delete' , attributes : [ '*' ] } ] ; let grantListWithExtendRoles = grantList . concat ( [ { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } , { role : 'editor' , extend : [ 'user' ] } , { role : 'editor' , resource : 'post' , action : 'delete' , attributes : [ '*' ] } ] ) ; let grantsObject = { admin : { grants : [ { resource : 'video' , action : 'create' } , { resource : 'video' , action : 'read' } , { resource : 'video' , action : 'update' } , { resource : 'video' , action : 'delete' } ] } , user : { grants : [ { resource : 'video' , action : 'create' , attributes : [ '*' ] } , { resource : 'video' , action : 'read' , attributes : [ '*' ] } , { resource : 'video' , action : 'update' , attributes : [ '*' ] } , { resource : 'video' , action : 'delete' , attributes : [ '*' ] } ] } } ; let categorySportsCondition = { 'Fn' : 'EQUALS' , 'args' : { 'category' : 'sports' } } ; let categoryPoliticsCondition = { 'Fn' : 'EQUALS' , 'args' : { 'category' : 'politics' } } ; let categoryHealthCondition = { 'Fn' : 'EQUALS' , 'args' : { 'category' : 'health' } } ; let categoryBusinessCondition = { 'Fn' : 'EQUALS' , 'args' : { 'category' : 'business' } } ; let categorySportsContext = { category : 'sports' } ; let categoryPoliticsContext = { category : 'politics' } ; let categoryHealthContext = { category : 'health' } ; let categoryBusinessContext = { category : 'business' } ; let customContextAllowed = { loginUserId : '1' , resourceProfileId : '1' } ; let customContextNotAllowed = { loginUserId : '1' , resourceProfileId : '2' } ; let conditionalGrantList = [ { role : 'sports/editor' , resource : 'article' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } , { role : 'sports/editor' , resource : 'article' , action : 'update' , attributes : [ '*' ] , condition : categorySportsCondition } , { role : 'sports/writer' , resource : 'article' , action : 'create' , attributes : [ '*' , '!status' ] , condition : categorySportsCondition } , { role : 'sports/writer' , resource : 'article' , action : 'update' , attributes : [ '*' , '!status' ] , condition : categorySportsCondition } ] ; let conditionalGrantObject = { 'sports/editor' : { grants : [ { resource : 'article' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } , { resource : 'article' , action : 'update' , attributes : [ '*' ] , condition : categorySportsCondition } ] } , 'sports/writer' : { grants : [ { resource : 'article' , action : 'create' , attributes : [ '*' , '!status' ] , condition : categorySportsCondition } , { resource : 'article' , action : 'update' , attributes : [ '*' , '!status' ] , condition : categorySportsCondition } ] } } ; let conditionalGrantObjectWithCustomAsyncFunction = { 'sports/custom' : { grants : [ { resource : 'profile' , action : [ 'create' , 'edit' ] , attributes : [ '*' ] , condition : ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . loginUserId === context . resourceProfileId ) ; } , 200 ) ; } ) ; } } ] } } ; let conditionalGrantObjectWithCustomSyncFunction = { 'sports/custom' : { grants : [ { resource : 'profile' , action : [ 'create' , 'edit' ] , attributes : [ '*' ] , condition : ( context ) => { return context . loginUserId === context . resourceProfileId ; } } ] } } ; let conditionalGrantArrayWithCustomAsyncFunction = [ { role : 'sports/custom' , resource : 'profile' , action : [ 'create' , 'edit' ] , attributes : [ '*' ] , condition : ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . loginUserId === context . resourceProfileId ) ; } , 200 ) ; } ) ; } } ] ; let conditionalGrantArrayWithCustomSyncFunction = [ { role : 'sports/custom' , resource : 'profile' , action : [ 'create' , 'edit' ] , attributes : [ '*' ] , condition : ( context ) => { return context . loginUserId === context . resourceProfileId ; } } ] ; beforeEach ( function ( ) { this . ac = new AccessControl ( ) ; } ) ; it ( 'should add grants from flat list (db), check/remove roles and resources' , async function ( ) { let ac = this . ac ; ac . setGrants ( grantList ) ; expect ( ac . getRoles ( ) . length ) . toEqual ( 2 ) ; expect ( ac . hasRole ( 'admin' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'user' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'moderator' ) ) . toEqual ( false ) ; ac . removeRoles ( 'admin' ) ; expect ( ac . hasRole ( 'admin' ) ) . toEqual ( false ) ; ac . removeRoles ( [ 'user' , 'moderator' ] ) ; expect ( ac . getRoles ( ) . length ) . toEqual ( 0 ) ; } ) ; it ( 'should add grants from flat list (db) with extendRoles' , async function ( ) { let ac = this . ac ; ac . setGrants ( grantListWithExtendRoles ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toBeTruthy ( ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toBeTruthy ( ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toBeTruthy ( ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'delete' ) . sync ( ) . on ( 'post' ) ) . granted ) . toBeTruthy ( ) ; } ) ; it ( 'should add conditional grants from flat list (db), check/remove roles and resources' , async function ( ) { let ac = this . ac ; ac . setGrants ( conditionalGrantList ) ; expect ( ac . getRoles ( ) . length ) . toEqual ( 2 ) ; expect ( ac . hasRole ( 'sports/editor' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'sports/writer' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'sports/moderator' ) ) . toEqual ( false ) ; ac . removeRoles ( 'sports/editor' ) ; expect ( ac . hasRole ( 'sports/editor' ) ) . toEqual ( false ) ; ac . removeRoles ( [ 'sports/writer' , 'moderator' ] ) ; expect ( ac . getRoles ( ) . length ) . toEqual ( 0 ) ; } ) ; it ( 'should grant access and check permissions' , async function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; const conditionalAttrs = [ { attributes : attrs , condition : undefined } ] ; ac . grant ( 'user' ) . execute ( 'create' ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; ac . grant ( [ 'user' , 'admin' ] ) . execute ( 'read' ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'read' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'read' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user' ) . execute ( 'update' ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'update' ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; ac . grant ( 'user' ) . execute ( 'delete' ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'delete' ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; } ) ; it ( 'should grant access and check permissions synchronously' , function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; ac . grant ( 'user' ) . execute ( 'create' ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; ac . grant ( [ 'user' , 'admin' ] ) . execute ( 'read' ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user' ) . execute ( 'read' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'read' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user' ) . execute ( 'update' ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; ac . grant ( 'user' ) . execute ( 'delete' ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user' ) . execute ( 'delete' ) . sync ( ) . on ( 'photo' ) ) . attributes ) . toEqual ( attrs ) ; } ) ; it ( 'should grant access and check permissions for wildcard resources' , async function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; ac . grant ( 'user1' ) . execute ( 'create' ) . on ( '!photo' , attrs ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user2' ) . execute ( 'create' ) . on ( [ 'photo' , 'video' ] , attrs ) ; expect ( ( await ac . can ( 'user2' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user2' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user3' ) . execute ( 'create' ) . on ( [ '!photo' ] , attrs ) ; expect ( ( await ac . can ( 'user3' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user3' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access and check permissions for wildcard resources synchronously' , function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; const conditionalAttrs = [ { attributes : attrs , condition : undefined } ] ; ac . grant ( 'user1' ) . execute ( 'create' ) . on ( '!photo' , attrs ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user2' ) . execute ( 'create' ) . on ( [ 'photo' , 'video' ] , attrs ) ; expect ( ( ac . can ( 'user2' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user2' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user3' ) . execute ( 'create' ) . on ( [ '!photo' ] , attrs ) ; expect ( ( ac . can ( 'user3' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user3' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access and check permissions for wildcard actions' , async function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; const conditionalAttrs = [ { attributes : attrs , condition : undefined } ] ; ac . grant ( 'user1' ) . execute ( '!create' ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'update' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user1' ) . execute ( [ '*' , '!create' ] ) . on ( 'photo' , attrs ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'update' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user1' ) . execute ( 'update' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user2' ) . execute ( [ 'create' , 'update' ] ) . on ( [ 'photo' ] , attrs ) ; expect ( ( await ac . can ( 'user2' ) . execute ( 'update' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user2' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user3' ) . execute ( [ '*' , '!create' ] ) . on ( [ 'photo' ] , attrs ) ; expect ( ( await ac . can ( 'user3' ) . execute ( 'update' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user3' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access and check permissions for wildcard actions synchronously' , function ( ) { const ac = this . ac ; const attrs = [ '*' , '!size' ] ; ac . grant ( 'user1' ) . execute ( '!create' ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user1' ) . execute ( [ '*' , '!create' ] ) . on ( 'photo' , attrs ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user1' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user2' ) . execute ( [ 'create' , 'update' ] ) . on ( [ 'photo' ] , attrs ) ; expect ( ( ac . can ( 'user2' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user2' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'user3' ) . execute ( [ '*' , '!create' ] ) . on ( [ 'photo' ] , attrs ) ; expect ( ( ac . can ( 'user3' ) . execute ( 'update' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user3' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should filter object properties' , async function ( ) { expect ( AccessControl . filter ( { status : 'approved' , id : 123 } , [ '*' , '!status' ] ) ) . toEqual ( { id : 123 } ) ; expect ( AccessControl . filter ( { status : 'approved' , id : 123 } , [ '*' ] ) ) . toEqual ( { status : 'approved' , id : 123 } ) ; } ) ; it ( 'should grant access with custom actions and check permissions' , async function ( ) { const ac = this . ac ; const attrs = [ '*' , '!status' ] ; const conditionalAttrs = [ { attributes : attrs , condition : undefined } ] ; ac . grant ( 'editor' ) . execute ( 'publish' ) . on ( 'article' , attrs ) ; let permission = await ac . can ( 'editor' ) . execute ( 'publish' ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( await ( permission . granted ) ) . toEqual ( true ) ; ac . grant ( 'sports/editor' ) . execute ( 'publish' ) . when ( categorySportsCondition ) . on ( 'article' , attrs ) ; permission = await ac . can ( 'sports/editor' ) . execute ( 'publish' ) . with ( categorySportsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; permission = await ac . can ( 'sports/editor' ) . execute ( 'publish' ) . with ( categoryPoliticsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( [ ] ) ; expect ( permission . granted ) . toEqual ( false ) ; ac . grant ( { role : 'politics/editor' , action : 'publish' , resource : 'article' , condition : categoryPoliticsCondition , attributes : attrs } ) ; permission = await ac . can ( 'politics/editor' ) . execute ( 'publish' ) . with ( categoryPoliticsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; ac . grant ( 'user' ) . action ( 'post' ) . resource ( 'blog' ) . attributes ( attrs ) . condition ( { Fn : 'EQUALS' , args : { logged : true } } ) . commit ( ) ; permission = await ac . can ( 'user' ) . execute ( 'post' ) . with ( { logged : true } ) . on ( 'blog' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with custom actions and check permissions synchronously' , function ( ) { const ac = this . ac ; const attrs = [ '*' , '!status' ] ; const conditionalAttrs = [ { attributes : attrs , condition : undefined } ] ; ac . grant ( 'editor' ) . execute ( 'publish' ) . on ( 'article' , attrs ) ; let permission = ac . can ( 'editor' ) . execute ( 'publish' ) . sync ( ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( ( permission . granted ) ) . toEqual ( true ) ; ac . grant ( 'sports/editor' ) . execute ( 'publish' ) . when ( categorySportsCondition ) . on ( 'article' , attrs ) ; permission = ac . can ( 'sports/editor' ) . execute ( 'publish' ) . sync ( ) . with ( categorySportsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; permission = ac . can ( 'sports/editor' ) . execute ( 'publish' ) . sync ( ) . with ( categoryPoliticsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( [ ] ) ; expect ( permission . granted ) . toEqual ( false ) ; ac . grant ( { role : 'politics/editor' , action : 'publish' , resource : 'article' , condition : categoryPoliticsCondition , attributes : attrs } ) ; permission = ac . can ( 'politics/editor' ) . execute ( 'publish' ) . sync ( ) . with ( categoryPoliticsContext ) . on ( 'article' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; ac . grant ( 'user' ) . action ( 'post' ) . resource ( 'blog' ) . attributes ( attrs ) . condition ( { Fn : 'EQUALS' , args : { logged : true } } ) . commit ( ) ; permission = ac . can ( 'user' ) . execute ( 'post' ) . sync ( ) . with ( { logged : true } ) . on ( 'blog' ) ; expect ( permission . attributes ) . toEqual ( attrs ) ; expect ( permission . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with OR condition and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'OR' , args : [ categorySportsCondition , categoryPoliticsCondition ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with OR condition and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'OR' , args : [ categorySportsCondition , categoryPoliticsCondition ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with equals conditions with null values' , function ( ) { const ac = new AccessControl ( [ { role : 'user' , resource : 'task' , action : [ 'update' ] , attributes : [ '*' ] , condition : { Fn : 'AND' , args : [ { 'Fn' : 'EQUALS' , 'args' : { 'userId' : '$.AssignedId' } } , { 'Fn' : 'EQUALS' , 'args' : { 'CompletedAt' : null } } ] } } ] ) ; expect ( ( ac . can ( 'user' ) . context ( { AssignedId : 'abc123' , CompletedAt : null , userId : 'abc123' } ) . execute ( 'update' ) . sync ( ) . on ( 'task' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with equals condition with list of values and check permissions ' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'category' : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tag : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with equals condition with list of values and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'category' : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tag : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with equals condition with single and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'category' : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with equals condition with single and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'category' : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with not equals condition with list of values and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { 'category' : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not equals condition with list of values and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { 'category' : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not equals condition with single value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not equals condition with single value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and condition with list value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'AND' , args : [ { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } , { Fn : 'NOT_EQUALS' , args : { 'category' : 'politics' } } ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and condition with list value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'AND' , args : [ { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } , { Fn : 'NOT_EQUALS' , args : { 'category' : 'politics' } } ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with JSONPath context keys or values with EQUALS condition' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'requester' : '$.owner' } } ) . execute ( 'edit' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'dilip' , requester : 'dilip' } ) . execute ( 'edit' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'tensult' , requester : 'dilip' } ) . execute ( 'edit' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { '$.request.initiator' : '$.owner' } } ) . execute ( 'edit' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'dilip' , request : { initiator : 'dilip' } } ) . execute ( 'edit' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'tensult' , request : { initiator : 'dilip' } } ) . execute ( 'edit' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with JSONPath context values with EQUALS condition synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'EQUALS' , args : { 'requester' : '$.owner' } } ) . execute ( 'edit' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { owner : 'dilip' , requester : 'dilip' } ) . execute ( 'edit' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { owner : 'tensult' , requester : 'dilip' } ) . execute ( 'edit' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with JSONPath context values with NOT_EQUALS condition' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { '$.requester' : '$.owner' } } ) . execute ( 'approve' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'dilip' , requester : 'dilip' } ) . execute ( 'approve' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { owner : 'tensult' , requester : 'dilip' } ) . execute ( 'approve' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with JSONPath context values with NOT_EQUALS condition synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT_EQUALS' , args : { '$.requester' : '$.owner' } } ) . execute ( 'approve' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { owner : 'dilip' , requester : 'dilip' } ) . execute ( 'approve' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { owner : 'tensult' , requester : 'dilip' } ) . execute ( 'approve' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and with custom condition function' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return context . category !== 'politics' ; } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and with custom condition function synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return context . category !== 'politics' ; } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and with async custom condition function' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . category !== 'politics' ) ; } , 200 ) ; } ) ; } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should fail when async custom condition used in sync' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . category !== 'politics' ) ; } , 200 ) ; } ) ; } ) . execute ( 'create' ) . on ( 'article' ) ; throwsAccessControlError ( ( ) => { ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ; } ) ; throwsAccessControlError ( ( ) => { ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ; } ) ; throwsAccessControlError ( ( ) => { ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ; } ) ; } ) ; it ( 'should support initializing ACL with custom named functions' , async function ( ) { let conditionalGrantObjectWithCustomNamedFunction = { grants : [ { role : 'user' , resource : 'profile' , action : [ 'create' , 'edit' ] , attributes : [ '*' ] , condition : "custom:isOwner" } ] , customConditionFunctions : { isOwner : ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . loginUserId === context . resourceProfileId ) ; } , 200 ) ; } ) ; } } } ; const acUsingObj = new AccessControl ( conditionalGrantObjectWithCustomNamedFunction . grants , conditionalGrantObjectWithCustomNamedFunction . customConditionFunctions ) ; expect ( ( await acUsingObj . can ( 'user' ) . context ( customContextAllowed ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await acUsingObj . can ( 'user' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should support registering custom named functions' , async function ( ) { let customConditionFunctions = { isOwner : ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . loginUserId === context . resourceProfileId ) ; } , 200 ) ; } ) ; } } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'isOwner' , customConditionFunctions . isOwner ) ; ac . grant ( "user" ) . condition ( 'custom:isOwner' ) . execute ( [ 'create' , 'edit' ] ) . on ( 'profile' ) ; expect ( ( await ac . can ( 'user' ) . context ( customContextAllowed ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should support registering custom named functions as condition object' , async function ( ) { let customConditionFunctions = { gte : ( context , args ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . level >= args . level ) ; } , 200 ) ; } ) ; } } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'gte' , customConditionFunctions . gte ) ; ac . grant ( "user" ) . condition ( { Fn : 'custom:gte' , args : { level : 2 } } ) . execute ( [ 'comment' ] ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { level : 2 } ) . execute ( 'comment' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { level : 3 } ) . execute ( 'comment' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { level : 1 } ) . execute ( 'comment' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should have valid basic custom condition example in the docs' , function ( ) { const greaterOrEqual = ( context , args ) => { if ( ! args || typeof args . level !== 'number' ) { throw new Error ( 'custom:gte requires "level" argument' ) ; } return + context . level >= args . level ; } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'gte' , greaterOrEqual ) ; ac . grant ( 'user' ) . condition ( { Fn : 'custom:gte' , args : { level : 2 } } ) . execute ( 'comment' ) . on ( 'article' ) ; const permission1 = ac . can ( 'user' ) . context ( { level : 2 } ) . execute ( 'comment' ) . sync ( ) . on ( 'article' ) ; expect ( permission1 . granted ) . toEqual ( true ) ; const permission2 = ac . can ( 'user' ) . context ( { level : 1 } ) . execute ( 'comment' ) . sync ( ) . on ( 'article' ) ; expect ( permission2 . granted ) . toEqual ( false ) ; } ) ; it ( 'should have valid no arguments custom condition example in the docs' , function ( ) { const myConditions = { isArticleOwner : ( context ) => { return context . loginUserId && context . loginUserId === context . articleOwnerId ; } } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'isArticleOwner' , myConditions . isArticleOwner ) ; ac . grant ( "user" ) . condition ( 'custom:isArticleOwner' ) . execute ( [ 'delete' , 'update' ] ) . on ( 'article' ) ; expect ( ac . can ( 'user' ) . context ( { loginUserId : 1 , articleOwnerId : 1 } ) . execute ( 'update' ) . sync ( ) . on ( 'article' ) . granted ) . toBe ( true ) ; } ) ; it ( 'should have valid async custom condition example in the docs' , async function ( ) { const asyncCheckResourceForUser = ( resource , loginUserId , record ) => new Promise ( ( resolve ) => { resolve ( resource === 'article' && loginUserId === 1 && record . id === 10 ) ; } ) ; const myConditions = { isResourceOwner : async ( context , args ) => { const { resource } = args || { } ; const { loginUserId } = context ; return asyncCheckResourceForUser ( resource , loginUserId , context [ resource ] ) ; } } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'isResourceOwner' , myConditions . isResourceOwner ) ; ac . grant ( "user" ) . condition ( { Fn : 'custom:isResourceOwner' , args : { resource : 'article' } } ) . execute ( [ 'delete' , 'update' ] ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { loginUserId : 1 , article : { id : 10 } } ) . execute ( 'update' ) . on ( 'article' ) ) . granted ) . toBe ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { loginUserId : 2 , article : { id : 10 } } ) . execute ( 'update' ) . on ( 'article' ) ) . granted ) . toBe ( false ) ; } ) ; it ( 'should have valid serialization and batch register custom conditions example in the docs' , async function ( ) { const myPolicy = { grants : [ { role : 'user' , resource : 'profile' , action : [ 'delete' , 'update' ] , attributes : [ '*' ] , condition : { Fn : 'custom:isResourceOwner' , args : { resource : 'profile' } } } , { role : 'user' , resource : 'article' , action : [ 'delete' , 'update' ] , attributes : [ '*' ] , condition : { Fn : 'custom:isResourceOwner' , args : { resource : 'article' } } } , ] , myConditions : { isResourceOwner : async ( { user , record } , { resource } ) => { if ( resource === 'profile' && user . id === 1 && record . id === 1 ) { return true ; } if ( resource === 'article' && user . id === 1 && record . id === 2 ) { return true ; } return false ; } } } ; const ac = new AccessControl ( myPolicy . grants , myPolicy . myConditions ) ; expect ( ( await ac . can ( 'user' ) . context ( { user : { id : 1 } , record : { id : 1 } } ) . execute ( 'update' ) . on ( 'profile' ) ) . granted ) . toBe ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { user : { id : 1 } , record : { id : 1 } } ) . execute ( 'delete' ) . on ( 'article' ) ) . granted ) . toBe ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { user : { id : 1 } , record : { id : 2 } } ) . execute ( 'delete' ) . on ( 'article' ) ) . granted ) . toBe ( true ) ; } ) ; it ( 'should have valid mix custom and core conditions and use JSON path helper example in the docs' , async function ( ) { const myPolicy = { grants : [ { role : 'editor/news' , resource : 'article' , action : 'approve' , attributes : [ '*' ] , condition : { Fn : 'AND' , args : [ { Fn : 'custom:categoryMatcher' , args : { type : 'news' } } , { Fn : 'custom:isResourceOwner' , args : { resource : 'article' } } ] } } , ] , myConditions : { categoryMatcher : ( context , { type } ) => { return type && getValueByPath ( context , '$.category.type' ) === type ; } , isResourceOwner : ( context , { resource } ) => { if ( ! resource ) { return false ; } return getValueByPath ( context , `$.${ resource }.owner` ) === getValueByPath ( context , '$.user.id' ) ; } , } } ; const ac = new AccessControl ( myPolicy . grants , myPolicy . myConditions ) ; expect ( ( await ac . can ( 'editor/news' ) . context ( { user : { id : 1 } , article : { owner : 1 } , category : { type : 'news' } } ) . execute ( 'approve' ) . on ( 'article' ) ) . granted ) . toBe ( true ) ; expect ( ( await ac . can ( 'editor/news' ) . context ( { user : { id : 1 } , article : { owner : 2 } , category : { type : 'news' } } ) . execute ( 'approve' ) . on ( 'article' ) ) . granted ) . toBe ( false ) ; expect ( ( await ac . can ( 'editor/news' ) . context ( { user : { id : 1 } , article : { owner : 1 } , category : { type : 'tutorials' } } ) . execute ( 'approve' ) . on ( 'article' ) ) . granted ) . toBe ( false ) ; } ) ; it ( 'should validate custom named functions as condition object' , async function ( ) { const ac = new AccessControl ( ) ; expect ( ( ) => ac . grant ( "user" ) . condition ( { Fn : 'custom:gte' , args : { level : 2 } } ) . execute ( [ 'comment' ] ) . on ( 'article' ) ) . toThrow ( ) ; } ) ; it ( 'should validate custom named functions as condition string' , async function ( ) { const ac = new AccessControl ( ) ; expect ( ( ) => ac . grant ( "user" ) . condition ( 'custom:gte' ) . execute ( [ 'comment' ] ) . on ( 'article' ) ) . toThrow ( ) ; } ) ; it ( 'should support initializing ACL when grants has custom functions' , async function ( ) { const acUsingObj = new AccessControl ( conditionalGrantObjectWithCustomAsyncFunction ) ; expect ( ( await acUsingObj . can ( 'sports/custom' ) . context ( customContextAllowed ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await acUsingObj . can ( 'sports/custom' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; const acUsingArray = new AccessControl ( conditionalGrantArrayWithCustomAsyncFunction ) ; expect ( ( await acUsingArray . can ( 'sports/custom' ) . context ( customContextAllowed ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await acUsingArray . can ( 'sports/custom' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should support initializing ACL when grants has custom functions synchronously' , function ( ) { const acUsingObj = new AccessControl ( conditionalGrantObjectWithCustomSyncFunction ) ; expect ( ( acUsingObj . can ( 'sports/custom' ) . context ( customContextAllowed ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( acUsingObj . can ( 'sports/custom' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; const acUsingArray = new AccessControl ( conditionalGrantArrayWithCustomSyncFunction ) ; expect ( ( acUsingArray . can ( 'sports/custom' ) . context ( customContextAllowed ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( acUsingArray . can ( 'sports/custom' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should stringfy and restore ACL with async custom condition function' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . category !== 'politics' ) ; } , 200 ) ; } ) ; } ) . execute ( 'create' ) . on ( 'article' ) ; const newAC = AccessControl . fromJSON ( ac . toJSON ( ) ) ; expect ( ac . toJSON ( ) ) . toEqual ( newAC . toJSON ( ) ) ; expect ( ( await newAC . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await newAC . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await newAC . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should stringfy and restore ACL with async custom named condition function' , async function ( ) { let customConditionFunctions = { isOwner : ( context ) => { return new Promise ( ( resolve ) => { setTimeout ( ( ) => { resolve ( context . loginUserId === context . resourceProfileId ) ; } , 200 ) ; } ) ; } } ; const ac = new AccessControl ( ) ; ac . registerConditionFunction ( 'isOwner' , customConditionFunctions . isOwner ) ; ac . grant ( "user" ) . condition ( 'custom:isOwner' ) . execute ( [ 'create' , 'edit' ] ) . on ( 'profile' ) ; const newAC = AccessControl . fromJSON ( ac . toJSON ( ) ) ; expect ( ac . toJSON ( ) ) . toEqual ( newAC . toJSON ( ) ) ; expect ( ( await newAC . can ( 'user' ) . context ( customContextAllowed ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await newAC . can ( 'user' ) . context ( customContextNotAllowed ) . execute ( 'edit' ) . on ( 'profile' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should stringfy and restore ACL with sync custom condition function' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return context . category !== 'politics' ; } ) . execute ( 'create' ) . on ( 'article' ) ; const newAC = AccessControl . fromJSON ( ac . toJSON ( ) ) ; expect ( ac . toJSON ( ) ) . toEqual ( newAC . toJSON ( ) ) ; expect ( ( newAC . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( newAC . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( newAC . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should not grant access with and with async custom bad condition function' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { return new Promise ( ( resolve , reject ) => { setTimeout ( ( ) => { reject ( 'I am bad function' ) ; } , 200 ) ; } ) ; } ) . execute ( 'create' ) . on ( 'article' ) ; await promiseThrowsError ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) ; } ) ; it ( 'should not grant access with and with sync custom bad condition function' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( ( context ) => { throw new Error ( 'I am bad function' ) ; } ) . execute ( 'create' ) . on ( 'article' ) ; throwsError ( ( ) => ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) ; } ) ; it ( 'should grant access with and condition with single value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'AND' , args : { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with and condition with single value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'AND' , args : { Fn : 'NOT_EQUALS' , args : { 'category' : 'sports' } } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not condition on list of values and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT' , args : [ { Fn : 'EQUALS' , args : { 'category' : 'sports' } } , { Fn : 'EQUALS' , args : { 'category' : 'politics' } } ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not condition on list of values and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT' , args : [ { Fn : 'EQUALS' , args : { 'category' : 'sports' } } , { Fn : 'EQUALS' , args : { 'category' : 'politics' } } ] } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not condition on single value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT' , args : { Fn : 'EQUALS' , args : { 'category' : 'sports' } } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with not condition on single value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'NOT' , args : { Fn : 'EQUALS' , args : { 'category' : 'sports' } } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . context ( { category : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access with list contains condition on single value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'LIST_CONTAINS' , args : { tags : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : [ 'sports' ] } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : [ 'politics' ] } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with list contains condition on single value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'LIST_CONTAINS' , args : { tags : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : [ 'sports' ] } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : [ 'politics' ] } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with starts with condition on single value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'STARTS_WITH' , args : { tags : 'sports' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : 'sports' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : 'politics' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with starts with condition on single value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'STARTS_WITH' , args : { tags : '$.category' } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : 'sports' , category : 'sports' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : 'politics' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with starts with condition with list value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'STARTS_WITH' , args : { tags : [ '$.mainCategory' , '$.subCategory' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : 'sports' , mainCategory : 'sports' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : 'politics' , subCategory : 'politics' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : 'tech' } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with starts with condition with list value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'STARTS_WITH' , args : { '$.tags' : [ '$.mainCategory' , '$.subCategory' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : 'sports' , mainCategory : 'sports' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : 'politics' , subCategory : 'politics' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : 'tech' } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with list contains condition with multiple value and check permissions' , async function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'LIST_CONTAINS' , args : { '$.tags' : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : [ 'sports' ] } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : [ 'politics' ] } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( { tags : [ 'tech' ] } ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access with list contains condition with multiple value and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . grant ( 'user' ) . condition ( { Fn : 'LIST_CONTAINS' , args : { tags : [ 'sports' , 'politics' ] } } ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : [ 'sports' ] } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : [ 'politics' ] } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( { tags : [ 'tech' ] } ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access to attribute based on conditions' , async function ( ) { const ac = this . ac ; const sportsAttrs = [ 'sportsField' ] ; const politicsAttrs = [ 'politicsField' ] ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' , sportsAttrs ) ; ac . grant ( 'user' ) . condition ( categoryPoliticsCondition ) . attributes ( politicsAttrs ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . attributes ) . toEqual ( sportsAttrs ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'article' ) ) . attributes ) . toEqual ( politicsAttrs ) ; } ) ; it ( 'should grant access to attribute based on conditions synchronously' , function ( ) { const ac = this . ac ; const sportsAttrs = [ 'sportsField' ] ; const politicsAttrs = [ 'politicsField' ] ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' , sportsAttrs ) ; ac . grant ( 'user' ) . condition ( categoryPoliticsCondition ) . attributes ( politicsAttrs ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . attributes ) . toEqual ( sportsAttrs ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . attributes ) . toEqual ( politicsAttrs ) ; } ) ; it ( 'should add conditional grants with list and check permissions' , async function ( ) { const ac = this . ac ; ac . setGrants ( conditionalGrantList ) ; const editorAttrs = [ '*' ] ; const writerAttrs = [ '*' , '!status' ] ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . attributes ) . toEqual ( editorAttrs ) ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'update' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'sports/writer' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'article' ) ) . attributes ) . toEqual ( writerAttrs ) ; expect ( ( await ac . can ( 'sports/writer' ) . context ( categoryPoliticsContext ) . execute ( 'update' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'sports/writer' ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should add conditional grants with list and check permissions synchronously' , function ( ) { const ac = this . ac ; ac . setGrants ( conditionalGrantList ) ; const editorAttrs = [ '*' ] ; const writerAttrs = [ '*' , '!status' ] ; expect ( ( ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . attributes ) . toEqual ( editorAttrs ) ; expect ( ( ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'update' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'sports/writer' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . attributes ) . toEqual ( writerAttrs ) ; expect ( ( ac . can ( 'sports/writer' ) . context ( categoryPoliticsContext ) . execute ( 'update' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'sports/writer' ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should chain grant methods and check permissions' , async function ( ) { let ac = this . ac , attrs = [ '*' ] ; ac . grant ( 'superadmin' ) . execute ( 'create' ) . on ( 'profile' , attrs ) . execute ( 'read' ) . on ( 'profile' , attrs ) . execute ( 'create' ) . on ( 'video' , [ ] ) . execute ( 'create' ) . on ( 'photo' ) ; expect ( ( await ac . can ( 'superadmin' ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'superadmin' ) . execute ( 'read' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'superadmin' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'superadmin' ) . execute ( 'create' ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should chain grant methods and check permissions synchronously' , function ( ) { let ac = this . ac , attrs = [ '*' ] ; ac . grant ( 'superadmin' ) . execute ( 'create' ) . on ( 'profile' , attrs ) . execute ( 'read' ) . on ( 'profile' , attrs ) . execute ( 'create' ) . on ( 'video' , [ ] ) . execute ( 'create' ) . on ( 'photo' ) ; expect ( ( ac . can ( 'superadmin' ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'superadmin' ) . execute ( 'read' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'superadmin' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'superadmin' ) . execute ( 'create' ) . sync ( ) . on ( 'photo' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access via object and check permissions' , async function ( ) { let ac = this . ac , attrs = [ '*' ] ; let o1 = { role : 'moderator' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; let o2 = { role : 'moderator' , resource : 'news' , action : 'read' , attributes : [ '*' ] } ; let o3 = { role : 'moderator' , resource : 'book' , attributes : [ '*' ] } ; ac . grant ( o1 ) . grant ( o2 ) ; ac . grant ( o3 ) . execute ( 'update' ) . on ( ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'read' ) . on ( 'news' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'update' ) . on ( 'book' ) ) . granted ) . toEqual ( true ) ; ac . grant ( o1 ) . execute ( 'read' ) . on ( ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'read' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'update' ) . on ( 'news' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'moderator' ) . execute ( 'create' ) . on ( 'foo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant access via object and check permissions synchronously' , function ( ) { let ac = this . ac , attrs = [ '*' ] ; let o1 = { role : 'moderator' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; let o2 = { role : 'moderator' , resource : 'news' , action : 'read' , attributes : [ '*' ] } ; let o3 = { role : 'moderator' , resource : 'book' , attributes : [ '*' ] } ; ac . grant ( o1 ) . grant ( o2 ) ; ac . grant ( o3 ) . execute ( 'update' ) . on ( ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'read' ) . sync ( ) . on ( 'news' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'update' ) . sync ( ) . on ( 'book' ) ) . granted ) . toEqual ( true ) ; ac . grant ( o1 ) . execute ( 'read' ) . on ( ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'read' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'update' ) . sync ( ) . on ( 'news' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'moderator' ) . execute ( 'create' ) . sync ( ) . on ( 'foo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant conditional access via object and check permissions' , async function ( ) { let ac = this . ac , attrs = [ '*' ] ; let o1 = { role : 'moderator' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } ; let o2 = { role : 'moderator' , resource : 'news' , action : 'read' , attributes : [ '*' ] , condition : categorySportsCondition } ; let o3 = { role : 'moderator' , resource : 'book' , attributes : [ '*' ] } ; ac . grant ( o1 ) . grant ( o2 ) ; ac . grant ( o3 ) . execute ( 'update' ) . on ( ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'read' ) . on ( 'news' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'update' ) . on ( 'book' ) ) . granted ) . toEqual ( true ) ; ac . grant ( o1 ) . execute ( 'read' ) . on ( ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'read' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'update' ) . on ( 'news' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'foo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant conditional access via object and check permissions synchronously' , function ( ) { let ac = this . ac , attrs = [ '*' ] ; let o1 = { role : 'moderator' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } ; let o2 = { role : 'moderator' , resource : 'news' , action : 'read' , attributes : [ '*' ] , condition : categorySportsCondition } ; let o3 = { role : 'moderator' , resource : 'book' , attributes : [ '*' ] } ; ac . grant ( o1 ) . grant ( o2 ) ; ac . grant ( o3 ) . execute ( 'update' ) . on ( ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'read' ) . sync ( ) . on ( 'news' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'update' ) . sync ( ) . on ( 'book' ) ) . granted ) . toEqual ( true ) ; ac . grant ( o1 ) . execute ( 'read' ) . on ( ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'read' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'update' ) . sync ( ) . on ( 'news' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'moderator' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'foo' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should skip conditions when skipConditions is used' , async function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( await ac . permission ( { role : 'user' , action : 'create' , resource : 'article' , context : categorySportsContext } ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . with ( categorySportsContext ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'article' , true ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'article' , false ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . permission ( { role : 'user' , action : 'create' , resource : 'article' } ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . permission ( { role : 'user' , action : 'create' , resource : 'article' , skipConditions : true } ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should skip conditions when skipConditions is used synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; expect ( ( ac . permissionSync ( { role : 'user' , action : 'create' , resource : 'article' , context : categorySportsContext } ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . with ( categorySportsContext ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'article' , true ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'article' , false ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'article' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . permissionSync ( { role : 'user' , action : 'create' , resource : 'article' } ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . permissionSync ( { role : 'user' , action : 'create' , resource : 'article' , skipConditions : true } ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should return allowed resources for given roles' , async function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; expect ( ( await ac . allowedResources ( { role : 'user' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'image' ] ) ; expect ( ( await ac . allowedResources ( { role : 'user' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'image' ] ) ; expect ( ( await ac . allowedResources ( { role : 'user' , context : categoryPoliticsContext } ) ) . sort ( ) ) . toEqual ( [ 'image' ] ) ; expect ( ( await ac . allowedResources ( { role : 'admin' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' ] ) ; expect ( ( await ac . allowedResources ( { role : 'owner' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' , 'video' ] ) ; expect ( ( await ac . allowedResources ( { role : [ 'admin' , 'owner' ] } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' , 'video' ] ) ; } ) ; it ( 'should return allowed resources for given roles synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; expect ( ( ac . allowedResourcesSync ( { role : 'user' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'image' ] ) ; expect ( ( ac . allowedResourcesSync ( { role : 'user' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'image' ] ) ; expect ( ( ac . allowedResourcesSync ( { role : 'user' , context : categoryPoliticsContext } ) ) . sort ( ) ) . toEqual ( [ 'image' ] ) ; expect ( ( ac . allowedResourcesSync ( { role : 'admin' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' ] ) ; expect ( ( ac . allowedResourcesSync ( { role : 'owner' } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' , 'video' ] ) ; expect ( ( ac . allowedResourcesSync ( { role : [ 'admin' , 'owner' ] } ) ) . sort ( ) ) . toEqual ( [ 'article' , 'category' , 'image' , 'video' ] ) ; } ) ; it ( 'should return allowed actions for given roles and resource' , async function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( 'delete' ) . on ( 'article' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; expect ( ( await ac . allowedActions ( { role : 'user' , resource : 'article' } ) ) . sort ( ) ) . toEqual ( [ 'create' ] ) ; expect ( ( await ac . allowedActions ( { role : 'user' , resource : 'article' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'create' ] ) ; expect ( ( await ac . allowedActions ( { role : 'user' , resource : 'article' , context : categoryPoliticsContext } ) ) ) . toEqual ( [ ] ) ; expect ( ( await ac . allowedActions ( { role : [ 'admin' , 'user' ] , resource : 'article' } ) ) . sort ( ) ) . toEqual ( [ 'create' , 'delete' ] ) ; expect ( ( await ac . allowedActions ( { role : 'admin' , resource : 'category' } ) ) . sort ( ) ) . toEqual ( [ '*' ] ) ; expect ( ( await ac . allowedActions ( { role : 'owner' , resource : 'video' } ) ) . sort ( ) ) . toEqual ( [ '*' ] ) ; } ) ; it ( 'should return allowed actions for given roles and resource synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'article' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( 'delete' ) . on ( 'article' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; expect ( ( ac . allowedActionsSync ( { role : 'user' , resource : 'article' } ) ) . sort ( ) ) . toEqual ( [ 'create' ] ) ; expect ( ( ac . allowedActionsSync ( { role : 'user' , resource : 'article' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'create' ] ) ; expect ( ( ac . allowedActionsSync ( { role : 'user' , resource : 'article' , context : categoryPoliticsContext } ) ) ) . toEqual ( [ ] ) ; expect ( ( ac . allowedActionsSync ( { role : [ 'admin' , 'user' ] , resource : 'article' } ) ) . sort ( ) ) . toEqual ( [ 'create' , 'delete' ] ) ; expect ( ( ac . allowedActionsSync ( { role : 'admin' , resource : 'category' } ) ) . sort ( ) ) . toEqual ( [ '*' ] ) ; expect ( ( ac . allowedActionsSync ( { role : 'owner' , resource : 'video' } ) ) . sort ( ) ) . toEqual ( [ '*' ] ) ; } ) ; it ( 'should return allowing roles for given permission' , async function ( ) { let ac = this . ac ; ac . setGrants ( conditionalGrantObject ) ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'blog' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'sports/editor' , 'user' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'role' ) ; expect ( ( await ac . allowingRoles ( { resource : 'image' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' , 'sports/editor' , 'user' ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'video' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'owner' ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'category' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'blog' , action : 'create' } ) ) ) . toEqual ( [ ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'blog' , action : 'create' , context : categoryPoliticsContext } ) ) ) . toEqual ( [ ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'blog' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' , 'sports/editor' , 'user' ] ) ; expect ( ( await ac . allowingRoles ( { resource : 'article' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'sports/editor' , 'sports/writer' ] ) ; ac . removeRoles ( 'user' ) ; expect ( ( await ac . allowingRoles ( { resource : 'blog' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ ] ) ; } ) ; it ( 'should return allowing roles for given permission synchronously' , function ( ) { let ac = this . ac ; ac . setGrants ( conditionalGrantObject ) ; ac . grant ( 'user' ) . condition ( categorySportsCondition ) . execute ( 'create' ) . on ( 'blog' ) ; ac . grant ( 'user' ) . execute ( '*' ) . on ( 'image' ) ; ac . extendRole ( 'sports/editor' , 'user' ) ; ac . extendRole ( 'admin' , 'user' ) ; ac . grant ( 'admin' ) . execute ( '*' ) . on ( 'category' ) ; ac . extendRole ( 'owner' , 'admin' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'video' ) ; ac . grant ( 'owner' ) . execute ( '*' ) . on ( 'role' ) ; expect ( ( ac . allowingRolesSync ( { resource : 'image' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' , 'sports/editor' , 'user' ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'video' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'owner' ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'category' , action : 'create' } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'blog' , action : 'create' } ) ) ) . toEqual ( [ ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'blog' , action : 'create' , context : categoryPoliticsContext } ) ) ) . toEqual ( [ ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'blog' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'admin' , 'owner' , 'sports/editor' , 'user' ] ) ; expect ( ( ac . allowingRolesSync ( { resource : 'article' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ 'sports/editor' , 'sports/writer' ] ) ; ac . removeRoles ( 'user' ) ; expect ( ( ac . allowingRolesSync ( { resource : 'blog' , action : 'create' , context : categorySportsContext } ) ) . sort ( ) ) . toEqual ( [ ] ) ; } ) ; it ( 'should grant access (variation, chained)' , async function ( ) { let ac = this . ac ; ac . setGrants ( grantsObject ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'foo' ) . execute ( 'create' ) . on ( 'bar' ) ; expect ( ( await ac . can ( 'foo' ) . execute ( 'create' ) . on ( 'bar' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'foo' ) . execute ( 'create' ) . on ( 'baz' , [ ] ) ; expect ( ( await ac . can ( 'foo' ) . execute ( 'create' ) . on ( 'baz' ) ) . granted ) . toEqual ( false ) ; ac . grant ( 'qux' ) . execute ( 'create' ) . on ( 'resource1' ) . execute ( 'update' ) . on ( 'resource2' ) . execute ( 'read' ) . on ( 'resource1' ) . execute ( 'delete' ) . on ( 'resource1' , [ ] ) ; expect ( ( await ac . can ( 'qux' ) . execute ( 'create' ) . on ( 'resource1' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'qux' ) . execute ( 'update' ) . on ( 'resource2' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'qux' ) . execute ( 'read' ) . on ( 'resource1' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'qux' ) . execute ( 'delete' ) . on ( 'resource1' ) ) . granted ) . toEqual ( false ) ; ac . grant ( 'editor' ) . resource ( 'file1' ) . execute ( 'update' ) . on ( ) ; ac . grant ( ) . role ( 'editor' ) . execute ( 'update' ) . on ( 'file2' ) ; ac . grant ( ) . role ( 'editor' ) . resource ( 'file3' ) . execute ( 'update' ) . on ( ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'update' ) . on ( 'file1' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'update' ) . on ( 'file2' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'update' ) . on ( 'file3' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'editor' ) . resource ( 'fileX' ) . execute ( 'read' ) . on ( ) . execute ( 'create' ) . on ( ) . resource ( 'fileY' ) . execute ( 'update' ) . on ( ) . execute ( 'delete' ) . on ( ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'read' ) . on ( 'fileX' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'create' ) . on ( 'fileX' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'update' ) . on ( 'fileY' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'delete' ) . on ( 'fileY' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant access (variation, chained) synchronously' , function ( ) { let ac = this . ac ; ac . setGrants ( grantsObject ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'foo' ) . execute ( 'create' ) . on ( 'bar' ) ; expect ( ( ac . can ( 'foo' ) . execute ( 'create' ) . sync ( ) . on ( 'bar' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'foo' ) . execute ( 'create' ) . on ( 'baz' , [ ] ) ; expect ( ( ac . can ( 'foo' ) . execute ( 'create' ) . sync ( ) . on ( 'baz' ) ) . granted ) . toEqual ( false ) ; ac . grant ( 'qux' ) . execute ( 'create' ) . on ( 'resource1' ) . execute ( 'update' ) . on ( 'resource2' ) . execute ( 'read' ) . on ( 'resource1' ) . execute ( 'delete' ) . on ( 'resource1' , [ ] ) ; expect ( ( ac . can ( 'qux' ) . execute ( 'create' ) . sync ( ) . on ( 'resource1' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'qux' ) . execute ( 'update' ) . sync ( ) . on ( 'resource2' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'qux' ) . execute ( 'read' ) . sync ( ) . on ( 'resource1' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'qux' ) . execute ( 'delete' ) . sync ( ) . on ( 'resource1' ) ) . granted ) . toEqual ( false ) ; ac . grant ( 'editor' ) . resource ( 'file1' ) . execute ( 'update' ) . on ( ) ; ac . grant ( ) . role ( 'editor' ) . execute ( 'update' ) . on ( 'file2' ) ; ac . grant ( ) . role ( 'editor' ) . resource ( 'file3' ) . execute ( 'update' ) . on ( ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'update' ) . sync ( ) . on ( 'file1' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'update' ) . sync ( ) . on ( 'file2' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'update' ) . sync ( ) . on ( 'file3' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'editor' ) . resource ( 'fileX' ) . execute ( 'read' ) . on ( ) . execute ( 'create' ) . on ( ) . resource ( 'fileY' ) . execute ( 'update' ) . on ( ) . execute ( 'delete' ) . on ( ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'read' ) . sync ( ) . on ( 'fileX' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'create' ) . sync ( ) . on ( 'fileX' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'update' ) . sync ( ) . on ( 'fileY' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'delete' ) . sync ( ) . on ( 'fileY' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should switch-chain grant roles' , async function ( ) { let ac = this . ac ; ac . grant ( 'r1' ) . execute ( 'create' ) . on ( 'a' ) . grant ( 'r2' ) . execute ( 'create' ) . on ( 'b' ) . execute ( 'read' ) . on ( 'b' ) . grant ( 'r1' ) . execute ( 'update' ) . on ( 'c' ) ; expect ( ( await ac . can ( 'r1' ) . execute ( 'create' ) . on ( 'a' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'r1' ) . execute ( 'update' ) . on ( 'c' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'r2' ) . execute ( 'create' ) . on ( 'b' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'r2' ) . execute ( 'read' ) . on ( 'b' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should switch-chain grant roles synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'r1' ) . execute ( 'create' ) . on ( 'a' ) . grant ( 'r2' ) . execute ( 'create' ) . on ( 'b' ) . execute ( 'read' ) . on ( 'b' ) . grant ( 'r1' ) . execute ( 'update' ) . on ( 'c' ) ; expect ( ( ac . can ( 'r1' ) . execute ( 'create' ) . sync ( ) . on ( 'a' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'r1' ) . execute ( 'update' ) . sync ( ) . on ( 'c' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'r2' ) . execute ( 'create' ) . sync ( ) . on ( 'b' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'r2' ) . execute ( 'read' ) . sync ( ) . on ( 'b' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant comma/semi-colon separated roles' , async function ( ) { let ac = this . ac ; ac . grant ( 'role2; role3, editor; viewer, agent' ) . execute ( 'create' ) . on ( 'book' ) ; expect ( ac . hasRole ( 'role3' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'editor' ) ) . toEqual ( true ) ; expect ( ac . hasRole ( 'agent' ) ) . toEqual ( true ) ; } ) ; it ( 'permission should also return queried role(s) and resource' , async function ( ) { let ac = this . ac ; ac . grant ( 'foo, bar' ) . execute ( 'create' ) . on ( 'baz' ) ; expect ( ( await ac . can ( 'bar' ) . execute ( 'create' ) . on ( 'baz' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'foo' ) . execute ( 'create' ) . on ( 'baz' ) ) . roles ) . toContain ( 'foo' ) ; expect ( ( await ac . can ( 'foo' ) . execute ( 'create' ) . on ( 'baz' ) ) . resource ) . toEqual ( 'baz' ) ; } ) ; it ( 'permission should also return queried role(s) and resource synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'foo, bar' ) . execute ( 'create' ) . on ( 'baz' ) ; expect ( ( ac . can ( 'bar' ) . execute ( 'create' ) . sync ( ) . on ( 'baz' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'foo' ) . execute ( 'create' ) . sync ( ) . on ( 'baz' ) ) . roles ) . toContain ( 'foo' ) ; expect ( ( ac . can ( 'foo' ) . execute ( 'create' ) . sync ( ) . on ( 'baz' ) ) . resource ) . toEqual ( 'baz' ) ; } ) ; it ( 'should extend / remove roles' , async function ( ) { let ac = this . ac ; ac . grant ( 'admin' ) . execute ( 'create' ) . on ( 'book' ) ; ac . extendRole ( 'onur' , 'admin' ) ; expect ( ac . getGrants ( ) . onur . $extend [ 'admin' ] ) . toEqual ( { condition : undefined } ) ; ac . grant ( 'role2, role3, editor, viewer, agent' ) . execute ( 'create' ) . on ( 'book' ) ; ac . extendRole ( 'onur' , [ 'role2' , 'role3' ] ) ; expect ( Object . keys ( ac . getGrants ( ) . onur . $extend ) . sort ( ) ) . toEqual ( [ 'admin' , 'role2' , 'role3' ] ) ; await ac . grant ( 'admin' ) . extend ( 'editor' ) ; expect ( Object . keys ( ac . getGrants ( ) . admin . $extend ) ) . toEqual ( [ 'editor' ] ) ; ( await ac . grant ( 'admin' ) . extend ( [ 'viewer' , 'editor' , 'agent' ] ) ) . execute ( 'read' ) . on ( 'video' ) ; expect ( Object . keys ( ac . getGrants ( ) . admin . $extend ) . sort ( ) ) . toEqual ( [ 'agent' , 'editor' , 'viewer' ] ) ; ( await ac . grant ( [ 'editor' , 'agent' ] ) . extend ( [ 'role2' , 'role3' ] ) ) . execute ( 'update' ) . on ( 'photo' ) ; expect ( Object . keys ( ac . getGrants ( ) . editor . $extend ) . sort ( ) ) . toEqual ( [ 'role2' , 'role3' ] ) ; ac . removeRoles ( [ 'editor' , 'agent' ] ) ; expect ( ac . getGrants ( ) . editor ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . agent ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . admin . $extend [ 'editor' ] ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . admin . $extend [ 'agent' ] ) . toBeUndefined ( ) ; throwsAccessControlError ( ( ) => ac . grant ( 'roleX' ) . extend ( 'roleX' ) ) ; throwsAccessControlError ( ( ) => ac . grant ( [ 'admin2' , 'roleX' ] ) . extend ( [ 'roleX' , 'admin3' ] ) ) ; } ) ; it ( 'should extend / remove roles synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'admin' ) . execute ( 'create' ) . on ( 'book' ) ; ac . extendRole ( 'onur' , 'admin' ) ; expect ( ac . getGrants ( ) . onur . $extend [ 'admin' ] ) . toEqual ( { condition : undefined } ) ; ac . grant ( 'role2, role3, editor, viewer, agent' ) . execute ( 'create' ) . on ( 'book' ) ; ac . extendRole ( 'onur' , [ 'role2' , 'role3' ] ) ; expect ( Object . keys ( ac . getGrants ( ) . onur . $extend ) . sort ( ) ) . toEqual ( [ 'admin' , 'role2' , 'role3' ] ) ; ac . grant ( 'admin' ) . extendSync ( 'editor' ) ; expect ( Object . keys ( ac . getGrants ( ) . admin . $extend ) ) . toEqual ( [ 'editor' ] ) ; ( ac . grant ( 'admin' ) . extendSync ( [ 'viewer' , 'editor' , 'agent' ] ) ) . execute ( 'read' ) . on ( 'video' ) ; expect ( Object . keys ( ac . getGrants ( ) . admin . $extend ) . sort ( ) ) . toEqual ( [ 'agent' , 'editor' , 'viewer' ] ) ; ( ac . grant ( [ 'editor' , 'agent' ] ) . extendSync ( [ 'role2' , 'role3' ] ) ) . execute ( 'update' ) . on ( 'photo' ) ; expect ( Object . keys ( ac . getGrants ( ) . editor . $extend ) . sort ( ) ) . toEqual ( [ 'role2' , 'role3' ] ) ; ac . removeRoles ( [ 'editor' , 'agent' ] ) ; expect ( ac . getGrants ( ) . editor ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . agent ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . admin . $extend [ 'editor' ] ) . toBeUndefined ( ) ; expect ( ac . getGrants ( ) . admin . $extend [ 'agent' ] ) . toBeUndefined ( ) ; throwsError ( ( ) => ac . grant ( 'roleX' ) . extendSync ( 'roleX' ) ) ; throwsError ( ( ) => ac . grant ( [ 'admin2' , 'roleX' ] ) . extendSync ( [ 'roleX' , 'admin3' ] ) ) ; } ) ; it ( 'should throw error while trying extend own role' , function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . execute ( 'create' ) . when ( categorySportsCondition ) . on ( 'book' ) ; ac . extendRole ( 'editor' , 'user' ) ; ac . grant ( 'editor' ) . execute ( 'delete' ) . on ( 'book' ) ; throwsAccessControlError ( ( ) => ac . extendRole ( 'user' , 'editor' ) ) ; throwsAccessControlError ( ( ) => ac . extendRole ( 'user' , 'user' ) ) ; } ) ; it ( 'should throw error while trying extend own role synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'user' ) . execute ( 'create' ) . when ( categorySportsCondition ) . on ( 'book' ) ; ac . extendRole ( 'editor' , 'user' ) ; ac . grant ( 'editor' ) . execute ( 'delete' ) . on ( 'book' ) ; throwsError ( ( ) => ac . extendRole ( 'user' , 'editor' ) ) ; throwsError ( ( ) => ac . extendRole ( 'user' , 'user' ) ) ; } ) ; it ( 'should extend roles when conditions used' , async function ( ) { let ac = this . ac ; let sportsEditorGrant = { role : 'sports/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } ; let politicsEditorGrant = { role : 'politics/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categoryPoliticsCondition } ; ac . grant ( sportsEditorGrant ) ; ac . grant ( politicsEditorGrant ) ; ac . extendRole ( 'editor' , [ 'sports/editor' , 'politics/editor' ] ) ; expect ( ( await ac . can ( 'editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should extend roles when conditions used synchronously' , function ( ) { let ac = this . ac ; let sportsEditorGrant = { role : 'sports/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } ; let politicsEditorGrant = { role : 'politics/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categoryPoliticsCondition } ; ac . grant ( sportsEditorGrant ) ; ac . grant ( politicsEditorGrant ) ; ac . extendRole ( 'editor' , [ 'sports/editor' , 'politics/editor' ] ) ; expect ( ( ac . can ( 'editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should extend roles and reflect the changes done to original role used synchronously' , function ( ) { let ac = this . ac ; let sportsEditorGrant = { role : 'news/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categorySportsCondition } ; let politicsEditorGrant = { role : 'news/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categoryPoliticsCondition } ; ac . grant ( sportsEditorGrant ) ; ac . grant ( politicsEditorGrant ) ; let businessEditorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categoryBusinessCondition } ; ac . grant ( businessEditorGrant ) ; ac . extendRole ( 'editor' , [ 'news/editor' ] ) ; expect ( ( ac . can ( 'editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . context ( categoryBusinessContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; let healthEditorGrant = { role : 'news/editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] , condition : categoryHealthCondition } ; ac . grant ( healthEditorGrant ) ; expect ( ( ac . can ( 'editor' ) . context ( categoryHealthContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should extend roles with conditions' , async function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; expect ( ( await ac . can ( 'editor' ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should extend roles with conditions synchronously' , function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; expect ( ( ac . can ( 'editor' ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should support multi-level extension of roles when conditions used' , async function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; ac . extendRole ( 'sports-and-politics/editor' , [ 'sports/editor' , 'politics/editor' ] ) ; expect ( ( await ac . can ( 'sports-and-politics/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'sports-and-politics/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; ac . extendRole ( 'conditonal/sports-and-politics/editor' , 'sports-and-politics/editor' , { Fn : 'EQUALS' , args : { status : 'draft' } } ) ; expect ( ( await ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'sports' , status : 'draft' } ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'tech' , status : 'draft' } ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'sports' , status : 'published' } ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should support multi-level extension of roles when conditions used synchronously' , function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; ac . extendRole ( 'sports-and-politics/editor' , [ 'sports/editor' , 'politics/editor' ] ) ; expect ( ( ac . can ( 'sports-and-politics/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'sports-and-politics/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; ac . extendRole ( 'conditonal/sports-and-politics/editor' , 'sports-and-politics/editor' , { Fn : 'EQUALS' , args : { status : 'draft' } } ) ; expect ( ( ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'sports' , status : 'draft' } ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'tech' , status : 'draft' } ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'conditonal/sports-and-politics/editor' ) . context ( { category : 'sports' , status : 'published' } ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should remove roles when conditions used' , async function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; ac . removeRoles ( 'editor' ) ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( await ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should remove roles when conditions used synchronously' , function ( ) { let ac = this . ac ; let editorGrant = { role : 'editor' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; ac . grant ( editorGrant ) ; ac . extendRole ( 'sports/editor' , 'editor' , categorySportsCondition ) ; ac . extendRole ( 'politics/editor' , 'editor' , categoryPoliticsCondition ) ; ac . removeRoles ( 'editor' ) ; expect ( ( ac . can ( 'sports/editor' ) . context ( categoryPoliticsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; expect ( ( ac . can ( 'sports/editor' ) . context ( categorySportsContext ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should throw if grant objects are invalid' , async function ( ) { let o , ac = this . ac ; o = { role : '' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'moderator' , resource : null , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'admin' , resource : 'post' , action : null , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'admin2' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . not . toThrow ( ) ; expect ( ( await ac . can ( 'admin2' ) . execute ( 'create' ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should throw if grant objects are invalid synchronously' , function ( ) { let o , ac = this . ac ; o = { role : '' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'moderator' , resource : null , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'admin' , resource : 'post' , action : null , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . toThrow ( ) ; o = { role : 'admin2' , resource : 'post' , action : 'create' , attributes : [ '*' ] } ; expect ( ( ) => ac . grant ( o ) ) . not . toThrow ( ) ; expect ( ( ac . can ( 'admin2' ) . execute ( 'create' ) . sync ( ) . on ( 'post' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should throw `AccessControlError` for invalid role' , async function ( ) { let ac = this . ac ; throwsAccessControlError ( ( ) => ac . grant ( ) . execute ( 'create' ) . on ( ) ) ; ac . setGrants ( grantsObject ) ; await promiseThrowsError ( ac . can ( 'invalid-role' ) . execute ( 'create' ) . on ( 'video' ) , 'Role not found' ) ; } ) ; it ( 'should throw `AccessControlError` for invalid role synchronously' , function ( ) { let ac = this . ac ; throwsAccessControlError ( ( ) => ac . grant ( ) . execute ( 'create' ) . on ( ) ) ; ac . setGrants ( grantsObject ) ; throwsError ( ( ) => ac . can ( 'invalid-role' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) , 'Role not found' ) ; } ) ; it ( 'should filter granted attributes' , async function ( ) { let ac = this . ac , attrs = [ '*' , '!account.balance.credit' , '!account.id' , '!secret' ] , data = { name : 'Company, LTD.' , address : { city : 'istanbul' , country : 'TR' } , account : { id : 33 , taxNo : 12345 , balance : { credit : 100 , deposit : 0 } } , secret : { value : 'hidden' } } ; ac . grant ( 'user' ) . execute ( 'create' ) . on ( 'company' , attrs ) ; let permission = await ( ac . can ( 'user' ) . execute ( 'create' ) . on ( 'company' ) ) ; expect ( permission . granted ) . toEqual ( true ) ; let filtered = permission . filter ( data ) ; expect ( filtered . name ) . toEqual ( jasmine . any ( String ) ) ; expect ( filtered . address ) . toEqual ( jasmine . any ( Object ) ) ; expect ( filtered . address . city ) . toEqual ( 'istanbul' ) ; expect ( filtered . account ) . toBeDefined ( ) ; expect ( filtered . account . id ) . toBeUndefined ( ) ; expect ( filtered . account . balance ) . toBeDefined ( ) ; expect ( filtered . account . credit ) . toBeUndefined ( ) ; expect ( filtered . secret ) . toBeUndefined ( ) ; } ) ; it ( 'should filter granted attributes synchronously' , function ( ) { let ac = this . ac , attrs = [ '*' , '!account.balance.credit' , '!account.id' , '!secret' ] , data = { name : 'Company, LTD.' , address : { city : 'istanbul' , country : 'TR' } , account : { id : 33 , taxNo : 12345 , balance : { credit : 100 , deposit : 0 } } , secret : { value : 'hidden' } } ; ac . grant ( 'user' ) . execute ( 'create' ) . on ( 'company' , attrs ) ; let permission = ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'company' ) ) ; expect ( permission . granted ) . toEqual ( true ) ; let filtered = permission . filter ( data ) ; expect ( filtered . name ) . toEqual ( jasmine . any ( String ) ) ; expect ( filtered . address ) . toEqual ( jasmine . any ( Object ) ) ; expect ( filtered . address . city ) . toEqual ( 'istanbul' ) ; expect ( filtered . account ) . toBeDefined ( ) ; expect ( filtered . account . id ) . toBeUndefined ( ) ; expect ( filtered . account . balance ) . toBeDefined ( ) ; expect ( filtered . account . credit ) . toBeUndefined ( ) ; expect ( filtered . secret ) . toBeUndefined ( ) ; } ) ; it ( 'Check with multiple roles changes grant list (issue #2)' , async function ( ) { let ac = this . ac ; ac . grant ( 'admin' ) . execute ( 'update' ) . on ( 'video' ) . grant ( [ 'user' , 'admin' ] ) . execute ( 'update' ) . on ( 'video' ) ; expect ( ( await ac . can ( [ 'admin' ] ) . execute ( 'update' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( [ 'admin' ] ) . execute ( 'update' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( [ 'admin' ] ) . execute ( 'update' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'Check with multiple roles changes grant list (issue #2) synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'admin' ) . execute ( 'update' ) . on ( 'video' ) . grant ( [ 'user' , 'admin' ] ) . execute ( 'update' ) . on ( 'video' ) ; expect ( ( ac . can ( [ 'admin' ] ) . execute ( 'update' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( [ 'admin' ] ) . execute ( 'update' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( [ 'admin' ] ) . execute ( 'update' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; } ) ; it ( 'should grant multiple roles and multiple resources' , async function ( ) { let ac = this . ac ; ac . grant ( 'admin, user' ) . execute ( 'create' ) . on ( 'profile, video' ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'admin, user' ) . execute ( 'create' ) . on ( 'profile, video' , '*,!id' ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'create' ) . on ( 'profile' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( await ac . can ( 'admin' ) . execute ( 'create' ) . on ( 'video' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'profile' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'video' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( await ac . can ( 'user' ) . execute ( 'create' ) . on ( 'non-existent' ) ) . granted ) . toEqual ( false ) ; } ) ; it ( 'should grant multiple roles and multiple resources synchronously' , function ( ) { let ac = this . ac ; ac . grant ( 'admin, user' ) . execute ( 'create' ) . on ( 'profile, video' ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . granted ) . toEqual ( true ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . granted ) . toEqual ( true ) ; ac . grant ( 'admin, user' ) . execute ( 'create' ) . on ( 'profile, video' , '*,!id' ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( ac . can ( 'admin' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'profile' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'video' ) ) . attributes ) . toEqual ( [ '*' ] ) ; expect ( ( ac . can ( 'user' ) . execute ( 'create' ) . sync ( ) . on ( 'non-existent' ) ) . granted ) . toEqual ( false ) ; } ) ; } ) ;