mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-01 15:52:26 +03:00
Extract Shared Test Harness Module from 9 Test Files (#85)
* refactor: extract shared test harness module from 9 test files Extract duplicated test utilities into a reusable test_harness.mjs module to eliminate ~200-250 lines of boilerplate code across test files. Changes: - Create skills/clawsec-suite/test/lib/test_harness.mjs with: - Test reporting: pass(), fail(), report(), exitWithResults() - Crypto utilities: generateEd25519KeyPair(), signPayload() - Temp directory: createTempDir() with cleanup - Environment helpers: withEnv() for isolated env vars - Test runner factory: createTestRunner() for isolated counters - Refactor 9 test files to use shared harness: - feed_verification.test.mjs - guarded_install.test.mjs - skill_catalog_discovery.test.mjs - advisory_suppression.test.mjs - advisory_application_scope.test.mjs - path_resolution.test.mjs - fuzz_properties.test.mjs - suppression_config.test.mjs - render_report_suppression.test.mjs Benefits: - Single source of truth for test utilities - Consistent test reporting across all files - Easier to add new test files - Reduced maintenance burden Verification: - All 80 tests pass (15+8+3+15+4+6+1+17+11) - Zero ESLint warnings - No behavior changes - only code deduplication - Cross-skill module sharing works (openclaw-audit-watchdog → clawsec-suite) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: update minimatch override to 10.2.4 to resolve ReDoS vulnerabilities Bump minimatch from 10.2.1 to 10.2.4 in overrides to fix 10 high-severity ReDoS vulnerabilities (GHSA-7r86-cg39-jmmj, GHSA-23c5-xmqv-rm74). Also add .venv/ to ESLint ignores to prevent linting Python venv files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,3 +42,12 @@ __pycache__/
|
||||
*.sln
|
||||
*.sw?
|
||||
clawsec-signing-private.pem
|
||||
|
||||
# Auto Claude generated files
|
||||
.auto-claude/
|
||||
.auto-claude-security.json
|
||||
.auto-claude-status
|
||||
.claude_settings.json
|
||||
.worktrees/
|
||||
.security-key
|
||||
logs/security/
|
||||
|
||||
+1
-1
@@ -113,6 +113,6 @@ export default [
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['dist/', 'node_modules/', '*.config.js', 'public/']
|
||||
ignores: ['dist/', 'node_modules/', '*.config.js', 'public/', '.venv/']
|
||||
}
|
||||
];
|
||||
|
||||
Generated
+117
-256
@@ -796,19 +796,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz",
|
||||
"integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"ajv": "^6.14.0",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^10.0.1",
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"minimatch": "^3.1.3",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1380,17 +1380,16 @@
|
||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
|
||||
"integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
|
||||
"integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/type-utils": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"@typescript-eslint/scope-manager": "8.56.1",
|
||||
"@typescript-eslint/type-utils": "8.56.1",
|
||||
"@typescript-eslint/utils": "8.56.1",
|
||||
"@typescript-eslint/visitor-keys": "8.56.1",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -1403,69 +1402,21 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.55.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
|
||||
"integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
|
||||
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
|
||||
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.56.0",
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
"@typescript-eslint/typescript-estree": "8.56.0",
|
||||
"@typescript-eslint/visitor-keys": "8.56.0",
|
||||
"@typescript-eslint/scope-manager": "8.56.1",
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"@typescript-eslint/typescript-estree": "8.56.1",
|
||||
"@typescript-eslint/visitor-keys": "8.56.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1480,145 +1431,14 @@
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
|
||||
"integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.56.0",
|
||||
"@typescript-eslint/types": "^8.56.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
|
||||
"integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
"@typescript-eslint/visitor-keys": "8.56.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
|
||||
"integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
|
||||
"integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
|
||||
"integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.56.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.56.0",
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
"@typescript-eslint/visitor-keys": "8.56.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
|
||||
"integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
|
||||
"integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
|
||||
"integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
|
||||
"integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.55.0",
|
||||
"@typescript-eslint/types": "^8.55.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.56.1",
|
||||
"@typescript-eslint/types": "^8.56.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1633,14 +1453,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
|
||||
"integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
|
||||
"integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0"
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"@typescript-eslint/visitor-keys": "8.56.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1651,11 +1470,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
|
||||
"integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
@@ -1667,12 +1485,35 @@
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
|
||||
"integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
|
||||
"integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"@typescript-eslint/typescript-estree": "8.56.1",
|
||||
"@typescript-eslint/utils": "8.56.1",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
|
||||
"integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
@@ -1682,18 +1523,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
|
||||
"integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
|
||||
"integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.55.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"@typescript-eslint/project-service": "8.56.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.56.1",
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"@typescript-eslint/visitor-keys": "8.56.1",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -1709,15 +1549,37 @@
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
|
||||
"integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
|
||||
"integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.56.1",
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"@typescript-eslint/typescript-estree": "8.56.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
|
||||
"integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.56.1",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1728,13 +1590,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
||||
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
@@ -2612,9 +2473,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"version": "9.39.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz",
|
||||
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
@@ -2623,7 +2484,7 @@
|
||||
"@eslint/config-helpers": "^0.4.2",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/js": "9.39.3",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -2755,9 +2616,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/@eslint/js": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
|
||||
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
|
||||
"version": "9.39.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
|
||||
"integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -4651,16 +4512,15 @@
|
||||
]
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz",
|
||||
"integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==",
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -5321,8 +5181,9 @@
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
+1
-1
@@ -38,6 +38,6 @@
|
||||
"ajv": "6.14.0",
|
||||
"balanced-match": "4.0.3",
|
||||
"brace-expansion": "5.0.2",
|
||||
"minimatch": "10.2.1"
|
||||
"minimatch": "10.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,25 +11,12 @@
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pass, fail, report, exitWithResults } from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LIB_PATH = path.resolve(__dirname, "..", "hooks", "clawsec-advisory-guardian", "lib");
|
||||
const { advisoryAppliesToOpenclaw } = await import(`${LIB_PATH}/advisory_scope.mjs`);
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount += 1;
|
||||
console.log(`\u2713 ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount += 1;
|
||||
console.error(`\u2717 ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
function testFindMatchesFiltersByApplicationScope() {
|
||||
const testName = "advisoryAppliesToOpenclaw: openclaw + legacy advisories are considered";
|
||||
|
||||
@@ -89,10 +76,8 @@ function runTests() {
|
||||
testFindMatchesAcceptsApplicationArray();
|
||||
testInvalidApplicationValueFallsBackCompat();
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests();
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pass, fail, report, exitWithResults, createTempDir } from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LIB_PATH = path.resolve(__dirname, "..", "hooks", "clawsec-advisory-guardian", "lib");
|
||||
@@ -27,29 +27,6 @@ const { isAdvisorySuppressed, loadAdvisorySuppression } = await import(
|
||||
);
|
||||
|
||||
let tempDir;
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount++;
|
||||
console.log(`\u2713 ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount++;
|
||||
console.error(`\u2717 ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
async function setupTestDir() {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "advisory-suppression-test-"));
|
||||
}
|
||||
|
||||
async function cleanupTestDir() {
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function makeMatch(advisoryId, skillName, version = "1.0.0") {
|
||||
return {
|
||||
@@ -190,7 +167,7 @@ async function testMissingAdvisoryId() {
|
||||
async function testLoadWithAdvisorySentinel() {
|
||||
const testName = "loadAdvisorySuppression: loads config with advisory sentinel";
|
||||
try {
|
||||
const configFile = path.join(tempDir, "advisory-config.json");
|
||||
const configFile = path.join(tempDir.path, "advisory-config.json");
|
||||
await fs.writeFile(configFile, JSON.stringify({
|
||||
enabledFor: ["advisory"],
|
||||
suppressions: [{
|
||||
@@ -215,7 +192,7 @@ async function testLoadWithAdvisorySentinel() {
|
||||
async function testLoadWithMissingSentinel() {
|
||||
const testName = "loadAdvisorySuppression: missing sentinel returns empty config";
|
||||
try {
|
||||
const configFile = path.join(tempDir, "no-sentinel.json");
|
||||
const configFile = path.join(tempDir.path, "no-sentinel.json");
|
||||
await fs.writeFile(configFile, JSON.stringify({
|
||||
suppressions: [{
|
||||
checkId: "CVE-2026-25593",
|
||||
@@ -239,7 +216,7 @@ async function testLoadWithMissingSentinel() {
|
||||
async function testLoadWithAuditOnlySentinel() {
|
||||
const testName = "loadAdvisorySuppression: audit-only sentinel returns empty for advisory";
|
||||
try {
|
||||
const configFile = path.join(tempDir, "audit-only.json");
|
||||
const configFile = path.join(tempDir.path, "audit-only.json");
|
||||
await fs.writeFile(configFile, JSON.stringify({
|
||||
enabledFor: ["audit"],
|
||||
suppressions: [{
|
||||
@@ -264,7 +241,7 @@ async function testLoadWithAuditOnlySentinel() {
|
||||
async function testLoadWithBothSentinels() {
|
||||
const testName = "loadAdvisorySuppression: both audit+advisory sentinels activates advisory";
|
||||
try {
|
||||
const configFile = path.join(tempDir, "both-sentinel.json");
|
||||
const configFile = path.join(tempDir.path, "both-sentinel.json");
|
||||
await fs.writeFile(configFile, JSON.stringify({
|
||||
enabledFor: ["audit", "advisory"],
|
||||
suppressions: [{
|
||||
@@ -289,7 +266,7 @@ async function testLoadWithBothSentinels() {
|
||||
async function testLoadNonexistentExplicitPath() {
|
||||
const testName = "loadAdvisorySuppression: explicit nonexistent path throws";
|
||||
try {
|
||||
await loadAdvisorySuppression(path.join(tempDir, "does-not-exist.json"));
|
||||
await loadAdvisorySuppression(path.join(tempDir.path, "does-not-exist.json"));
|
||||
fail(testName, "Expected error for nonexistent explicit path");
|
||||
} catch (error) {
|
||||
if (String(error).includes("not found")) {
|
||||
@@ -328,7 +305,7 @@ async function testLoadNoConfigReturnsEmpty() {
|
||||
async function testEnvPathHomeExpansion() {
|
||||
const testName = "loadAdvisorySuppression: OPENCLAW_AUDIT_CONFIG expands $HOME";
|
||||
try {
|
||||
const configFile = path.join(tempDir, "env-home.json");
|
||||
const configFile = path.join(tempDir.path, "env-home.json");
|
||||
await fs.writeFile(configFile, JSON.stringify({
|
||||
enabledFor: ["advisory"],
|
||||
suppressions: [{
|
||||
@@ -341,7 +318,7 @@ async function testEnvPathHomeExpansion() {
|
||||
|
||||
const savedConfig = process.env.OPENCLAW_AUDIT_CONFIG;
|
||||
const savedHome = process.env.HOME;
|
||||
process.env.HOME = tempDir;
|
||||
process.env.HOME = tempDir.path;
|
||||
process.env.OPENCLAW_AUDIT_CONFIG = "$HOME/env-home.json";
|
||||
try {
|
||||
const config = await loadAdvisorySuppression();
|
||||
@@ -390,7 +367,7 @@ async function testEscapedHomeTokenRejected() {
|
||||
async function runAllTests() {
|
||||
console.log("=== Advisory Suppression Tests ===\n");
|
||||
|
||||
await setupTestDir();
|
||||
tempDir = await createTempDir();
|
||||
|
||||
try {
|
||||
// isAdvisorySuppressed tests
|
||||
@@ -412,15 +389,11 @@ async function runAllTests() {
|
||||
await testEnvPathHomeExpansion();
|
||||
await testEscapedHomeTokenRejected();
|
||||
} finally {
|
||||
await cleanupTestDir();
|
||||
await tempDir.cleanup();
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log(`=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runAllTests().catch((err) => {
|
||||
|
||||
@@ -14,9 +14,17 @@
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
pass,
|
||||
fail,
|
||||
report,
|
||||
exitWithResults,
|
||||
generateEd25519KeyPair,
|
||||
signPayload,
|
||||
createTempDir,
|
||||
} from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LIB_PATH = path.resolve(__dirname, "..", "hooks", "clawsec-advisory-guardian", "lib");
|
||||
@@ -26,33 +34,7 @@ const { verifySignedPayload, loadLocalFeed, isValidFeedPayload } = await import(
|
||||
`${LIB_PATH}/feed.mjs`
|
||||
);
|
||||
|
||||
let tempDir;
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount++;
|
||||
console.log(`✓ ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount++;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
function generateEd25519KeyPair() {
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||||
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
||||
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
||||
return { publicKeyPem, privateKeyPem };
|
||||
}
|
||||
|
||||
function signPayload(data, privateKeyPem) {
|
||||
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||
const signature = crypto.sign(null, Buffer.from(data, "utf8"), privateKey);
|
||||
return signature.toString("base64");
|
||||
}
|
||||
let tempDirCleanup;
|
||||
|
||||
function createValidFeed() {
|
||||
return JSON.stringify(
|
||||
@@ -88,16 +70,6 @@ function createChecksumManifest(files) {
|
||||
);
|
||||
}
|
||||
|
||||
async function setupTestDir() {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawsec-test-"));
|
||||
}
|
||||
|
||||
async function cleanupTestDir() {
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Test: verifySignedPayload - valid signature
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -252,11 +224,11 @@ async function testLoadLocalFeed_ValidSignedFeed() {
|
||||
const checksumSignature = signPayload(checksumManifest, privateKeyPem);
|
||||
|
||||
// Write files
|
||||
const feedPath = path.join(tempDir, "feed.json");
|
||||
const sigPath = path.join(tempDir, "feed.json.sig");
|
||||
const checksumPath = path.join(tempDir, "checksums.json");
|
||||
const checksumSigPath = path.join(tempDir, "checksums.json.sig");
|
||||
const keyPath = path.join(tempDir, "feed-signing-public.pem");
|
||||
const feedPath = path.join(globalThis.__testTempDir, "feed.json");
|
||||
const sigPath = path.join(globalThis.__testTempDir, "feed.json.sig");
|
||||
const checksumPath = path.join(globalThis.__testTempDir, "checksums.json");
|
||||
const checksumSigPath = path.join(globalThis.__testTempDir, "checksums.json.sig");
|
||||
const keyPath = path.join(globalThis.__testTempDir, "feed-signing-public.pem");
|
||||
|
||||
await fs.writeFile(feedPath, feedContent);
|
||||
await fs.writeFile(sigPath, feedSignature + "\n");
|
||||
@@ -293,7 +265,7 @@ async function testLoadLocalFeed_AdvisoriesPrefixedChecksumKeys() {
|
||||
const feedContent = createValidFeed();
|
||||
const feedSignature = signPayload(feedContent, privateKeyPem);
|
||||
|
||||
const advisoriesDir = path.join(tempDir, "advisories");
|
||||
const advisoriesDir = path.join(globalThis.__testTempDir, "advisories");
|
||||
await fs.mkdir(advisoriesDir, { recursive: true });
|
||||
|
||||
const checksumManifest = createChecksumManifest({
|
||||
@@ -347,8 +319,8 @@ async function testLoadLocalFeed_TamperedFeedFails() {
|
||||
// Tamper with feed after signing
|
||||
const tamperedFeed = feedContent.replace("TEST-001", "TAMPERED-001");
|
||||
|
||||
const feedPath = path.join(tempDir, "tampered-feed.json");
|
||||
const sigPath = path.join(tempDir, "tampered-feed.json.sig");
|
||||
const feedPath = path.join(globalThis.__testTempDir, "tampered-feed.json");
|
||||
const sigPath = path.join(globalThis.__testTempDir, "tampered-feed.json.sig");
|
||||
|
||||
await fs.writeFile(feedPath, tamperedFeed);
|
||||
await fs.writeFile(sigPath, feedSignature + "\n");
|
||||
@@ -383,8 +355,8 @@ async function testLoadLocalFeed_MissingSignatureFails() {
|
||||
const { publicKeyPem } = generateEd25519KeyPair();
|
||||
const feedContent = createValidFeed();
|
||||
|
||||
const feedPath = path.join(tempDir, "nosig-feed.json");
|
||||
const sigPath = path.join(tempDir, "nosig-feed.json.sig");
|
||||
const feedPath = path.join(globalThis.__testTempDir, "nosig-feed.json");
|
||||
const sigPath = path.join(globalThis.__testTempDir, "nosig-feed.json.sig");
|
||||
|
||||
await fs.writeFile(feedPath, feedContent);
|
||||
// Don't write signature file
|
||||
@@ -418,7 +390,7 @@ async function testLoadLocalFeed_AllowUnsignedBypasses() {
|
||||
try {
|
||||
const feedContent = createValidFeed();
|
||||
|
||||
const feedPath = path.join(tempDir, "unsigned-feed.json");
|
||||
const feedPath = path.join(globalThis.__testTempDir, "unsigned-feed.json");
|
||||
await fs.writeFile(feedPath, feedContent);
|
||||
|
||||
const feed = await loadLocalFeed(feedPath, {
|
||||
@@ -462,10 +434,10 @@ async function testLoadLocalFeed_ChecksumMismatchFails() {
|
||||
);
|
||||
const checksumSignature = signPayload(badChecksumManifest, privateKeyPem);
|
||||
|
||||
const feedPath = path.join(tempDir, "badcs-feed.json");
|
||||
const sigPath = path.join(tempDir, "badcs-feed.json.sig");
|
||||
const checksumPath = path.join(tempDir, "badcs-checksums.json");
|
||||
const checksumSigPath = path.join(tempDir, "badcs-checksums.json.sig");
|
||||
const feedPath = path.join(globalThis.__testTempDir, "badcs-feed.json");
|
||||
const sigPath = path.join(globalThis.__testTempDir, "badcs-feed.json.sig");
|
||||
const checksumPath = path.join(globalThis.__testTempDir, "badcs-checksums.json");
|
||||
const checksumSigPath = path.join(globalThis.__testTempDir, "badcs-checksums.json.sig");
|
||||
|
||||
await fs.writeFile(feedPath, feedContent);
|
||||
await fs.writeFile(sigPath, feedSignature + "\n");
|
||||
@@ -580,7 +552,11 @@ async function testIsValidFeedPayload_AdvisoryMissingId() {
|
||||
async function runTests() {
|
||||
console.log("=== ClawSec Feed Verification Tests ===\n");
|
||||
|
||||
await setupTestDir();
|
||||
const tempDir = await createTempDir();
|
||||
tempDirCleanup = tempDir.cleanup;
|
||||
|
||||
// Store temp dir path in module scope for tests to access
|
||||
globalThis.__testTempDir = tempDir.path;
|
||||
|
||||
try {
|
||||
// Signature verification tests
|
||||
@@ -604,14 +580,11 @@ async function runTests() {
|
||||
await testIsValidFeedPayload_MissingVersion();
|
||||
await testIsValidFeedPayload_AdvisoryMissingId();
|
||||
} finally {
|
||||
await cleanupTestDir();
|
||||
await tempDirCleanup();
|
||||
}
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
* Run: node skills/clawsec-suite/test/fuzz_properties.test.mjs
|
||||
*/
|
||||
|
||||
import { pass, fail, report, exitWithResults } from "./lib/test_harness.mjs";
|
||||
import { runFuzzProperties } from "./fuzz_properties.js";
|
||||
|
||||
console.log("=== ClawSec Fast-Check Fuzz Properties ===\n");
|
||||
|
||||
try {
|
||||
console.log("=== ClawSec Fast-Check Fuzz Properties ===\n");
|
||||
runFuzzProperties();
|
||||
console.log("=== Results: all fuzz properties passed ===");
|
||||
pass("Property-based fuzz tests");
|
||||
} catch (error) {
|
||||
console.error("Fuzz property test failed:");
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
fail("Property-based fuzz tests", error);
|
||||
}
|
||||
|
||||
report();
|
||||
exitWithResults();
|
||||
|
||||
@@ -18,37 +18,19 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
pass,
|
||||
fail,
|
||||
report,
|
||||
exitWithResults,
|
||||
generateEd25519KeyPair,
|
||||
signPayload,
|
||||
} from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const SCRIPT_PATH = path.resolve(__dirname, "..", "scripts", "guarded_skill_install.mjs");
|
||||
|
||||
let tempDir;
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount++;
|
||||
console.log(`✓ ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount++;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
function generateEd25519KeyPair() {
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||||
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
||||
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
||||
return { publicKeyPem, privateKeyPem };
|
||||
}
|
||||
|
||||
function signPayload(data, privateKeyPem) {
|
||||
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||
const signature = crypto.sign(null, Buffer.from(data, "utf8"), privateKey);
|
||||
return signature.toString("base64");
|
||||
}
|
||||
|
||||
function createFeed(advisories) {
|
||||
return JSON.stringify(
|
||||
@@ -416,11 +398,8 @@ async function runTests() {
|
||||
await cleanupTestDir();
|
||||
}
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Shared test harness for clawsec-suite tests.
|
||||
* Provides consistent test reporting and runner utilities.
|
||||
*/
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
/**
|
||||
* Records a passing test.
|
||||
* @param {string} name - Test name
|
||||
*/
|
||||
export function pass(name) {
|
||||
passCount++;
|
||||
console.log(`✓ ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a failing test.
|
||||
* @param {string} name - Test name
|
||||
* @param {Error|string} error - Error details
|
||||
*/
|
||||
export function fail(name, error) {
|
||||
failCount++;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current test statistics.
|
||||
* @returns {{passCount: number, failCount: number}}
|
||||
*/
|
||||
export function getStats() {
|
||||
return { passCount, failCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports final test results to console.
|
||||
*/
|
||||
export function report() {
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits with appropriate code based on test results.
|
||||
* Exit code 0 for success, 1 for failures.
|
||||
*/
|
||||
export function exitWithResults() {
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an isolated test runner with its own pass/fail counters.
|
||||
* Useful for running independent test suites within the same process.
|
||||
* @returns {{pass: Function, fail: Function, getStats: Function, report: Function, exitWithResults: Function}}
|
||||
*/
|
||||
export function createTestRunner() {
|
||||
let localPassCount = 0;
|
||||
let localFailCount = 0;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Records a passing test.
|
||||
* @param {string} name - Test name
|
||||
*/
|
||||
pass(name) {
|
||||
localPassCount++;
|
||||
console.log(`✓ ${name}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Records a failing test.
|
||||
* @param {string} name - Test name
|
||||
* @param {Error|string} error - Error details
|
||||
*/
|
||||
fail(name, error) {
|
||||
localFailCount++;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets current test statistics.
|
||||
* @returns {{passCount: number, failCount: number}}
|
||||
*/
|
||||
getStats() {
|
||||
return { passCount: localPassCount, failCount: localFailCount };
|
||||
},
|
||||
|
||||
/**
|
||||
* Reports final test results to console.
|
||||
*/
|
||||
report() {
|
||||
console.log(`\n=== Results: ${localPassCount} passed, ${localFailCount} failed ===`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Exits with appropriate code based on test results.
|
||||
* Exit code 0 for success, 1 for failures.
|
||||
*/
|
||||
exitWithResults() {
|
||||
if (localFailCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an Ed25519 keypair for test use.
|
||||
* @returns {{publicKeyPem: string, privateKeyPem: string}}
|
||||
*/
|
||||
export function generateEd25519KeyPair() {
|
||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
||||
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
||||
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
||||
return { publicKeyPem, privateKeyPem };
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a payload with an Ed25519 private key.
|
||||
* @param {string} data - Data to sign
|
||||
* @param {string} privateKeyPem - PEM-encoded private key
|
||||
* @returns {string} Base64-encoded signature
|
||||
*/
|
||||
export function signPayload(data, privateKeyPem) {
|
||||
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||
const signature = crypto.sign(null, Buffer.from(data, "utf8"), privateKey);
|
||||
return signature.toString("base64");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary directory for test use.
|
||||
* @returns {Promise<{path: string, cleanup: Function}>} Object with temp dir path and cleanup function
|
||||
*/
|
||||
export async function createTempDir() {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawsec-test-"));
|
||||
|
||||
return {
|
||||
path: tmpDir,
|
||||
cleanup: async () => {
|
||||
try {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily sets an environment variable for the duration of a function.
|
||||
* Restores the original value (or deletes the variable) after the function completes.
|
||||
* @param {string} key - Environment variable name
|
||||
* @param {string|undefined} value - Value to set (undefined to delete)
|
||||
* @param {Function} fn - Function to execute with the modified environment
|
||||
* @returns {Promise<*>} Result of the function
|
||||
*/
|
||||
export async function withEnv(key, value, fn) {
|
||||
const oldValue = process.env[key];
|
||||
try {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
return await fn();
|
||||
} finally {
|
||||
if (oldValue === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,43 +8,12 @@
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pass, fail, report, exitWithResults, withEnv } from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const LIB_PATH = path.resolve(__dirname, "..", "hooks", "clawsec-advisory-guardian", "lib");
|
||||
const { resolveUserPath, resolveConfiguredPath } = await import(`${LIB_PATH}/utils.mjs`);
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount += 1;
|
||||
console.log(`\u2713 ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount += 1;
|
||||
console.error(`\u2717 ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
async function withEnv(key, value, fn) {
|
||||
const oldValue = process.env[key];
|
||||
try {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
return await fn();
|
||||
} finally {
|
||||
if (oldValue === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function testTildeExpansion() {
|
||||
const testName = "resolveUserPath: expands leading tilde";
|
||||
await withEnv("HOME", "/tmp/clawsec-home", async () => {
|
||||
@@ -157,10 +126,8 @@ async function runTests() {
|
||||
await testConfiguredPathFallbackOnInvalidExplicit();
|
||||
await testConfiguredPathUsesValidExplicit();
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
|
||||
@@ -15,24 +15,11 @@ import http from "node:http";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pass, fail, report, exitWithResults } from "./lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const SCRIPT_PATH = path.resolve(__dirname, "..", "scripts", "discover_skill_catalog.mjs");
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount += 1;
|
||||
console.log(`✓ ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount += 1;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
function runCatalogScript(args, env = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const proc = spawn("node", [SCRIPT_PATH, ...args], {
|
||||
@@ -237,11 +224,8 @@ async function runTests() {
|
||||
await testInvalidRemotePayloadFallsBack();
|
||||
await testUnreachableRemoteFallsBack();
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
|
||||
@@ -16,39 +16,16 @@
|
||||
*/
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pass, fail, report, exitWithResults, createTempDir } from "../../clawsec-suite/test/lib/test_harness.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const SCRIPT_PATH = path.resolve(__dirname, "..", "scripts", "render_report.mjs");
|
||||
const NODE_BIN = process.execPath;
|
||||
|
||||
let tempDir;
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount++;
|
||||
console.log(`✓ ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount++;
|
||||
console.error(`✗ ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
async function setupTestDir() {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "render-report-test-"));
|
||||
}
|
||||
|
||||
async function cleanupTestDir() {
|
||||
if (tempDir) {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function createAuditJson(findings) {
|
||||
return JSON.stringify({
|
||||
@@ -730,7 +707,8 @@ async function testConfigWithoutEnableFlagDoesNotSuppress() {
|
||||
// Main test runner
|
||||
// -----------------------------------------------------------------------------
|
||||
async function runAllTests() {
|
||||
await setupTestDir();
|
||||
const tmpDir = await createTempDir();
|
||||
tempDir = tmpDir.path;
|
||||
|
||||
try {
|
||||
await testSuppressedFindingsDisplayed();
|
||||
@@ -745,16 +723,11 @@ async function runAllTests() {
|
||||
await testEmptySuppressions();
|
||||
await testConfigWithoutEnableFlagDoesNotSuppress();
|
||||
} finally {
|
||||
await cleanupTestDir();
|
||||
await tmpDir.cleanup();
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log(`Passed: ${passCount}`);
|
||||
console.log(`Failed: ${failCount}`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runAllTests().catch((err) => {
|
||||
|
||||
@@ -19,57 +19,33 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import {
|
||||
pass,
|
||||
fail,
|
||||
report,
|
||||
exitWithResults,
|
||||
createTempDir,
|
||||
withEnv,
|
||||
} from "../../clawsec-suite/test/lib/test_harness.mjs";
|
||||
import { loadSuppressionConfig } from "../scripts/load_suppression_config.mjs";
|
||||
|
||||
let passCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
function pass(name) {
|
||||
passCount += 1;
|
||||
console.log(`\u2713 ${name}`);
|
||||
}
|
||||
|
||||
function fail(name, error) {
|
||||
failCount += 1;
|
||||
console.error(`\u2717 ${name}`);
|
||||
console.error(` ${String(error)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary file with the given content.
|
||||
* Wrapper around createTempDir for test config file creation.
|
||||
* @param {string} content - File content
|
||||
* @returns {Promise<{path: string, cleanup: Function}>}
|
||||
*/
|
||||
async function withTempFile(content) {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
||||
const tmpFile = path.join(tmpDir, "test-config.json");
|
||||
const tmpDir = await createTempDir();
|
||||
const tmpFile = path.join(tmpDir.path, "test-config.json");
|
||||
await fs.writeFile(tmpFile, content, "utf8");
|
||||
|
||||
return {
|
||||
path: tmpFile,
|
||||
cleanup: async () => {
|
||||
try {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
},
|
||||
cleanup: tmpDir.cleanup,
|
||||
};
|
||||
}
|
||||
|
||||
async function withEnv(key, value, fn) {
|
||||
const oldValue = process.env[key];
|
||||
try {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
return await fn();
|
||||
} finally {
|
||||
if (oldValue === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Suppress stderr output during a function call (avoids noisy warnings in test output). */
|
||||
async function silenceStderr(fn) {
|
||||
const original = process.stderr.write;
|
||||
@@ -748,11 +724,8 @@ async function runTests() {
|
||||
await testMissingSentinel();
|
||||
await testWrongSentinel();
|
||||
|
||||
console.log(`\n=== Results: ${passCount} passed, ${failCount} failed ===`);
|
||||
|
||||
if (failCount > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
report();
|
||||
exitWithResults();
|
||||
}
|
||||
|
||||
runTests().catch((error) => {
|
||||
|
||||
Reference in New Issue
Block a user