Skip to content

Commit

Permalink
feat: set up a test in CI to verify whether all Casbin engines return…
Browse files Browse the repository at this point in the history
… the same response in all scenarios. (#185)

* chore: add Jest configuration and update package.json for testing setup

* feat: add Casbin engine cross-engine enforcement consistency tests

* test: enhance error handling and logging in Casbin engine tests

* refactor: simplify error handling and remove redundant try-catch in casbinEngine tests

* chore: add cross-engine test workflow for Casbin

* chore: add `add_jest_configuration` branch to cross-engine-test workflow triggers

* chore: remove add_jest_configuration branch from cross-engine-test workflow
  • Loading branch information
HashCookie authored Jan 2, 2025
1 parent 7353505 commit 1bd5e26
Show file tree
Hide file tree
Showing 7 changed files with 1,774 additions and 19 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/cross-engine-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Casbin Cross-Engine Tests

on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20.8.1'

- name: Install dependencies
run: yarn install

- name: Run tests
run: yarn test
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ next-env.d.ts

# editor
.idea
test
12 changes: 12 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
};
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"lint": "next lint",
"electron": "electron .",
"dist": "yarn build && electron-builder",
"release": "npx -p semantic-release -p @semantic-release/git -p @semantic-release/changelog -p @semantic-release/exec semantic-release"
"release": "npx -p semantic-release -p @semantic-release/git -p @semantic-release/changelog -p @semantic-release/exec semantic-release",
"test": "jest",
"test:watch": "jest --watch"
},
"build": {
"appId": "org.casbin.editor",
Expand Down Expand Up @@ -95,6 +97,7 @@
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand All @@ -103,9 +106,11 @@
"electron-builder": "^24.13.3",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"jest": "^29.7.0",
"postcss": "^8",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5"
},
"browser": {
Expand Down
60 changes: 60 additions & 0 deletions test/casbinEngine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { newEnforcer, newModel, StringAdapter } from 'casbin';
import { RemoteCasbinEngine } from '../app/components/editor/CasbinEngine';
import { example } from '../app/components/editor/casbin-mode/example';

describe('Casbin Engine Tests', () => {
describe('Cross-engine enforcement consistency', () => {
Object.entries(example).forEach(([key, testCase]) => {
test(`should return consistent enforcement result for ${testCase.name}`, async () => {
const nodeEnforcer = await newEnforcer(newModel(testCase.model), new StringAdapter(testCase.policy || ' '));

const remoteEngines = {
java: new RemoteCasbinEngine('java'),
go: new RemoteCasbinEngine('go'),
};

const requests = testCase.request.split('\n').filter(Boolean);

for (const request of requests) {
const requestParams = request.split(',').map((param) => {return param.trim()});
const nodeResult = await nodeEnforcer.enforce(...requestParams);

for (const [engineType, engine] of Object.entries(remoteEngines)) {
try {
const adjustedRequestParams = [...requestParams];
if (engineType === 'go' && adjustedRequestParams.length < 3) {
adjustedRequestParams.push('');
}

const remoteResult = await engine.enforce({
model: testCase.model,
policy: testCase.policy || ' ',
request: adjustedRequestParams.join(','),
});

if (remoteResult.error) {
throw new Error(`${engineType} engine error: ${remoteResult.error}`);
}

console.log(`${testCase.name} - ${engineType} complete response:`, {
request: adjustedRequestParams,
response: remoteResult,
nodeResult: nodeResult,
});

expect(remoteResult.allowed).toBe(nodeResult);
} catch (engineError: any) {
console.error(`${testCase.name} - ${engineType} engine error:`, {
error: engineError.message,
request: requestParams,
model: testCase.model,
policy: testCase.policy,
});
throw engineError;
}
}
}
}, 10000);
});
});
});
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
],
"paths": {
"@/*": ["./*"]
}
},
"baseUrl": ".",
"types": ["jest", "node"]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
Expand Down
Loading

0 comments on commit 1bd5e26

Please sign in to comment.