| // Copyright 2018 The Casbin Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // noinspection JSMismatchedCollectionQueryUpdate |
| |
| import * as _ from 'lodash'; |
| import { DefaultRoleManager, Enforcer, newModel } from '../src'; |
| import { keyMatch2Func, keyMatch3Func, keyMatchFunc } from '../src/util'; |
| import { getEnforcerWithPath } from './utils'; |
| |
| async function testEnforce(e: Enforcer, sub: string, obj: any, act: string, res: boolean): Promise<void> { |
| await expect(e.enforce(sub, obj, act)).resolves.toBe(res); |
| } |
| |
| async function testEnforceWithoutUsers(e: Enforcer, obj: string, act: string, res: boolean): Promise<void> { |
| await expect(e.enforce(obj, act)).resolves.toBe(res); |
| } |
| |
| async function testDomainEnforce(e: Enforcer, sub: string, dom: string, obj: string, act: string, res: boolean): Promise<void> { |
| await expect(e.enforce(sub, dom, obj, act)).resolves.toBe(res); |
| } |
| |
| test('TestBasicModel', async () => { |
| const e = await getEnforcerWithPath('examples/basic_model.conf', 'examples/basic_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', false); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', true); |
| }); |
| |
| test('TestBasicModelNoPolicy', async () => { |
| const e = await getEnforcerWithPath('examples/basic_model.conf'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', false); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', false); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', false); |
| }); |
| |
| test('TestBasicModelWithRoot', async () => { |
| const e = await getEnforcerWithPath('examples/basic_with_root_model.conf', 'examples/basic_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', false); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', true); |
| await testEnforce(e, 'root', 'data1', 'read', true); |
| await testEnforce(e, 'root', 'data1', 'write', true); |
| await testEnforce(e, 'root', 'data2', 'read', true); |
| await testEnforce(e, 'root', 'data2', 'write', true); |
| }); |
| |
| test('TestBasicModelWithRootNoPolicy', async () => { |
| const e = await getEnforcerWithPath('examples/basic_with_root_model.conf'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', false); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', false); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', false); |
| await testEnforce(e, 'root', 'data1', 'read', true); |
| await testEnforce(e, 'root', 'data1', 'write', true); |
| await testEnforce(e, 'root', 'data2', 'read', true); |
| await testEnforce(e, 'root', 'data2', 'write', true); |
| }); |
| |
| test('TestBasicModelWithoutUsers', async () => { |
| const e = await getEnforcerWithPath('examples/basic_without_users_model.conf', 'examples/basic_without_users_policy.csv'); |
| |
| await testEnforceWithoutUsers(e, 'data1', 'read', true); |
| await testEnforceWithoutUsers(e, 'data1', 'write', false); |
| await testEnforceWithoutUsers(e, 'data2', 'read', false); |
| await testEnforceWithoutUsers(e, 'data2', 'write', true); |
| }); |
| |
| test('TestBasicModelWithoutResources', async () => { |
| const e = await getEnforcerWithPath('examples/basic_without_resources_model.conf', 'examples/basic_without_resources_policy.csv'); |
| |
| await testEnforceWithoutUsers(e, 'alice', 'read', true); |
| await testEnforceWithoutUsers(e, 'alice', 'write', false); |
| await testEnforceWithoutUsers(e, 'bob', 'read', false); |
| await testEnforceWithoutUsers(e, 'bob', 'write', true); |
| }); |
| |
| test('TestRBACModel', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_model.conf', 'examples/rbac_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', true); |
| await testEnforce(e, 'alice', 'data2', 'write', true); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', true); |
| }); |
| |
| test('TestRBACModelWithResourceRoles', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_with_resource_roles_model.conf', 'examples/rbac_with_resource_roles_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'alice', 'data1', 'write', true); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', true); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', true); |
| }); |
| |
| test('TestRBACModelWithDomains', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_with_domains_model.conf', 'examples/rbac_with_domains_policy.csv'); |
| |
| await testDomainEnforce(e, 'alice', 'domain1', 'data1', 'read', true); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data1', 'write', true); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data2', 'read', false); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data2', 'write', false); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data1', 'read', false); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data1', 'write', false); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data2', 'read', true); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data2', 'write', true); |
| }); |
| |
| class TestResource { |
| public Name: string; |
| public Owner: string; |
| |
| constructor(name: string, owner: string) { |
| this.Name = name; |
| this.Owner = owner; |
| } |
| } |
| |
| test('TestGlobMatchModel', async () => { |
| const e = await getEnforcerWithPath('examples/glob_model.conf', 'examples/glob_policy.csv'); |
| |
| await testEnforce(e, 'u1', '/foo', 'read', false); |
| await testEnforce(e, 'u1', '/foo/subprefix', 'read', true); |
| await testEnforce(e, 'u1', 'foo', 'read', false); |
| |
| await testEnforce(e, 'u2', '/foosubprefix', 'read', true); |
| await testEnforce(e, 'u2', '/foo/subprefix', 'read', false); |
| await testEnforce(e, 'u2', 'foo', 'read', false); |
| |
| await testEnforce(e, 'u3', '/prefix/foo/subprefix', 'read', true); |
| await testEnforce(e, 'u3', '/prefix/foo', 'read', false); |
| |
| await testEnforce(e, 'u4', '/foo', 'read', false); |
| await testEnforce(e, 'u4', 'foo', 'read', true); |
| }); |
| |
| test('TestABACModel', async () => { |
| const e = await getEnforcerWithPath('examples/abac_model.conf'); |
| |
| const data1 = new TestResource('data1', 'alice'); |
| const data2 = new TestResource('data2', 'bob'); |
| |
| await testEnforce(e, 'alice', data1, 'read', true); |
| await testEnforce(e, 'alice', data1, 'write', true); |
| await testEnforce(e, 'alice', data2, 'read', false); |
| await testEnforce(e, 'alice', data2, 'write', false); |
| await testEnforce(e, 'bob', data1, 'read', false); |
| await testEnforce(e, 'bob', data1, 'write', false); |
| await testEnforce(e, 'bob', data2, 'read', true); |
| await testEnforce(e, 'bob', data2, 'write', true); |
| }); |
| |
| test('TestKeyMatchModel', async () => { |
| const e = await getEnforcerWithPath('examples/keymatch_model.conf', 'examples/keymatch_policy.csv'); |
| |
| await testEnforce(e, 'alice', '/alice_data/resource1', 'GET', true); |
| await testEnforce(e, 'alice', '/alice_data/resource1', 'POST', true); |
| await testEnforce(e, 'alice', '/alice_data/resource2', 'GET', true); |
| await testEnforce(e, 'alice', '/alice_data/resource2', 'POST', false); |
| await testEnforce(e, 'alice', '/bob_data/resource1', 'GET', false); |
| await testEnforce(e, 'alice', '/bob_data/resource1', 'POST', false); |
| await testEnforce(e, 'alice', '/bob_data/resource2', 'GET', false); |
| await testEnforce(e, 'alice', '/bob_data/resource2', 'POST', false); |
| |
| await testEnforce(e, 'bob', '/alice_data/resource1', 'GET', false); |
| await testEnforce(e, 'bob', '/alice_data/resource1', 'POST', false); |
| await testEnforce(e, 'bob', '/alice_data/resource2', 'GET', true); |
| await testEnforce(e, 'bob', '/alice_data/resource2', 'POST', false); |
| await testEnforce(e, 'bob', '/bob_data/resource1', 'GET', false); |
| await testEnforce(e, 'bob', '/bob_data/resource1', 'POST', true); |
| await testEnforce(e, 'bob', '/bob_data/resource2', 'GET', false); |
| await testEnforce(e, 'bob', '/bob_data/resource2', 'POST', true); |
| |
| await testEnforce(e, 'cathy', '/cathy_data', 'GET', true); |
| await testEnforce(e, 'cathy', '/cathy_data', 'POST', true); |
| await testEnforce(e, 'cathy', '/cathy_data', 'DELETE', false); |
| }); |
| |
| test('TestKeyMatch2Model', async () => { |
| const e = await getEnforcerWithPath('examples/keymatch2_model.conf', 'examples/keymatch2_policy.csv'); |
| |
| await testEnforce(e, 'alice', '/alice_data', 'GET', false); |
| await testEnforce(e, 'alice', '/alice_data/resource1', 'GET', true); |
| await testEnforce(e, 'alice', '/alice_data2/myid', 'GET', false); |
| await testEnforce(e, 'alice', '/alice_data2/myid/using/res_id', 'GET', true); |
| }); |
| |
| function customFunction(key1: string, key2: string): boolean { |
| if (key1 === '/alice_data2/myid/using/res_id' && key2 === '/alice_data/:resource') { |
| return true; |
| } else return key1 === '/alice_data2/myid/using/res_id' && key2 === '/alice_data2/:id/using/:resId'; |
| } |
| |
| function customFunctionWrapper(...args: any[]): boolean { |
| const name1: string = _.toString(args[0]); |
| const name2: string = _.toString(args[1]); |
| |
| return customFunction(name1, name2); |
| } |
| |
| test('TestKeyMatchCustomModel', async () => { |
| const e = await getEnforcerWithPath('examples/keymatch_custom_model.conf', 'examples/keymatch2_policy.csv'); |
| |
| e.addFunction('keyMatchCustom', customFunctionWrapper); |
| |
| await testEnforce(e, 'alice', '/alice_data2/myid', 'GET', false); |
| await testEnforce(e, 'alice', '/alice_data2/myid/using/res_id', 'GET', true); |
| }); |
| |
| test('TestIPMatchModel', async () => { |
| const e = await getEnforcerWithPath('examples/ipmatch_model.conf', 'examples/ipmatch_policy.csv'); |
| |
| await testEnforce(e, '192.168.2.123', 'data1', 'read', true); |
| await testEnforce(e, '192.168.2.123', 'data1', 'write', false); |
| await testEnforce(e, '192.168.2.123', 'data2', 'read', false); |
| await testEnforce(e, '192.168.2.123', 'data2', 'write', false); |
| |
| await testEnforce(e, '192.168.0.123', 'data1', 'read', false); |
| await testEnforce(e, '192.168.0.123', 'data1', 'write', false); |
| await testEnforce(e, '192.168.0.123', 'data2', 'read', false); |
| await testEnforce(e, '192.168.0.123', 'data2', 'write', false); |
| |
| await testEnforce(e, '10.0.0.5', 'data1', 'read', false); |
| await testEnforce(e, '10.0.0.5', 'data1', 'write', false); |
| await testEnforce(e, '10.0.0.5', 'data2', 'read', false); |
| await testEnforce(e, '10.0.0.5', 'data2', 'write', true); |
| |
| await testEnforce(e, '192.168.0.1', 'data1', 'read', false); |
| await testEnforce(e, '192.168.0.1', 'data1', 'write', false); |
| await testEnforce(e, '192.168.0.1', 'data2', 'read', false); |
| await testEnforce(e, '192.168.0.1', 'data2', 'write', false); |
| }); |
| |
| test('TestPriorityModel', async () => { |
| const e = await getEnforcerWithPath('examples/priority_model.conf', 'examples/priority_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'alice', 'data1', 'write', false); |
| await testEnforce(e, 'alice', 'data2', 'read', false); |
| await testEnforce(e, 'alice', 'data2', 'write', false); |
| await testEnforce(e, 'bob', 'data1', 'read', false); |
| await testEnforce(e, 'bob', 'data1', 'write', false); |
| await testEnforce(e, 'bob', 'data2', 'read', true); |
| await testEnforce(e, 'bob', 'data2', 'write', false); |
| }); |
| |
| test('TestExplicitPriorityModel', async () => { |
| const e = await getEnforcerWithPath('examples/priority_model_explicit.conf', 'examples/priority_policy_explicit.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'write', true); |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', true); |
| await testEnforce(e, 'data1_deny_group', 'data1', 'read', false); |
| await testEnforce(e, 'data1_deny_group', 'data1', 'write', false); |
| await testEnforce(e, 'data2_allow_group', 'data2', 'read', true); |
| await testEnforce(e, 'data2_allow_group', 'data2', 'write', true); |
| }); |
| |
| test('TestExplicitPriorityModelAddPolicy', async () => { |
| const e = await getEnforcerWithPath('examples/priority_model_explicit.conf', 'examples/priority_policy_explicit.csv'); |
| |
| await e.addPolicy('1', 'bob', 'data2', 'write', 'deny'); |
| |
| await testEnforce(e, 'alice', 'data1', 'write', true); |
| await testEnforce(e, 'alice', 'data1', 'read', true); |
| await testEnforce(e, 'bob', 'data2', 'read', false); |
| await testEnforce(e, 'bob', 'data2', 'write', false); |
| await testEnforce(e, 'data1_deny_group', 'data1', 'read', false); |
| await testEnforce(e, 'data1_deny_group', 'data1', 'write', false); |
| await testEnforce(e, 'data2_allow_group', 'data2', 'read', true); |
| await testEnforce(e, 'data2_allow_group', 'data2', 'write', true); |
| }); |
| |
| // test('TestExplicitPriorityModelUpdatePolicy', async () => { |
| // const e = await getEnforcerWithPath('examples/priority_model_explicit.conf', 'examples/priority_policy_explicit_update.csv'); |
| // |
| // await e.updatePolicy(['1', 'bob', 'data2', 'write', 'allow'], ['1', 'bob', 'data2', 'write', 'deny']); |
| // |
| // await testEnforce(e, 'alice', 'data1', 'write', true); |
| // await testEnforce(e, 'alice', 'data1', 'read', true); |
| // await testEnforce(e, 'bob', 'data2', 'read', false); |
| // await testEnforce(e, 'bob', 'data2', 'write', false); |
| // await testEnforce(e, 'data1_deny_group', 'data1', 'read', false); |
| // await testEnforce(e, 'data1_deny_group', 'data1', 'write', false); |
| // await testEnforce(e, 'data2_allow_group', 'data2', 'read', true); |
| // await testEnforce(e, 'data2_allow_group', 'data2', 'write', true); |
| // |
| // await expect(e.updatePolicy(['1', 'bob', 'data2', 'write', 'allow'], ['2999', 'bob', 'data2', 'write', 'deny'])).resolves.toBe(false); |
| // }); TODO: implement this |
| |
| test('TestPriorityModelIndeterminate', async () => { |
| const e = await getEnforcerWithPath('examples/priority_model.conf', 'examples/priority_indeterminate_policy.csv'); |
| |
| await testEnforce(e, 'alice', 'data1', 'read', false); |
| }); |
| |
| test('TestMatcher', async () => { |
| const m = newModel(); |
| |
| m.addDef('m', 'm', 'keyMatch(r.obj, ".*get$") || regexMatch(r.act, ".user.")'); |
| |
| expect(m.model.get('m')?.get('m')?.value).toEqual(`keyMatch(r_obj, ".*get$") || regexMatch(r_act, ".user.")`); |
| }); |
| |
| test('TestRBACModelWithPattern', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_with_pattern_model.conf', 'examples/rbac_with_pattern_policy.csv'); |
| |
| // Here's a little confusing: the matching function here is not the custom function used in matcher. |
| // It is the matching function used by "g" (and "g2", "g3" if any..) |
| // You can see in policy that: "g2, /book/:id, book_group", so in "g2()" function in the matcher, instead |
| // of checking whether "/book/:id" equals the obj: "/book/1", it checks whether the pattern matches. |
| // You can see it as normal RBAC: "/book/:id" == "/book/1" becomes KeyMatch2("/book/:id", "/book/1") |
| await e.addNamedMatchingFunc('g2', keyMatch2Func); |
| await testEnforce(e, 'alice', '/book/1', 'GET', true); |
| await testEnforce(e, 'alice', '/book/2', 'GET', true); |
| await testEnforce(e, 'alice', '/pen/1', 'GET', true); |
| await testEnforce(e, 'alice', '/pen/2', 'GET', false); |
| await testEnforce(e, 'bob', '/book/1', 'GET', false); |
| await testEnforce(e, 'bob', '/book/2', 'GET', false); |
| await testEnforce(e, 'bob', '/pen/1', 'GET', true); |
| await testEnforce(e, 'bob', '/pen/2', 'GET', true); |
| |
| // AddMatchingFunc() is actually setting a function because only one function is allowed, |
| // so when we set "KeyMatch3", we are actually replacing "KeyMatch2" with "KeyMatch3". |
| // From v5.5.0, you can use addNamedMatchingFunc(), which resolve the problem above |
| await e.addNamedMatchingFunc('g2', keyMatch3Func); |
| await testEnforce(e, 'alice', '/book2/1', 'GET', true); |
| await testEnforce(e, 'alice', '/book2/2', 'GET', true); |
| await testEnforce(e, 'alice', '/pen2/1', 'GET', true); |
| await testEnforce(e, 'alice', '/pen2/2', 'GET', false); |
| await testEnforce(e, 'bob', '/book2/1', 'GET', false); |
| await testEnforce(e, 'bob', '/book2/2', 'GET', false); |
| await testEnforce(e, 'bob', '/pen2/1', 'GET', true); |
| await testEnforce(e, 'bob', '/pen2/2', 'GET', true); |
| }); |
| |
| test('TestNodeCasbin150', async () => { |
| const e = await getEnforcerWithPath('examples/issues/node_casbin_150_model.conf', 'examples/issues/node_casbin_150_policy.csv'); |
| |
| const rm = e.getRoleManager() as DefaultRoleManager; |
| await rm.addMatchingFunc(keyMatchFunc); |
| await e.buildRoleLinks(); |
| |
| await e.getImplicitRolesForUser('alice'); |
| }); |
| |
| test('TestDomainMatchModel', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_with_domain_pattern_model.conf', 'examples/rbac_with_domain_pattern_policy.csv'); |
| |
| const rm = e.getRoleManager() as DefaultRoleManager; |
| await rm.addDomainMatchingFunc(keyMatch2Func); |
| |
| await testDomainEnforce(e, 'alice', 'domain1', 'data1', 'read', true); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data1', 'write', true); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data2', 'read', false); |
| await testDomainEnforce(e, 'alice', 'domain1', 'data2', 'write', false); |
| await testDomainEnforce(e, 'alice', 'domain2', 'data2', 'read', true); |
| await testDomainEnforce(e, 'alice', 'domain2', 'data2', 'write', true); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data1', 'read', false); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data1', 'write', false); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data2', 'read', true); |
| await testDomainEnforce(e, 'bob', 'domain2', 'data2', 'write', true); |
| }); |
| |
| test('TestAllMatchModel', async () => { |
| const e = await getEnforcerWithPath('examples/rbac_with_all_pattern_model.conf', 'examples/rbac_with_all_pattern_policy.csv'); |
| |
| const rm = e.getRoleManager() as DefaultRoleManager; |
| await rm.addMatchingFunc(keyMatch2Func); |
| await rm.addDomainMatchingFunc(keyMatch2Func); |
| |
| await testDomainEnforce(e, 'alice', 'domain1', '/book/1', 'read', true); |
| await testDomainEnforce(e, 'alice', 'domain1', '/book/1', 'write', false); |
| await testDomainEnforce(e, 'alice', 'domain2', '/book/1', 'read', false); |
| await testDomainEnforce(e, 'alice', 'domain2', '/book/1', 'write', true); |
| }); |
| |
| test('ABACModelWithInOperator', async () => { |
| const e = await getEnforcerWithPath('examples/in_operator_model.conf'); |
| |
| class TestRule1 { |
| public Owner: string; |
| public Doc: string; |
| |
| constructor(Owner: string, Doc: string) { |
| this.Owner = Owner; |
| this.Doc = Doc; |
| } |
| } |
| |
| class TestRule2 { |
| public Owner: string; |
| public Docs: Array<string>; |
| |
| constructor(Owner: string, Doc: string[]) { |
| this.Owner = Owner; |
| this.Docs = Doc; |
| } |
| } |
| |
| const rule1 = new TestRule1('alice', 'aa'); |
| const rule2 = new TestRule2('alice', ['aa', 'bb']); |
| |
| await expect(e.enforce(rule1, rule2)).resolves.toBe(true); |
| }); |