@howlil/ez-agents 3.4.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +735 -462
- package/agents/ez-architect-agent.md +267 -0
- package/agents/ez-backend-agent.md +303 -0
- package/agents/ez-chief-strategist.md +271 -0
- package/agents/ez-codebase-mapper.md +770 -770
- package/agents/ez-context-manager.md +319 -0
- package/agents/ez-debugger.md +1255 -1255
- package/agents/ez-design-expert.md +347 -0
- package/agents/ez-devops-agent.md +331 -0
- package/agents/ez-executor.md +487 -487
- package/agents/ez-frontend-agent.md +322 -0
- package/agents/ez-phase-researcher.md +553 -553
- package/agents/ez-planner.md +1307 -1307
- package/agents/ez-product-engineer.md +435 -0
- package/agents/ez-project-researcher.md +629 -629
- package/agents/ez-qa-agent.md +320 -0
- package/agents/ez-release-agent.md +333 -0
- package/agents/ez-requirements-agent.md +377 -0
- package/agents/ez-roadmapper.md +650 -650
- package/agents/ez-technical-writer.md +551 -0
- package/agents/ez-ux-expert.md +393 -0
- package/agents/ez-verifier.md +579 -579
- package/bin/guards/autonomy-guard.cjs +346 -0
- package/bin/guards/context-budget-guard.cjs +278 -0
- package/bin/guards/hallucination-guard.cjs +380 -0
- package/bin/guards/hidden-state-guard.cjs +182 -0
- package/bin/guards/team-overhead-guard.cjs +266 -0
- package/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/bin/install.js +3221 -3272
- package/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/bin/lib/archetype-detector.cjs +289 -0
- package/bin/lib/assistant-adapter.cjs +361 -0
- package/bin/lib/audit-exec.cjs +175 -0
- package/bin/lib/auth.cjs +176 -0
- package/bin/lib/backup-service.cjs +422 -0
- package/bin/lib/bdd-validator.cjs +622 -0
- package/bin/lib/business-flow-mapper.cjs +429 -0
- package/bin/lib/circuit-breaker.cjs +276 -0
- package/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/bin/lib/codebase-analyzer.cjs +241 -0
- package/bin/lib/commands.cjs +691 -0
- package/bin/lib/config.cjs +236 -0
- package/bin/lib/constraint-extractor.cjs +526 -0
- package/bin/lib/content-scanner.cjs +238 -0
- package/bin/lib/context-cache.cjs +154 -0
- package/bin/lib/context-compressor.cjs +102 -0
- package/bin/lib/context-deduplicator.cjs +105 -0
- package/bin/lib/context-errors.cjs +78 -0
- package/bin/lib/context-manager.cjs +338 -0
- package/bin/lib/context-metadata-tracker.cjs +140 -0
- package/bin/lib/context-relevance-scorer.cjs +99 -0
- package/bin/lib/core.cjs +507 -0
- package/bin/lib/cost-alerts.cjs +174 -0
- package/bin/lib/cost-tracker.cjs +275 -0
- package/bin/lib/crash-recovery.cjs +220 -0
- package/bin/lib/dependency-graph.cjs +319 -0
- package/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/bin/lib/deploy/deploy-status.cjs +74 -0
- package/bin/lib/discussion-synthesizer.cjs +439 -0
- package/bin/lib/error-cache.cjs +114 -0
- package/bin/lib/error-registry.cjs +177 -0
- package/bin/lib/file-access.cjs +207 -0
- package/bin/lib/file-lock.cjs +236 -0
- package/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/bin/lib/finops/cost-reporter.cjs +132 -0
- package/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/bin/lib/finops/spot-manager.cjs +118 -0
- package/bin/lib/framework-detector.cjs +396 -0
- package/bin/lib/frontmatter.cjs +313 -0
- package/bin/lib/fs-utils.cjs +153 -0
- package/bin/lib/gate-executor.cjs +272 -0
- package/bin/lib/gates/README.md +374 -0
- package/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/bin/lib/gates/gate-03-code.cjs +635 -0
- package/bin/lib/gates/gate-04-security.cjs +829 -0
- package/bin/lib/git-errors.cjs +83 -0
- package/bin/lib/git-utils.cjs +321 -0
- package/bin/lib/git-workflow-engine.cjs +1157 -0
- package/bin/lib/health-check.cjs +227 -0
- package/bin/lib/index.cjs +279 -0
- package/bin/lib/init.cjs +725 -0
- package/bin/lib/lock-logger.cjs +194 -0
- package/bin/lib/lock-state.cjs +263 -0
- package/bin/lib/lockfile-validator.cjs +227 -0
- package/bin/lib/log-rotation.cjs +71 -0
- package/bin/lib/logger.cjs +125 -0
- package/bin/lib/memory-compression.cjs +256 -0
- package/bin/lib/milestone.cjs +247 -0
- package/bin/lib/model-provider.cjs +241 -0
- package/bin/lib/package-manager-detector.cjs +203 -0
- package/bin/lib/package-manager-executor.cjs +385 -0
- package/bin/lib/package-manager-service.cjs +216 -0
- package/bin/lib/perf/api-monitor.cjs +88 -0
- package/bin/lib/perf/db-optimizer.cjs +78 -0
- package/bin/lib/perf/frontend-performance.cjs +56 -0
- package/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/bin/lib/perf/perf-baseline.cjs +102 -0
- package/bin/lib/perf/perf-reporter.cjs +117 -0
- package/bin/lib/perf/regression-detector.cjs +92 -0
- package/bin/lib/phase.cjs +963 -0
- package/bin/lib/planning-write.cjs +123 -0
- package/bin/lib/project-reporter.cjs +565 -0
- package/bin/lib/quality-gate.cjs +332 -0
- package/bin/lib/quality-metrics.cjs +324 -0
- package/bin/lib/recovery-manager.cjs +98 -0
- package/bin/lib/release-validator.cjs +617 -0
- package/bin/lib/retry.cjs +119 -0
- package/bin/lib/roadmap.cjs +309 -0
- package/bin/lib/safe-exec.cjs +173 -0
- package/bin/lib/safe-path.cjs +130 -0
- package/bin/lib/security-errors.cjs +62 -0
- package/bin/lib/session-chain.cjs +304 -0
- package/bin/lib/session-errors.cjs +81 -0
- package/bin/lib/session-export.cjs +251 -0
- package/bin/lib/session-import.cjs +262 -0
- package/bin/lib/session-manager.cjs +280 -0
- package/bin/lib/skill-context.cjs +148 -0
- package/bin/lib/skill-matcher.cjs +236 -0
- package/bin/lib/skill-registry.cjs +360 -0
- package/bin/lib/skill-resolver.cjs +449 -0
- package/bin/lib/skill-triggers.cjs +90 -0
- package/bin/lib/skill-validator.cjs +270 -0
- package/bin/lib/skill-versioning.cjs +355 -0
- package/bin/lib/stack-detector.cjs +399 -0
- package/bin/lib/state.cjs +736 -0
- package/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/bin/lib/temp-file.cjs +239 -0
- package/bin/lib/template.cjs +223 -0
- package/bin/lib/test-file-lock.cjs +112 -0
- package/bin/lib/test-graceful.cjs +93 -0
- package/bin/lib/test-logger.cjs +60 -0
- package/bin/lib/test-safe-exec.cjs +38 -0
- package/bin/lib/test-safe-path.cjs +33 -0
- package/bin/lib/test-temp-file.cjs +125 -0
- package/bin/lib/tier-manager.cjs +428 -0
- package/bin/lib/timeout-exec.cjs +63 -0
- package/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/bin/lib/url-fetch.cjs +170 -0
- package/bin/lib/verify.cjs +863 -0
- package/bin/update.js +217 -214
- package/commands/deploy.cjs +53 -0
- package/commands/ez/add-tests.md +41 -41
- package/commands/ez/audit-milestone.md +36 -36
- package/commands/ez/complete-milestone.md +136 -136
- package/commands/ez/discuss-phase.md +90 -90
- package/commands/ez/execute-phase.md +52 -41
- package/commands/ez/help.md +22 -22
- package/commands/ez/map-codebase.md +71 -71
- package/commands/ez/new-milestone.md +44 -44
- package/commands/ez/new-project.md +51 -42
- package/commands/ez/plan-phase.md +53 -45
- package/commands/ez/progress.md +36 -24
- package/commands/ez/quick.md +45 -45
- package/commands/ez/resume-work.md +40 -40
- package/commands/ez/run-phase.md +580 -0
- package/commands/ez/settings.md +36 -36
- package/commands/ez/update.md +37 -37
- package/commands/ez/verify-work.md +402 -38
- package/commands/health-check.cjs +44 -0
- package/commands/rollback.cjs +47 -0
- package/ez-agents/bin/ez-tools.cjs +1692 -716
- package/ez-agents/bin/guards/autonomy-guard.cjs +346 -0
- package/ez-agents/bin/guards/context-budget-guard.cjs +247 -0
- package/ez-agents/bin/guards/hallucination-guard.cjs +271 -0
- package/ez-agents/bin/guards/hidden-state-guard.cjs +182 -0
- package/ez-agents/bin/guards/team-overhead-guard.cjs +266 -0
- package/ez-agents/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/ez-agents/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/ez-agents/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/ez-agents/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/ez-agents/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/ez-agents/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/ez-agents/bin/lib/archetype-detector.cjs +289 -0
- package/ez-agents/bin/lib/audit-exec.cjs +166 -167
- package/ez-agents/bin/lib/auth.cjs +176 -176
- package/ez-agents/bin/lib/backup-service.cjs +422 -0
- package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
- package/ez-agents/bin/lib/business-flow-mapper.cjs +429 -0
- package/ez-agents/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/ez-agents/bin/lib/codebase-analyzer.cjs +241 -0
- package/ez-agents/bin/lib/commands.cjs +685 -685
- package/ez-agents/bin/lib/config.cjs +41 -1
- package/ez-agents/bin/lib/constraint-extractor.cjs +526 -0
- package/ez-agents/bin/lib/content-scanner.cjs +238 -0
- package/ez-agents/bin/lib/context-cache.cjs +154 -0
- package/ez-agents/bin/lib/context-errors.cjs +71 -0
- package/ez-agents/bin/lib/context-manager.cjs +220 -0
- package/ez-agents/bin/lib/core.cjs +507 -512
- package/ez-agents/bin/lib/cost-tracker.cjs +243 -0
- package/ez-agents/bin/lib/crash-recovery.cjs +172 -0
- package/ez-agents/bin/lib/dependency-graph.cjs +319 -0
- package/ez-agents/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/ez-agents/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/ez-agents/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/ez-agents/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/ez-agents/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/ez-agents/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/ez-agents/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/ez-agents/bin/lib/deploy/deploy-status.cjs +74 -0
- package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -0
- package/ez-agents/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/ez-agents/bin/lib/finops/cost-reporter.cjs +132 -0
- package/ez-agents/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/ez-agents/bin/lib/finops/spot-manager.cjs +118 -0
- package/ez-agents/bin/lib/framework-detector.cjs +396 -0
- package/ez-agents/bin/lib/frontmatter.cjs +3 -1
- package/ez-agents/bin/lib/gates/README.md +374 -0
- package/ez-agents/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/ez-agents/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/ez-agents/bin/lib/gates/gate-03-code.cjs +635 -0
- package/ez-agents/bin/lib/gates/gate-04-security.cjs +829 -0
- package/ez-agents/bin/lib/git-errors.cjs +83 -0
- package/ez-agents/bin/lib/git-utils.cjs +118 -0
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
- package/ez-agents/bin/lib/health-check.cjs +162 -162
- package/ez-agents/bin/lib/index.cjs +40 -2
- package/ez-agents/bin/lib/init.cjs +0 -2
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
- package/ez-agents/bin/lib/log-rotation.cjs +71 -0
- package/ez-agents/bin/lib/logger.cjs +99 -154
- package/ez-agents/bin/lib/memory-compression.cjs +256 -0
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
- package/ez-agents/bin/lib/perf/api-monitor.cjs +88 -0
- package/ez-agents/bin/lib/perf/db-optimizer.cjs +78 -0
- package/ez-agents/bin/lib/perf/frontend-performance.cjs +56 -0
- package/ez-agents/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/ez-agents/bin/lib/perf/perf-baseline.cjs +102 -0
- package/ez-agents/bin/lib/perf/perf-reporter.cjs +117 -0
- package/ez-agents/bin/lib/perf/regression-detector.cjs +92 -0
- package/ez-agents/bin/lib/project-reporter.cjs +502 -0
- package/ez-agents/bin/lib/quality-gate.cjs +332 -0
- package/ez-agents/bin/lib/recovery-manager.cjs +98 -0
- package/ez-agents/bin/lib/release-validator.cjs +617 -0
- package/ez-agents/bin/lib/safe-exec.cjs +128 -214
- package/ez-agents/bin/lib/security-errors.cjs +62 -0
- package/ez-agents/bin/lib/session-chain.cjs +304 -0
- package/ez-agents/bin/lib/session-errors.cjs +81 -0
- package/ez-agents/bin/lib/session-export.cjs +251 -0
- package/ez-agents/bin/lib/session-import.cjs +262 -0
- package/ez-agents/bin/lib/session-manager.cjs +280 -0
- package/ez-agents/bin/lib/skill-context.cjs +148 -0
- package/ez-agents/bin/lib/skill-matcher.cjs +236 -0
- package/ez-agents/bin/lib/skill-registry.cjs +341 -0
- package/ez-agents/bin/lib/skill-resolver.cjs +449 -0
- package/ez-agents/bin/lib/skill-triggers.cjs +90 -0
- package/ez-agents/bin/lib/skill-validator.cjs +270 -0
- package/ez-agents/bin/lib/skill-versioning.cjs +355 -0
- package/ez-agents/bin/lib/stack-detector.cjs +399 -0
- package/ez-agents/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/ez-agents/bin/lib/tier-manager.cjs +428 -0
- package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/ez-agents/bin/lib/url-fetch.cjs +170 -0
- package/ez-agents/bin/lib/verify.cjs +863 -863
- package/ez-agents/references/decimal-phase-calculation.md +65 -65
- package/ez-agents/references/git-integration.md +248 -248
- package/ez-agents/references/git-planning-commit.md +38 -38
- package/ez-agents/references/metrics-schema.md +118 -0
- package/ez-agents/references/model-profile-resolution.md +34 -34
- package/ez-agents/references/model-profiles.md +93 -93
- package/ez-agents/references/phase-argument-parsing.md +61 -61
- package/ez-agents/references/planning-config.md +340 -200
- package/ez-agents/references/tier-strategy.md +103 -0
- package/ez-agents/references/ui-brand.md +160 -160
- package/ez-agents/references/verification-patterns.md +612 -612
- package/ez-agents/templates/DEBUG.md +164 -164
- package/ez-agents/templates/UAT.md +247 -247
- package/ez-agents/templates/agent-output-format.md +404 -0
- package/ez-agents/templates/bdd-feature.md +173 -0
- package/ez-agents/templates/codebase/architecture.md +255 -255
- package/ez-agents/templates/codebase/structure.md +285 -285
- package/ez-agents/templates/copilot-instructions.md +7 -7
- package/ez-agents/templates/debug-subagent-prompt.md +91 -91
- package/ez-agents/templates/discovery.md +146 -146
- package/ez-agents/templates/discussion.md +68 -0
- package/ez-agents/templates/handoff-protocol.md +294 -0
- package/ez-agents/templates/incident-runbook.md +205 -0
- package/ez-agents/templates/mode-workflow-templates.md +301 -0
- package/ez-agents/templates/phase-prompt.md +610 -610
- package/ez-agents/templates/planner-subagent-prompt.md +117 -117
- package/ez-agents/templates/project.md +184 -184
- package/ez-agents/templates/release-checklist.md +136 -0
- package/ez-agents/templates/research.md +552 -552
- package/ez-agents/templates/rollback-plan.md +201 -0
- package/ez-agents/templates/security-user-setup.md +244 -0
- package/ez-agents/templates/skill-validation-rules.md +476 -0
- package/ez-agents/templates/state.md +180 -176
- package/ez-agents/templates/summary-complex.md +59 -59
- package/ez-agents/tests/gates/gate-01-02.test.cjs +812 -0
- package/ez-agents/tests/gates/gate-03-04.test.cjs +762 -0
- package/ez-agents/tests/gates/gate-05-validator.test.cjs +145 -0
- package/ez-agents/tests/gates/gate-06-docs-validator.test.cjs +244 -0
- package/ez-agents/tests/gates/gate-07-release-validator.test.cjs +219 -0
- package/ez-agents/tests/guards/context-budget-guard.test.cjs +145 -0
- package/ez-agents/tests/guards/edge-case-guards.test.cjs +238 -0
- package/ez-agents/tests/guards/hallucination-guard.test.cjs +124 -0
- package/ez-agents/workflows/audit-milestone.md +1 -1
- package/ez-agents/workflows/autonomous.md +131 -30
- package/ez-agents/workflows/complete-milestone.md +1 -1
- package/ez-agents/workflows/discuss-phase.md +1 -1
- package/ez-agents/workflows/execute-phase.md +169 -3
- package/ez-agents/workflows/help.md +86 -133
- package/ez-agents/workflows/hotfix.md +291 -0
- package/ez-agents/workflows/new-milestone.md +340 -11
- package/ez-agents/workflows/new-project.md +294 -318
- package/ez-agents/workflows/plan-phase.md +22 -40
- package/ez-agents/workflows/progress.md +15 -25
- package/ez-agents/workflows/release.md +253 -0
- package/ez-agents/workflows/resume-session.md +215 -0
- package/ez-agents/workflows/run-phase.md +531 -0
- package/ez-agents/workflows/settings.md +2 -35
- package/hooks/dist/ez-check-update.js +81 -81
- package/hooks/dist/ez-context-monitor.js +148 -141
- package/hooks/dist/ez-statusline.js +115 -115
- package/package.json +78 -64
- package/scripts/fix-qwen-installation.js +144 -144
- package/agents/ez-integration-checker.md +0 -443
- package/agents/ez-nyquist-auditor.md +0 -176
- package/agents/ez-plan-checker.md +0 -706
- package/agents/ez-research-synthesizer.md +0 -247
- package/agents/ez-ui-auditor.md +0 -439
- package/agents/ez-ui-checker.md +0 -300
- package/agents/ez-ui-researcher.md +0 -353
- package/commands/ez/add-phase.md +0 -43
- package/commands/ez/add-todo.md +0 -47
- package/commands/ez/auth.md +0 -87
- package/commands/ez/autonomous.md +0 -41
- package/commands/ez/check-todos.md +0 -45
- package/commands/ez/cleanup.md +0 -18
- package/commands/ez/debug.md +0 -168
- package/commands/ez/health.md +0 -22
- package/commands/ez/insert-phase.md +0 -32
- package/commands/ez/join-discord.md +0 -18
- package/commands/ez/list-phase-assumptions.md +0 -46
- package/commands/ez/pause-work.md +0 -38
- package/commands/ez/plan-milestone-gaps.md +0 -34
- package/commands/ez/reapply-patches.md +0 -124
- package/commands/ez/remove-phase.md +0 -31
- package/commands/ez/research-phase.md +0 -190
- package/commands/ez/set-profile.md +0 -34
- package/commands/ez/stats.md +0 -18
- package/commands/ez/ui-phase.md +0 -34
- package/commands/ez/ui-review.md +0 -32
- package/commands/ez/validate-phase.md +0 -35
- package/ez-agents/templates/UI-SPEC.md +0 -100
- package/ez-agents/templates/VALIDATION.md +0 -76
- package/ez-agents/templates/context.md +0 -352
- package/ez-agents/templates/verification-report.md +0 -322
- package/ez-agents/workflows/research-phase.md +0 -74
- package/ez-agents/workflows/ui-phase.md +0 -290
- package/ez-agents/workflows/ui-review.md +0 -157
- package/ez-agents/workflows/validate-phase.md +0 -167
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate 3-4 Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for Code Quality (Gate 3) and Security Baseline (Gate 4)
|
|
5
|
+
*
|
|
6
|
+
* Test cases:
|
|
7
|
+
* Gate 3:
|
|
8
|
+
* 1. Gate 3 passes for clean code with short functions
|
|
9
|
+
* 2. Gate 3 flags functions > 50 lines
|
|
10
|
+
* 3. Gate 3 flags nesting > 4 levels
|
|
11
|
+
* 4. Gate 3 detects magic numbers without named constants
|
|
12
|
+
* 5. Gate 3 detects generic helper sprawl
|
|
13
|
+
*
|
|
14
|
+
* Gate 4:
|
|
15
|
+
* 6. Gate 4 passes when auth uses secure session management
|
|
16
|
+
* 7. Gate 4 flags hardcoded credentials
|
|
17
|
+
* 8. Gate 4 flags eval() usage
|
|
18
|
+
* 9. Gate 4 flags execSync with user input
|
|
19
|
+
* 10. Gate 4 flags SQL query concatenation
|
|
20
|
+
* 11. Gate 4 passes when env vars used for secrets
|
|
21
|
+
* 12. Gate 4 flags missing input validation
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
25
|
+
const { strict: assert } = require('node:assert');
|
|
26
|
+
|
|
27
|
+
// Import Gate 3
|
|
28
|
+
const {
|
|
29
|
+
executeGate3,
|
|
30
|
+
checkFunctionLength,
|
|
31
|
+
calculateNestingDepth,
|
|
32
|
+
detectMagicNumbers,
|
|
33
|
+
detectGenericHelperSprawl,
|
|
34
|
+
THRESHOLDS,
|
|
35
|
+
} = require('../../bin/lib/gates/gate-03-code.cjs');
|
|
36
|
+
|
|
37
|
+
// Import Gate 4
|
|
38
|
+
const {
|
|
39
|
+
executeGate4,
|
|
40
|
+
detectEvalUsage,
|
|
41
|
+
detectExecUsage,
|
|
42
|
+
detectSqlInjection,
|
|
43
|
+
detectHardcodedSecrets,
|
|
44
|
+
checkSessionSecurity,
|
|
45
|
+
checkEnvVarUsage,
|
|
46
|
+
} = require('../../bin/lib/gates/gate-04-security.cjs');
|
|
47
|
+
|
|
48
|
+
// Import QualityGate for integration test
|
|
49
|
+
const { QualityGate } = require('../../bin/lib/quality-gate.cjs');
|
|
50
|
+
const { registerGate3 } = require('../../bin/lib/gates/gate-03-code.cjs');
|
|
51
|
+
const { registerGate4 } = require('../../bin/lib/gates/gate-04-security.cjs');
|
|
52
|
+
|
|
53
|
+
describe('Gate 3: Code Quality', () => {
|
|
54
|
+
describe('executeGate3', () => {
|
|
55
|
+
it('Gate 3 passes for clean code with short functions', async () => {
|
|
56
|
+
const context = {
|
|
57
|
+
files: [
|
|
58
|
+
{
|
|
59
|
+
path: '/src/utils.js',
|
|
60
|
+
content: `
|
|
61
|
+
function add(a, b) {
|
|
62
|
+
return a + b;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function greet(name) {
|
|
66
|
+
return 'Hello, ' + name;
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
69
|
+
functions: [
|
|
70
|
+
{
|
|
71
|
+
name: 'add',
|
|
72
|
+
startLine: 1,
|
|
73
|
+
endLine: 3,
|
|
74
|
+
body: 'function add(a, b) {\n return a + b;\n}',
|
|
75
|
+
parameters: ['a', 'b'],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'greet',
|
|
79
|
+
startLine: 5,
|
|
80
|
+
endLine: 7,
|
|
81
|
+
body: "function greet(name) {\n return 'Hello, ' + name;\n}",
|
|
82
|
+
parameters: ['name'],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const result = await executeGate3(context);
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(result.passed, true);
|
|
92
|
+
assert.strictEqual(result.errors.length, 0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('Gate 3 flags functions > 50 lines', async () => {
|
|
96
|
+
// Generate a function with 55 lines
|
|
97
|
+
const longFunctionBody = 'function longFunction() {\n' +
|
|
98
|
+
Array(55).fill(' console.log("line");').join('\n') +
|
|
99
|
+
'\n}';
|
|
100
|
+
|
|
101
|
+
const context = {
|
|
102
|
+
files: [
|
|
103
|
+
{
|
|
104
|
+
path: '/src/long.js',
|
|
105
|
+
content: longFunctionBody,
|
|
106
|
+
functions: [
|
|
107
|
+
{
|
|
108
|
+
name: 'longFunction',
|
|
109
|
+
startLine: 1,
|
|
110
|
+
endLine: 56,
|
|
111
|
+
body: longFunctionBody,
|
|
112
|
+
parameters: [],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = await executeGate3(context);
|
|
120
|
+
|
|
121
|
+
assert.strictEqual(result.passed, false);
|
|
122
|
+
assert.ok(result.errors.length > 0);
|
|
123
|
+
assert.ok(result.errors.some(e => e.message.includes('longFunction')));
|
|
124
|
+
assert.ok(result.errors.some(e => e.message.includes('lines')));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('Gate 3 flags nesting > 4 levels', async () => {
|
|
128
|
+
const deeplyNestedCode = `
|
|
129
|
+
function process() {
|
|
130
|
+
if (true) {
|
|
131
|
+
for (let i = 0; i < 10; i++) {
|
|
132
|
+
while (true) {
|
|
133
|
+
switch (x) {
|
|
134
|
+
case 1:
|
|
135
|
+
if (y) {
|
|
136
|
+
console.log('too deep');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const context = {
|
|
146
|
+
files: [
|
|
147
|
+
{
|
|
148
|
+
path: '/src/nested.js',
|
|
149
|
+
content: deeplyNestedCode,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = await executeGate3(context);
|
|
155
|
+
|
|
156
|
+
assert.strictEqual(result.passed, false);
|
|
157
|
+
assert.ok(result.errors.length > 0);
|
|
158
|
+
assert.ok(result.errors.some(e => e.message.includes('Nesting')));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('Gate 3 detects magic numbers without named constants', async () => {
|
|
162
|
+
const codeWithMagicNumbers = `
|
|
163
|
+
function calculate() {
|
|
164
|
+
const tax = 1250 * 0.0875;
|
|
165
|
+
const discount = price * 0.15;
|
|
166
|
+
const shipping = 49.99;
|
|
167
|
+
return tax + discount + shipping;
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
const context = {
|
|
172
|
+
files: [
|
|
173
|
+
{
|
|
174
|
+
path: '/src/calc.js',
|
|
175
|
+
content: codeWithMagicNumbers,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
namedConstants: [],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const result = await executeGate3(context);
|
|
182
|
+
|
|
183
|
+
assert.strictEqual(result.passed, true); // Magic numbers are warnings, not errors
|
|
184
|
+
assert.ok(result.warnings.length > 0);
|
|
185
|
+
assert.ok(result.warnings.some(w => w.includes('Magic number')));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('Gate 3 detects generic helper sprawl', async () => {
|
|
189
|
+
const context = {
|
|
190
|
+
genericHelpers: [
|
|
191
|
+
{ name: 'utils', usageCount: 5 },
|
|
192
|
+
{ name: 'helpers', usageCount: 10 },
|
|
193
|
+
{ name: 'common', usageCount: 0 },
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const result = await executeGate3(context);
|
|
198
|
+
|
|
199
|
+
assert.strictEqual(result.passed, true); // Helper sprawl is a warning
|
|
200
|
+
assert.ok(result.warnings.length > 0);
|
|
201
|
+
assert.ok(result.warnings.some(w => w.includes('Generic helper') || w.includes('utils') || w.includes('helpers')));
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('checkFunctionLength', () => {
|
|
206
|
+
it('should pass for short functions', () => {
|
|
207
|
+
const shortBody = 'function test() {\n return 1;\n}';
|
|
208
|
+
const result = checkFunctionLength(shortBody);
|
|
209
|
+
assert.strictEqual(result.exceeds, false);
|
|
210
|
+
assert.strictEqual(result.length, 3);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should fail for long functions', () => {
|
|
214
|
+
const longBody = 'function test() {\n' +
|
|
215
|
+
Array(60).fill(' console.log("line");').join('\n') +
|
|
216
|
+
'\n}';
|
|
217
|
+
const result = checkFunctionLength(longBody);
|
|
218
|
+
assert.strictEqual(result.exceeds, true);
|
|
219
|
+
assert.ok(result.length > THRESHOLDS.maxFunctionLength);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('calculateNestingDepth', () => {
|
|
224
|
+
it('should calculate nesting depth correctly', () => {
|
|
225
|
+
const code = `
|
|
226
|
+
function test() {
|
|
227
|
+
if (true) {
|
|
228
|
+
for (let i = 0; i < 10; i++) {
|
|
229
|
+
while (true) {
|
|
230
|
+
console.log('deep');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
const depth = calculateNestingDepth(code);
|
|
237
|
+
// The function counts brace nesting: function->if->for->while = 4 levels
|
|
238
|
+
// But subtracts 1 for the outermost scope, so expected is 3
|
|
239
|
+
assert.strictEqual(depth, 3); // if -> for -> while
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should return 0 for flat code', () => {
|
|
243
|
+
const code = 'const x = 1;\nconst y = 2;';
|
|
244
|
+
const depth = calculateNestingDepth(code);
|
|
245
|
+
assert.strictEqual(depth, 0);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle shallow nesting', () => {
|
|
249
|
+
const code = `
|
|
250
|
+
if (true) {
|
|
251
|
+
console.log('hello');
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
const depth = calculateNestingDepth(code);
|
|
255
|
+
// Single if block = 1 level of braces, but no outer function scope
|
|
256
|
+
// So depth is 0 (we subtract 1 for the outermost scope)
|
|
257
|
+
assert.strictEqual(depth, 0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('detectMagicNumbers', () => {
|
|
262
|
+
it('should detect magic numbers', () => {
|
|
263
|
+
const code = 'const x = 42; const y = 100;';
|
|
264
|
+
const magicNumbers = detectMagicNumbers(code);
|
|
265
|
+
assert.ok(magicNumbers.length > 0);
|
|
266
|
+
assert.ok(magicNumbers.some(m => m.value === 42));
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should ignore 0, 1, 2', () => {
|
|
270
|
+
const code = 'for (let i = 0; i < 10; i += 1) { const x = 2; }';
|
|
271
|
+
const magicNumbers = detectMagicNumbers(code);
|
|
272
|
+
// 10 should still be detected
|
|
273
|
+
assert.ok(magicNumbers.some(m => m.value === 10));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should skip named constants', () => {
|
|
277
|
+
const code = 'const TAX_RATE = 0.0875; const tax = price * TAX_RATE;';
|
|
278
|
+
const namedConstants = [{ name: 'TAX_RATE', value: 0.0875 }];
|
|
279
|
+
const magicNumbers = detectMagicNumbers(code, namedConstants);
|
|
280
|
+
// Should not flag 0.0875 since it's a named constant
|
|
281
|
+
assert.strictEqual(magicNumbers.length, 0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should skip numbers in strings', () => {
|
|
285
|
+
const code = 'const msg = "Price is 99.99"; const x = 42;';
|
|
286
|
+
const magicNumbers = detectMagicNumbers(code);
|
|
287
|
+
// Should only flag 42, not 99.99 in string
|
|
288
|
+
assert.ok(magicNumbers.some(m => m.value === 42));
|
|
289
|
+
assert.ok(!magicNumbers.some(m => m.value === 99.99));
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('detectGenericHelperSprawl', () => {
|
|
294
|
+
it('should detect sprawl for generic helper names', () => {
|
|
295
|
+
const helpers = [
|
|
296
|
+
{ name: 'utils', usageCount: 5 },
|
|
297
|
+
{ name: 'helpers', usageCount: 10 },
|
|
298
|
+
];
|
|
299
|
+
const issues = detectGenericHelperSprawl(helpers);
|
|
300
|
+
assert.ok(issues.length > 0);
|
|
301
|
+
assert.ok(issues.some(i => i.name === 'utils' || i.name === 'helpers'));
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should not flag specific helper names', () => {
|
|
305
|
+
const helpers = [
|
|
306
|
+
{ name: 'stringUtils', usageCount: 5 },
|
|
307
|
+
{ name: 'dateHelpers', usageCount: 3 },
|
|
308
|
+
];
|
|
309
|
+
const issues = detectGenericHelperSprawl(helpers);
|
|
310
|
+
assert.strictEqual(issues.length, 0);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should flag unused generic helpers', () => {
|
|
314
|
+
const helpers = [
|
|
315
|
+
{ name: 'utils', usageCount: 0 },
|
|
316
|
+
];
|
|
317
|
+
const issues = detectGenericHelperSprawl(helpers);
|
|
318
|
+
assert.ok(issues.length > 0);
|
|
319
|
+
assert.ok(issues.some(i => i.issue.includes('Unused')));
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('Gate 4: Security Baseline', () => {
|
|
325
|
+
describe('executeGate4', () => {
|
|
326
|
+
it('Gate 4 passes when auth uses secure session management', async () => {
|
|
327
|
+
const secureCode = `
|
|
328
|
+
const session = require('express-session');
|
|
329
|
+
const RedisStore = require('connect-redis')(session);
|
|
330
|
+
|
|
331
|
+
app.use(session({
|
|
332
|
+
store: new RedisStore({ client: redisClient }),
|
|
333
|
+
secret: process.env.SESSION_SECRET,
|
|
334
|
+
resave: false,
|
|
335
|
+
saveUninitialized: false,
|
|
336
|
+
cookie: {
|
|
337
|
+
secure: true,
|
|
338
|
+
httpOnly: true,
|
|
339
|
+
sameSite: 'strict',
|
|
340
|
+
maxAge: 3600000
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
`;
|
|
344
|
+
|
|
345
|
+
const context = {
|
|
346
|
+
files: [
|
|
347
|
+
{
|
|
348
|
+
path: '/src/auth.js',
|
|
349
|
+
content: secureCode,
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
authConfig: {
|
|
353
|
+
method: 'session',
|
|
354
|
+
sessionStore: 'redis',
|
|
355
|
+
httpsEnforced: true,
|
|
356
|
+
csrfProtection: true,
|
|
357
|
+
hashingAlgorithm: 'bcrypt',
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const result = await executeGate4(context);
|
|
362
|
+
|
|
363
|
+
assert.strictEqual(result.passed, true);
|
|
364
|
+
assert.strictEqual(result.errors.length, 0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('Gate 4 flags hardcoded credentials', async () => {
|
|
368
|
+
const insecureCode = `
|
|
369
|
+
const config = {
|
|
370
|
+
dbPassword: 'super_secret_password123',
|
|
371
|
+
apiKey: 'sk_live_abcdef1234567890abcdef',
|
|
372
|
+
awsKey: 'AKIAIOSFODNN7EXAMPLE'
|
|
373
|
+
};
|
|
374
|
+
`;
|
|
375
|
+
|
|
376
|
+
const context = {
|
|
377
|
+
files: [
|
|
378
|
+
{
|
|
379
|
+
path: '/src/config.js',
|
|
380
|
+
content: insecureCode,
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const result = await executeGate4(context);
|
|
386
|
+
|
|
387
|
+
assert.strictEqual(result.passed, false);
|
|
388
|
+
assert.ok(result.errors.length > 0);
|
|
389
|
+
assert.ok(result.errors.some(e => e.message.includes('password') || e.message.includes('secret') || e.message.includes('API')));
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('Gate 4 flags eval() usage', async () => {
|
|
393
|
+
const codeWithEval = `
|
|
394
|
+
function processUserInput(input) {
|
|
395
|
+
const result = eval(input);
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
const context = {
|
|
401
|
+
files: [
|
|
402
|
+
{
|
|
403
|
+
path: '/src/dangerous.js',
|
|
404
|
+
content: codeWithEval,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const result = await executeGate4(context);
|
|
410
|
+
|
|
411
|
+
assert.strictEqual(result.passed, false);
|
|
412
|
+
assert.ok(result.errors.length > 0);
|
|
413
|
+
assert.ok(result.errors.some(e => e.message.includes('eval')));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('Gate 4 flags execSync with user input', async () => {
|
|
417
|
+
const codeWithExec = `
|
|
418
|
+
const { execSync } = require('child_process');
|
|
419
|
+
|
|
420
|
+
function runCommand(userInput) {
|
|
421
|
+
const result = execSync('echo ' + userInput, { encoding: 'utf-8' });
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
const context = {
|
|
427
|
+
files: [
|
|
428
|
+
{
|
|
429
|
+
path: '/src/exec.js',
|
|
430
|
+
content: codeWithExec,
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const result = await executeGate4(context);
|
|
436
|
+
|
|
437
|
+
assert.strictEqual(result.passed, false);
|
|
438
|
+
assert.ok(result.errors.length > 0);
|
|
439
|
+
assert.ok(result.errors.some(e => e.message.includes('execSync') || e.message.includes('command injection')));
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('Gate 4 flags SQL query concatenation', async () => {
|
|
443
|
+
const codeWithSqlInjection = `
|
|
444
|
+
function getUserById(id) {
|
|
445
|
+
const query = 'SELECT * FROM users WHERE id = ' + id;
|
|
446
|
+
return db.query(query);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function searchUsers(name) {
|
|
450
|
+
return db.execute(\`SELECT * FROM users WHERE name = '\${name}'\`);
|
|
451
|
+
}
|
|
452
|
+
`;
|
|
453
|
+
|
|
454
|
+
const context = {
|
|
455
|
+
files: [
|
|
456
|
+
{
|
|
457
|
+
path: '/src/db.js',
|
|
458
|
+
content: codeWithSqlInjection,
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const result = await executeGate4(context);
|
|
464
|
+
|
|
465
|
+
assert.strictEqual(result.passed, false);
|
|
466
|
+
assert.ok(result.errors.length > 0);
|
|
467
|
+
assert.ok(result.errors.some(e => e.message.includes('SQL') || e.message.includes('parameterized')));
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('Gate 4 passes when env vars used for secrets', async () => {
|
|
471
|
+
const secureCode = `
|
|
472
|
+
const config = {
|
|
473
|
+
dbPassword: process.env.DB_PASSWORD,
|
|
474
|
+
apiKey: process.env.API_KEY,
|
|
475
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
476
|
+
sessionSecret: process.env.SESSION_SECRET
|
|
477
|
+
};
|
|
478
|
+
`;
|
|
479
|
+
|
|
480
|
+
const context = {
|
|
481
|
+
files: [
|
|
482
|
+
{
|
|
483
|
+
path: '/src/config.js',
|
|
484
|
+
content: secureCode,
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const result = await executeGate4(context);
|
|
490
|
+
|
|
491
|
+
assert.strictEqual(result.passed, true);
|
|
492
|
+
assert.strictEqual(result.errors.length, 0);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('Gate 4 flags missing input validation', async () => {
|
|
496
|
+
const codeWithoutValidation = `
|
|
497
|
+
app.post('/api/users', (req, res) => {
|
|
498
|
+
const { name, email, age } = req.body;
|
|
499
|
+
db.insert({ name, email, age });
|
|
500
|
+
res.json({ success: true });
|
|
501
|
+
});
|
|
502
|
+
`;
|
|
503
|
+
|
|
504
|
+
const context = {
|
|
505
|
+
files: [
|
|
506
|
+
{
|
|
507
|
+
path: '/src/routes.js',
|
|
508
|
+
content: codeWithoutValidation,
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
hasInputValidation: false,
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const result = await executeGate4(context);
|
|
515
|
+
|
|
516
|
+
assert.strictEqual(result.passed, false);
|
|
517
|
+
assert.ok(result.errors.length > 0);
|
|
518
|
+
assert.ok(result.errors.some(e => e.message.includes('input validation') || e.message.includes('Validation')));
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe('detectEvalUsage', () => {
|
|
523
|
+
it('should detect eval() calls', () => {
|
|
524
|
+
const code = 'const result = eval("1 + 1");';
|
|
525
|
+
const issues = detectEvalUsage(code);
|
|
526
|
+
assert.ok(issues.length > 0);
|
|
527
|
+
assert.ok(issues.some(i => i.type === 'eval'));
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should detect Function constructor', () => {
|
|
531
|
+
const code = 'const fn = new Function("a", "b", "return a + b");';
|
|
532
|
+
const issues = detectEvalUsage(code);
|
|
533
|
+
assert.ok(issues.length > 0);
|
|
534
|
+
assert.ok(issues.some(i => i.type === 'function-constructor'));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should return empty for safe code', () => {
|
|
538
|
+
const code = 'const result = JSON.parse("{\"x\": 1}");';
|
|
539
|
+
const issues = detectEvalUsage(code);
|
|
540
|
+
assert.strictEqual(issues.length, 0);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
describe('detectExecUsage', () => {
|
|
545
|
+
it('should detect execSync with user input', () => {
|
|
546
|
+
const code = 'execSync("ls " + req.params.path);';
|
|
547
|
+
const issues = detectExecUsage(code);
|
|
548
|
+
assert.ok(issues.length > 0);
|
|
549
|
+
assert.ok(issues.some(i => i.type === 'execSync-user-input'));
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('should warn about execSync without obvious user input', () => {
|
|
553
|
+
const code = 'execSync("npm install");';
|
|
554
|
+
const issues = detectExecUsage(code);
|
|
555
|
+
assert.ok(issues.length > 0);
|
|
556
|
+
assert.ok(issues.some(i => i.severity === 'warning'));
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
describe('detectSqlInjection', () => {
|
|
561
|
+
it('should detect string concatenation in SQL', () => {
|
|
562
|
+
const code = "db.query('SELECT * FROM users WHERE id = ' + userId);";
|
|
563
|
+
const issues = detectSqlInjection(code);
|
|
564
|
+
assert.ok(issues.length > 0);
|
|
565
|
+
assert.ok(issues.some(i => i.message.includes('SQL')));
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should detect template literal SQL injection', () => {
|
|
569
|
+
const code = 'db.execute(`SELECT * FROM users WHERE name = "${name}"`);';
|
|
570
|
+
const issues = detectSqlInjection(code);
|
|
571
|
+
assert.ok(issues.length > 0);
|
|
572
|
+
assert.ok(issues.some(i => i.message.includes('parameterized')));
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe('detectHardcodedSecrets', () => {
|
|
577
|
+
it('should detect AWS keys', () => {
|
|
578
|
+
const code = "const key = 'AKIAIOSFODNN7EXAMPLE';";
|
|
579
|
+
const issues = detectHardcodedSecrets(code);
|
|
580
|
+
assert.ok(issues.length > 0);
|
|
581
|
+
assert.ok(issues.some(i => i.message.includes('AWS')));
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should detect API keys', () => {
|
|
585
|
+
const code = "const apiKey = 'api_key_1234567890abcdef';";
|
|
586
|
+
const issues = detectHardcodedSecrets(code);
|
|
587
|
+
assert.ok(issues.length > 0);
|
|
588
|
+
assert.ok(issues.some(i => i.message.includes('API key')));
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should detect passwords', () => {
|
|
592
|
+
const code = "const password = 'mySecretPassword123';";
|
|
593
|
+
const issues = detectHardcodedSecrets(code);
|
|
594
|
+
assert.ok(issues.length > 0);
|
|
595
|
+
assert.ok(issues.some(i => i.message.toLowerCase().includes('password')));
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should skip process.env usage', () => {
|
|
599
|
+
const code = "const secret = process.env.SECRET_KEY;";
|
|
600
|
+
const issues = detectHardcodedSecrets(code);
|
|
601
|
+
assert.strictEqual(issues.length, 0);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should skip example values', () => {
|
|
605
|
+
const code = "const apiKey = 'YOUR_API_KEY_HERE';";
|
|
606
|
+
const issues = detectHardcodedSecrets(code);
|
|
607
|
+
assert.strictEqual(issues.length, 0);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe('checkSessionSecurity', () => {
|
|
612
|
+
it('should detect insecure session config', () => {
|
|
613
|
+
const code = `
|
|
614
|
+
app.use(session({
|
|
615
|
+
cookie: {
|
|
616
|
+
secure: false,
|
|
617
|
+
httpOnly: false
|
|
618
|
+
}
|
|
619
|
+
}));
|
|
620
|
+
`;
|
|
621
|
+
const result = checkSessionSecurity(code);
|
|
622
|
+
assert.strictEqual(result.secure, false);
|
|
623
|
+
assert.ok(result.issues.length > 0);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('should flag memory session store', () => {
|
|
627
|
+
const code = 'app.use(session({}));';
|
|
628
|
+
const result = checkSessionSecurity(code, { sessionStore: 'memory' });
|
|
629
|
+
assert.strictEqual(result.secure, false);
|
|
630
|
+
assert.ok(result.issues.some(i => i.message.includes('memory')));
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should flag weak hashing algorithm', () => {
|
|
634
|
+
const code = 'app.use(session({}));';
|
|
635
|
+
const result = checkSessionSecurity(code, { hashingAlgorithm: 'md5' });
|
|
636
|
+
assert.strictEqual(result.secure, false);
|
|
637
|
+
assert.ok(result.issues.some(i => i.message.includes('md5') || i.message.includes('Weak')));
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
describe('checkEnvVarUsage', () => {
|
|
642
|
+
it('should recognize process.env usage', () => {
|
|
643
|
+
const code = 'const secret = process.env.SECRET_KEY;';
|
|
644
|
+
const result = checkEnvVarUsage(code);
|
|
645
|
+
assert.strictEqual(result.usesEnvVars, true);
|
|
646
|
+
assert.strictEqual(result.issues.length, 0);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('should flag hardcoded sensitive values', () => {
|
|
650
|
+
const code = "const password = 'secret123';";
|
|
651
|
+
const result = checkEnvVarUsage(code);
|
|
652
|
+
assert.ok(result.issues.length > 0);
|
|
653
|
+
assert.ok(result.issues.some(i => i.message.includes('password')));
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
describe('Gate Integration Tests', () => {
|
|
659
|
+
let gates;
|
|
660
|
+
|
|
661
|
+
beforeEach(() => {
|
|
662
|
+
gates = new QualityGate();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it('Gate 3 registers with QualityGate coordinator', () => {
|
|
666
|
+
registerGate3(gates);
|
|
667
|
+
|
|
668
|
+
const registeredGates = gates.getRegisteredGates();
|
|
669
|
+
assert.ok(registeredGates.includes('gate-03-code'));
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('Gate 4 registers with QualityGate coordinator', () => {
|
|
673
|
+
registerGate4(gates);
|
|
674
|
+
|
|
675
|
+
const registeredGates = gates.getRegisteredGates();
|
|
676
|
+
assert.ok(registeredGates.includes('gate-04-security'));
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
it('Both gates execute successfully through coordinator', async () => {
|
|
680
|
+
registerGate3(gates);
|
|
681
|
+
registerGate4(gates);
|
|
682
|
+
|
|
683
|
+
const gate3Context = {
|
|
684
|
+
files: [
|
|
685
|
+
{
|
|
686
|
+
path: '/src/clean.js',
|
|
687
|
+
content: 'function add(a, b) { return a + b; }',
|
|
688
|
+
functions: [
|
|
689
|
+
{ name: 'add', startLine: 1, endLine: 1, body: 'function add(a, b) { return a + b; }', parameters: ['a', 'b'] },
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const gate4Context = {
|
|
696
|
+
files: [
|
|
697
|
+
{
|
|
698
|
+
path: '/src/secure.js',
|
|
699
|
+
content: 'const secret = process.env.SECRET_KEY;',
|
|
700
|
+
},
|
|
701
|
+
],
|
|
702
|
+
authConfig: {
|
|
703
|
+
hashingAlgorithm: 'bcrypt',
|
|
704
|
+
httpsEnforced: true,
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const gate3Result = await gates.executeGate('gate-03-code', gate3Context);
|
|
709
|
+
const gate4Result = await gates.executeGate('gate-04-security', gate4Context);
|
|
710
|
+
|
|
711
|
+
assert.strictEqual(gate3Result.passed, true);
|
|
712
|
+
assert.strictEqual(gate4Result.passed, true);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('Gate 3 fails through coordinator for long functions', async () => {
|
|
716
|
+
registerGate3(gates);
|
|
717
|
+
|
|
718
|
+
const longFunctionBody = 'function longFunction() {\n' +
|
|
719
|
+
Array(60).fill(' console.log("line");').join('\n') +
|
|
720
|
+
'\n}';
|
|
721
|
+
|
|
722
|
+
const context = {
|
|
723
|
+
files: [
|
|
724
|
+
{
|
|
725
|
+
path: '/src/long.js',
|
|
726
|
+
content: longFunctionBody,
|
|
727
|
+
functions: [
|
|
728
|
+
{ name: 'longFunction', startLine: 1, endLine: 61, body: longFunctionBody, parameters: [] },
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
],
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const result = await gates.executeGate('gate-03-code', context);
|
|
735
|
+
|
|
736
|
+
assert.strictEqual(result.passed, false);
|
|
737
|
+
assert.ok(result.errors.length > 0);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
it('Gate 4 fails through coordinator for security issues', async () => {
|
|
741
|
+
registerGate4(gates);
|
|
742
|
+
|
|
743
|
+
const insecureCode = `
|
|
744
|
+
const password = 'hardcoded123';
|
|
745
|
+
const result = eval(userInput);
|
|
746
|
+
`;
|
|
747
|
+
|
|
748
|
+
const context = {
|
|
749
|
+
files: [
|
|
750
|
+
{
|
|
751
|
+
path: '/src/insecure.js',
|
|
752
|
+
content: insecureCode,
|
|
753
|
+
},
|
|
754
|
+
],
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const result = await gates.executeGate('gate-04-security', context);
|
|
758
|
+
|
|
759
|
+
assert.strictEqual(result.passed, false);
|
|
760
|
+
assert.ok(result.errors.length > 0);
|
|
761
|
+
});
|
|
762
|
+
});
|