@avi770/testteam 1.2.0 → 3.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/CHANGELOG.md +53 -1
- package/README.md +72 -7
- package/agents/15-regression-sentinel.ts +3 -2
- package/agents/24-signup-onboarding-tester.ts +429 -0
- package/agents/25-crud-flow-tester.ts +302 -0
- package/agents/26-form-validator.ts +297 -0
- package/agents/27-search-filter-tester.ts +326 -0
- package/agents/28-navigation-routing-tester.ts +425 -0
- package/agents/29-responsive-interaction-tester.ts +350 -0
- package/agents/30-multi-user-scenario-tester.ts +319 -0
- package/agents/31-load-tester.ts +134 -0
- package/agents/32-memory-leak-detector.ts +194 -0
- package/agents/33-bundle-analyzer.ts +132 -0
- package/agents/34-xss-scanner.ts +191 -0
- package/agents/35-csrf-tester.ts +82 -0
- package/agents/36-auth-fuzzer.ts +194 -0
- package/agents/37-dependency-scanner.ts +176 -0
- package/agents/38-secrets-scanner.ts +137 -0
- package/agents/39-api-contract-tester.ts +199 -0
- package/agents/40-rate-limit-tester.ts +94 -0
- package/agents/41-api-pagination-tester.ts +97 -0
- package/agents/42-graphql-tester.ts +222 -0
- package/agents/43-data-consistency-checker.ts +205 -0
- package/agents/44-backup-recovery-tester.ts +152 -0
- package/agents/45-data-privacy-scanner.ts +125 -0
- package/agents/46-seo-auditor.ts +294 -0
- package/agents/47-social-preview-tester.ts +232 -0
- package/agents/48-lighthouse-auditor.ts +213 -0
- package/agents/49-i18n-tester.ts +198 -0
- package/agents/50-timezone-tester.ts +173 -0
- package/agents/51-error-recovery-tester.ts +155 -0
- package/agents/52-offline-mode-tester.ts +180 -0
- package/agents/53-graceful-degradation-tester.ts +156 -0
- package/agents/54-websocket-tester.ts +151 -0
- package/agents/55-realtime-sync-tester.ts +194 -0
- package/agents/56-file-upload-tester.ts +194 -0
- package/agents/57-export-tester.ts +174 -0
- package/agents/58-payment-flow-tester.ts +183 -0
- package/agents/59-ssl-tls-auditor.ts +141 -0
- package/agents/60-dns-cdn-tester.ts +117 -0
- package/agents/61-docker-health-checker.ts +111 -0
- package/agents/62-env-config-validator.ts +152 -0
- package/agents/63-log-quality-auditor.ts +136 -0
- package/agents/64-analytics-tracker-tester.ts +165 -0
- package/agents/65-gdpr-compliance-tester.ts +215 -0
- package/agents/66-soc2-control-validator.ts +210 -0
- package/agents/67-wcag-aaa-tester.ts +241 -0
- package/agents/68-dead-code-detector.ts +135 -0
- package/agents/69-type-safety-auditor.ts +164 -0
- package/agents/70-complexity-analyzer.ts +179 -0
- package/agents/__tests__/24-signup-onboarding-tester.test.ts +274 -0
- package/agents/__tests__/25-crud-flow-tester.test.ts +322 -0
- package/agents/__tests__/26-form-validator.test.ts +345 -0
- package/agents/__tests__/27-search-filter-tester.test.ts +311 -0
- package/agents/__tests__/28-navigation-routing-tester.test.ts +328 -0
- package/agents/__tests__/29-responsive-interaction-tester.test.ts +297 -0
- package/agents/__tests__/30-multi-user-scenario-tester.test.ts +328 -0
- package/agents/__tests__/31-load-tester.test.ts +189 -0
- package/agents/__tests__/32-memory-leak-detector.test.ts +251 -0
- package/agents/__tests__/33-bundle-analyzer.test.ts +237 -0
- package/agents/__tests__/34-xss-scanner.test.ts +258 -0
- package/agents/__tests__/35-csrf-tester.test.ts +200 -0
- package/agents/__tests__/36-auth-fuzzer.test.ts +214 -0
- package/agents/__tests__/37-dependency-scanner.test.ts +266 -0
- package/agents/__tests__/38-secrets-scanner.test.ts +224 -0
- package/agents/__tests__/39-api-contract-tester.test.ts +312 -0
- package/agents/__tests__/40-rate-limit-tester.test.ts +192 -0
- package/agents/__tests__/41-api-pagination-tester.test.ts +198 -0
- package/agents/__tests__/42-graphql-tester.test.ts +252 -0
- package/agents/__tests__/43-data-consistency-checker.test.ts +232 -0
- package/agents/__tests__/44-backup-recovery-tester.test.ts +222 -0
- package/agents/__tests__/45-data-privacy-scanner.test.ts +223 -0
- package/agents/__tests__/46-seo-auditor.test.ts +261 -0
- package/agents/__tests__/47-social-preview-tester.test.ts +245 -0
- package/agents/__tests__/48-lighthouse-auditor.test.ts +276 -0
- package/agents/__tests__/49-i18n-tester.test.ts +201 -0
- package/agents/__tests__/50-timezone-tester.test.ts +172 -0
- package/agents/__tests__/51-error-recovery-tester.test.ts +162 -0
- package/agents/__tests__/52-offline-mode-tester.test.ts +164 -0
- package/agents/__tests__/53-graceful-degradation-tester.test.ts +168 -0
- package/agents/__tests__/54-websocket-tester.test.ts +157 -0
- package/agents/__tests__/55-realtime-sync-tester.test.ts +181 -0
- package/agents/__tests__/56-file-upload-tester.test.ts +172 -0
- package/agents/__tests__/57-export-tester.test.ts +169 -0
- package/agents/__tests__/58-payment-flow-tester.test.ts +182 -0
- package/agents/__tests__/59-ssl-tls-auditor.test.ts +179 -0
- package/agents/__tests__/60-dns-cdn-tester.test.ts +176 -0
- package/agents/__tests__/61-docker-health-checker.test.ts +150 -0
- package/agents/__tests__/62-env-config-validator.test.ts +166 -0
- package/agents/__tests__/63-log-quality-auditor.test.ts +175 -0
- package/agents/__tests__/64-analytics-tracker-tester.test.ts +158 -0
- package/agents/__tests__/65-gdpr-compliance-tester.test.ts +174 -0
- package/agents/__tests__/66-soc2-control-validator.test.ts +183 -0
- package/agents/__tests__/67-wcag-aaa-tester.test.ts +190 -0
- package/agents/__tests__/68-dead-code-detector.test.ts +174 -0
- package/agents/__tests__/69-type-safety-auditor.test.ts +173 -0
- package/agents/__tests__/70-complexity-analyzer.test.ts +177 -0
- package/agents/__tests__/registry.test.ts +13 -13
- package/agents/base-agent.ts +8 -0
- package/agents/registry.ts +171 -28
- package/core/__tests__/integration.test.ts +4 -4
- package/core/__tests__/orchestrator.test.ts +17 -16
- package/core/cli.ts +128 -6
- package/core/license.ts +208 -211
- package/core/orchestrator.ts +78 -5
- package/dist/agents/15-regression-sentinel.d.ts +2 -1
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -1
- package/dist/agents/15-regression-sentinel.js +2 -2
- package/dist/agents/15-regression-sentinel.js.map +1 -1
- package/dist/agents/24-signup-onboarding-tester.d.ts +35 -0
- package/dist/agents/24-signup-onboarding-tester.d.ts.map +1 -0
- package/dist/agents/24-signup-onboarding-tester.js +357 -0
- package/dist/agents/24-signup-onboarding-tester.js.map +1 -0
- package/dist/agents/25-crud-flow-tester.d.ts +11 -0
- package/dist/agents/25-crud-flow-tester.d.ts.map +1 -0
- package/dist/agents/25-crud-flow-tester.js +253 -0
- package/dist/agents/25-crud-flow-tester.js.map +1 -0
- package/dist/agents/26-form-validator.d.ts +12 -0
- package/dist/agents/26-form-validator.d.ts.map +1 -0
- package/dist/agents/26-form-validator.js +257 -0
- package/dist/agents/26-form-validator.js.map +1 -0
- package/dist/agents/27-search-filter-tester.d.ts +20 -0
- package/dist/agents/27-search-filter-tester.d.ts.map +1 -0
- package/dist/agents/27-search-filter-tester.js +267 -0
- package/dist/agents/27-search-filter-tester.js.map +1 -0
- package/dist/agents/28-navigation-routing-tester.d.ts +32 -0
- package/dist/agents/28-navigation-routing-tester.d.ts.map +1 -0
- package/dist/agents/28-navigation-routing-tester.js +363 -0
- package/dist/agents/28-navigation-routing-tester.js.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts +26 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.js +272 -0
- package/dist/agents/29-responsive-interaction-tester.js.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts +24 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.js +254 -0
- package/dist/agents/30-multi-user-scenario-tester.js.map +1 -0
- package/dist/agents/31-load-tester.d.ts +12 -0
- package/dist/agents/31-load-tester.d.ts.map +1 -0
- package/dist/agents/31-load-tester.js +110 -0
- package/dist/agents/31-load-tester.js.map +1 -0
- package/dist/agents/32-memory-leak-detector.d.ts +12 -0
- package/dist/agents/32-memory-leak-detector.d.ts.map +1 -0
- package/dist/agents/32-memory-leak-detector.js +167 -0
- package/dist/agents/32-memory-leak-detector.js.map +1 -0
- package/dist/agents/33-bundle-analyzer.d.ts +10 -0
- package/dist/agents/33-bundle-analyzer.d.ts.map +1 -0
- package/dist/agents/33-bundle-analyzer.js +111 -0
- package/dist/agents/33-bundle-analyzer.js.map +1 -0
- package/dist/agents/34-xss-scanner.d.ts +11 -0
- package/dist/agents/34-xss-scanner.d.ts.map +1 -0
- package/dist/agents/34-xss-scanner.js +164 -0
- package/dist/agents/34-xss-scanner.js.map +1 -0
- package/dist/agents/35-csrf-tester.d.ts +11 -0
- package/dist/agents/35-csrf-tester.d.ts.map +1 -0
- package/dist/agents/35-csrf-tester.js +70 -0
- package/dist/agents/35-csrf-tester.js.map +1 -0
- package/dist/agents/36-auth-fuzzer.d.ts +13 -0
- package/dist/agents/36-auth-fuzzer.d.ts.map +1 -0
- package/dist/agents/36-auth-fuzzer.js +163 -0
- package/dist/agents/36-auth-fuzzer.js.map +1 -0
- package/dist/agents/37-dependency-scanner.d.ts +11 -0
- package/dist/agents/37-dependency-scanner.d.ts.map +1 -0
- package/dist/agents/37-dependency-scanner.js +139 -0
- package/dist/agents/37-dependency-scanner.js.map +1 -0
- package/dist/agents/38-secrets-scanner.d.ts +11 -0
- package/dist/agents/38-secrets-scanner.d.ts.map +1 -0
- package/dist/agents/38-secrets-scanner.js +116 -0
- package/dist/agents/38-secrets-scanner.js.map +1 -0
- package/dist/agents/39-api-contract-tester.d.ts +12 -0
- package/dist/agents/39-api-contract-tester.d.ts.map +1 -0
- package/dist/agents/39-api-contract-tester.js +142 -0
- package/dist/agents/39-api-contract-tester.js.map +1 -0
- package/dist/agents/40-rate-limit-tester.d.ts +12 -0
- package/dist/agents/40-rate-limit-tester.d.ts.map +1 -0
- package/dist/agents/40-rate-limit-tester.js +79 -0
- package/dist/agents/40-rate-limit-tester.js.map +1 -0
- package/dist/agents/41-api-pagination-tester.d.ts +12 -0
- package/dist/agents/41-api-pagination-tester.d.ts.map +1 -0
- package/dist/agents/41-api-pagination-tester.js +79 -0
- package/dist/agents/41-api-pagination-tester.js.map +1 -0
- package/dist/agents/42-graphql-tester.d.ts +13 -0
- package/dist/agents/42-graphql-tester.d.ts.map +1 -0
- package/dist/agents/42-graphql-tester.js +187 -0
- package/dist/agents/42-graphql-tester.js.map +1 -0
- package/dist/agents/43-data-consistency-checker.d.ts +11 -0
- package/dist/agents/43-data-consistency-checker.d.ts.map +1 -0
- package/dist/agents/43-data-consistency-checker.js +176 -0
- package/dist/agents/43-data-consistency-checker.js.map +1 -0
- package/dist/agents/44-backup-recovery-tester.d.ts +11 -0
- package/dist/agents/44-backup-recovery-tester.d.ts.map +1 -0
- package/dist/agents/44-backup-recovery-tester.js +128 -0
- package/dist/agents/44-backup-recovery-tester.js.map +1 -0
- package/dist/agents/45-data-privacy-scanner.d.ts +11 -0
- package/dist/agents/45-data-privacy-scanner.d.ts.map +1 -0
- package/dist/agents/45-data-privacy-scanner.js +100 -0
- package/dist/agents/45-data-privacy-scanner.js.map +1 -0
- package/dist/agents/46-seo-auditor.d.ts +12 -0
- package/dist/agents/46-seo-auditor.d.ts.map +1 -0
- package/dist/agents/46-seo-auditor.js +275 -0
- package/dist/agents/46-seo-auditor.js.map +1 -0
- package/dist/agents/47-social-preview-tester.d.ts +11 -0
- package/dist/agents/47-social-preview-tester.d.ts.map +1 -0
- package/dist/agents/47-social-preview-tester.js +219 -0
- package/dist/agents/47-social-preview-tester.js.map +1 -0
- package/dist/agents/48-lighthouse-auditor.d.ts +11 -0
- package/dist/agents/48-lighthouse-auditor.d.ts.map +1 -0
- package/dist/agents/48-lighthouse-auditor.js +192 -0
- package/dist/agents/48-lighthouse-auditor.js.map +1 -0
- package/dist/agents/49-i18n-tester.d.ts +13 -0
- package/dist/agents/49-i18n-tester.d.ts.map +1 -0
- package/dist/agents/49-i18n-tester.js +172 -0
- package/dist/agents/49-i18n-tester.js.map +1 -0
- package/dist/agents/50-timezone-tester.d.ts +11 -0
- package/dist/agents/50-timezone-tester.d.ts.map +1 -0
- package/dist/agents/50-timezone-tester.js +152 -0
- package/dist/agents/50-timezone-tester.js.map +1 -0
- package/dist/agents/51-error-recovery-tester.d.ts +11 -0
- package/dist/agents/51-error-recovery-tester.d.ts.map +1 -0
- package/dist/agents/51-error-recovery-tester.js +134 -0
- package/dist/agents/51-error-recovery-tester.js.map +1 -0
- package/dist/agents/52-offline-mode-tester.d.ts +12 -0
- package/dist/agents/52-offline-mode-tester.d.ts.map +1 -0
- package/dist/agents/52-offline-mode-tester.js +161 -0
- package/dist/agents/52-offline-mode-tester.js.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts +12 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.js +130 -0
- package/dist/agents/53-graceful-degradation-tester.js.map +1 -0
- package/dist/agents/54-websocket-tester.d.ts +10 -0
- package/dist/agents/54-websocket-tester.d.ts.map +1 -0
- package/dist/agents/54-websocket-tester.js +132 -0
- package/dist/agents/54-websocket-tester.js.map +1 -0
- package/dist/agents/55-realtime-sync-tester.d.ts +11 -0
- package/dist/agents/55-realtime-sync-tester.d.ts.map +1 -0
- package/dist/agents/55-realtime-sync-tester.js +175 -0
- package/dist/agents/55-realtime-sync-tester.js.map +1 -0
- package/dist/agents/56-file-upload-tester.d.ts +12 -0
- package/dist/agents/56-file-upload-tester.d.ts.map +1 -0
- package/dist/agents/56-file-upload-tester.js +166 -0
- package/dist/agents/56-file-upload-tester.js.map +1 -0
- package/dist/agents/57-export-tester.d.ts +11 -0
- package/dist/agents/57-export-tester.d.ts.map +1 -0
- package/dist/agents/57-export-tester.js +155 -0
- package/dist/agents/57-export-tester.js.map +1 -0
- package/dist/agents/58-payment-flow-tester.d.ts +11 -0
- package/dist/agents/58-payment-flow-tester.d.ts.map +1 -0
- package/dist/agents/58-payment-flow-tester.js +159 -0
- package/dist/agents/58-payment-flow-tester.js.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts +10 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.js +132 -0
- package/dist/agents/59-ssl-tls-auditor.js.map +1 -0
- package/dist/agents/60-dns-cdn-tester.d.ts +10 -0
- package/dist/agents/60-dns-cdn-tester.d.ts.map +1 -0
- package/dist/agents/60-dns-cdn-tester.js +105 -0
- package/dist/agents/60-dns-cdn-tester.js.map +1 -0
- package/dist/agents/61-docker-health-checker.d.ts +10 -0
- package/dist/agents/61-docker-health-checker.d.ts.map +1 -0
- package/dist/agents/61-docker-health-checker.js +95 -0
- package/dist/agents/61-docker-health-checker.js.map +1 -0
- package/dist/agents/62-env-config-validator.d.ts +10 -0
- package/dist/agents/62-env-config-validator.d.ts.map +1 -0
- package/dist/agents/62-env-config-validator.js +132 -0
- package/dist/agents/62-env-config-validator.js.map +1 -0
- package/dist/agents/63-log-quality-auditor.d.ts +9 -0
- package/dist/agents/63-log-quality-auditor.d.ts.map +1 -0
- package/dist/agents/63-log-quality-auditor.js +121 -0
- package/dist/agents/63-log-quality-auditor.js.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts +10 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.js +146 -0
- package/dist/agents/64-analytics-tracker-tester.js.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts +13 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.js +186 -0
- package/dist/agents/65-gdpr-compliance-tester.js.map +1 -0
- package/dist/agents/66-soc2-control-validator.d.ts +14 -0
- package/dist/agents/66-soc2-control-validator.d.ts.map +1 -0
- package/dist/agents/66-soc2-control-validator.js +178 -0
- package/dist/agents/66-soc2-control-validator.js.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts +13 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.js +207 -0
- package/dist/agents/67-wcag-aaa-tester.js.map +1 -0
- package/dist/agents/68-dead-code-detector.d.ts +9 -0
- package/dist/agents/68-dead-code-detector.d.ts.map +1 -0
- package/dist/agents/68-dead-code-detector.js +116 -0
- package/dist/agents/68-dead-code-detector.js.map +1 -0
- package/dist/agents/69-type-safety-auditor.d.ts +9 -0
- package/dist/agents/69-type-safety-auditor.d.ts.map +1 -0
- package/dist/agents/69-type-safety-auditor.js +148 -0
- package/dist/agents/69-type-safety-auditor.js.map +1 -0
- package/dist/agents/70-complexity-analyzer.d.ts +10 -0
- package/dist/agents/70-complexity-analyzer.d.ts.map +1 -0
- package/dist/agents/70-complexity-analyzer.js +154 -0
- package/dist/agents/70-complexity-analyzer.js.map +1 -0
- package/dist/agents/base-agent.d.ts +3 -1
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +7 -1
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/registry.d.ts +5 -4
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +170 -29
- package/dist/agents/registry.js.map +1 -1
- package/dist/clients/agent-mvp.d.ts +66 -0
- package/dist/clients/agent-mvp.d.ts.map +1 -0
- package/dist/clients/agent-mvp.js +91 -0
- package/dist/clients/agent-mvp.js.map +1 -0
- package/dist/clients/total-recall.d.ts +37 -0
- package/dist/clients/total-recall.d.ts.map +1 -0
- package/dist/clients/total-recall.js +224 -0
- package/dist/clients/total-recall.js.map +1 -0
- package/dist/core/cli.d.ts +3 -0
- package/dist/core/cli.d.ts.map +1 -1
- package/dist/core/cli.js +122 -6
- package/dist/core/cli.js.map +1 -1
- package/dist/core/license.js +2 -5
- package/dist/core/license.js.map +1 -1
- package/dist/core/orchestrator.d.ts +10 -1
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +66 -5
- package/dist/core/orchestrator.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 1, name: 'test',
|
|
8
|
+
projectRoot: '/fake/project',
|
|
9
|
+
auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
|
|
10
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
11
|
+
environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
|
|
12
|
+
portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
|
|
13
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
14
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
15
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
16
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
17
|
+
thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
|
|
18
|
+
disabledAgents: [], disabledAgentSet: new Set<number>(),
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
24
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
25
|
+
|
|
26
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
27
|
+
login: vi.fn().mockResolvedValue(undefined),
|
|
28
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const existsSyncMock = vi.fn();
|
|
34
|
+
const readFileSyncMock = vi.fn();
|
|
35
|
+
const readdirSyncMock = vi.fn();
|
|
36
|
+
|
|
37
|
+
vi.mock('node:fs', () => ({
|
|
38
|
+
existsSync: (...args: unknown[]) => existsSyncMock(...args),
|
|
39
|
+
readFileSync: (...args: unknown[]) => readFileSyncMock(...args),
|
|
40
|
+
readdirSync: (...args: unknown[]) => readdirSyncMock(...args),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
const { LogQualityAuditorAgent } = await import('../63-log-quality-auditor');
|
|
44
|
+
|
|
45
|
+
function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
|
|
46
|
+
return new LogQualityAuditorAgent(config, phase, '/tmp/test-run');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('LogQualityAuditorAgent', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
existsSyncMock.mockReset().mockReturnValue(true);
|
|
52
|
+
readFileSyncMock.mockReset();
|
|
53
|
+
readdirSyncMock.mockReset();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('has agentId 63 and correct name', () => {
|
|
57
|
+
const agent = createAgent();
|
|
58
|
+
expect(agent.agentId).toBe(63);
|
|
59
|
+
expect(agent.agentName).toBe('Log Quality Auditor');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('reports no src dir when missing', async () => {
|
|
63
|
+
existsSyncMock.mockReturnValue(false);
|
|
64
|
+
const agent = createAgent();
|
|
65
|
+
const result = await agent.run();
|
|
66
|
+
const finding = result.findings.find(f => f.id.includes('no-src-dir'));
|
|
67
|
+
expect(finding).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('detects console.log calls', async () => {
|
|
71
|
+
readdirSyncMock.mockReturnValue([
|
|
72
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
73
|
+
]);
|
|
74
|
+
readFileSyncMock.mockReturnValue('const x = 1;\nconsole.log("debug");\nreturn x;');
|
|
75
|
+
const agent = createAgent();
|
|
76
|
+
const result = await agent.run();
|
|
77
|
+
const finding = result.findings.find(f => f.id.includes('console-log'));
|
|
78
|
+
expect(finding).toBeDefined();
|
|
79
|
+
expect(finding!.severity).toBe('low');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('detects unstructured console.error', async () => {
|
|
83
|
+
readdirSyncMock.mockReturnValue([
|
|
84
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
85
|
+
]);
|
|
86
|
+
readFileSyncMock.mockReturnValue('console.error("something failed");');
|
|
87
|
+
const agent = createAgent();
|
|
88
|
+
const result = await agent.run();
|
|
89
|
+
const finding = result.findings.find(f => f.id.includes('unstructured-error'));
|
|
90
|
+
expect(finding).toBeDefined();
|
|
91
|
+
expect(finding!.severity).toBe('medium');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('reports no structured logging when none found', async () => {
|
|
95
|
+
readdirSyncMock.mockReturnValue([
|
|
96
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
97
|
+
]);
|
|
98
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
99
|
+
const agent = createAgent();
|
|
100
|
+
const result = await agent.run();
|
|
101
|
+
const finding = result.findings.find(f => f.id.includes('no-structured-logging'));
|
|
102
|
+
expect(finding).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('no logging finding when pino detected', async () => {
|
|
106
|
+
readdirSyncMock.mockReturnValue([
|
|
107
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
108
|
+
]);
|
|
109
|
+
readFileSyncMock.mockReturnValue('import pino from "pino";\nconst logger = pino();');
|
|
110
|
+
const agent = createAgent();
|
|
111
|
+
const result = await agent.run();
|
|
112
|
+
const finding = result.findings.find(f => f.id.includes('no-structured-logging'));
|
|
113
|
+
expect(finding).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('skips test files', async () => {
|
|
117
|
+
readdirSyncMock.mockReturnValue([
|
|
118
|
+
{ name: 'app.test.ts', isFile: () => true, isDirectory: () => false },
|
|
119
|
+
]);
|
|
120
|
+
const agent = createAgent();
|
|
121
|
+
const result = await agent.run();
|
|
122
|
+
// Should not find console.log in test files
|
|
123
|
+
expect(result.status).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('recurses into subdirectories', async () => {
|
|
127
|
+
readdirSyncMock
|
|
128
|
+
.mockReturnValueOnce([
|
|
129
|
+
{ name: 'utils', isFile: () => false, isDirectory: () => true },
|
|
130
|
+
])
|
|
131
|
+
.mockReturnValueOnce([
|
|
132
|
+
{ name: 'helper.ts', isFile: () => true, isDirectory: () => false },
|
|
133
|
+
]);
|
|
134
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
135
|
+
const agent = createAgent();
|
|
136
|
+
const result = await agent.run();
|
|
137
|
+
expect(result.status).toBeDefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('limits console.log findings to 5', async () => {
|
|
141
|
+
readdirSyncMock.mockReturnValue([
|
|
142
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
143
|
+
]);
|
|
144
|
+
readFileSyncMock.mockReturnValue(
|
|
145
|
+
Array(10).fill('console.log("x");').join('\n'),
|
|
146
|
+
);
|
|
147
|
+
const agent = createAgent();
|
|
148
|
+
const result = await agent.run();
|
|
149
|
+
const logFindings = result.findings.filter(f => f.id.match(/console-log-\d+/));
|
|
150
|
+
expect(logFindings.length).toBe(5);
|
|
151
|
+
const summary = result.findings.find(f => f.id.includes('console-log-summary'));
|
|
152
|
+
expect(summary).toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('adds evidence with scan stats', async () => {
|
|
156
|
+
readdirSyncMock.mockReturnValue([
|
|
157
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
158
|
+
]);
|
|
159
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
160
|
+
const agent = createAgent();
|
|
161
|
+
const result = await agent.run();
|
|
162
|
+
expect(result.evidence.length).toBeGreaterThan(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('detects winston as structured logging', async () => {
|
|
166
|
+
readdirSyncMock.mockReturnValue([
|
|
167
|
+
{ name: 'logger.ts', isFile: () => true, isDirectory: () => false },
|
|
168
|
+
]);
|
|
169
|
+
readFileSyncMock.mockReturnValue('import winston from "winston";');
|
|
170
|
+
const agent = createAgent();
|
|
171
|
+
const result = await agent.run();
|
|
172
|
+
const finding = result.findings.find(f => f.id.includes('no-structured-logging'));
|
|
173
|
+
expect(finding).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 1, name: 'test',
|
|
8
|
+
auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
|
|
9
|
+
modules: [
|
|
10
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
11
|
+
{ id: 'tasks', route: '/tasks', sidebarIcon: true },
|
|
12
|
+
],
|
|
13
|
+
environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
|
|
14
|
+
portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
|
|
15
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
16
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
17
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
18
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
19
|
+
thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
|
|
20
|
+
disabledAgents: [], disabledAgentSet: new Set<number>(),
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const fetchMock = vi.fn();
|
|
26
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
27
|
+
|
|
28
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
29
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
30
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
31
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
32
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
33
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
function makeMockPage() {
|
|
37
|
+
return {
|
|
38
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
39
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
40
|
+
on: vi.fn(),
|
|
41
|
+
locator: vi.fn().mockReturnValue({ count: vi.fn().mockResolvedValue(0) }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let currentMockPage: ReturnType<typeof makeMockPage>;
|
|
46
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
47
|
+
|
|
48
|
+
vi.mock('playwright', async () => ({
|
|
49
|
+
chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const { AnalyticsTrackerTesterAgent } = await import('../64-analytics-tracker-tester');
|
|
53
|
+
|
|
54
|
+
function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
|
|
55
|
+
return new AnalyticsTrackerTesterAgent(config, phase, '/tmp/test-run');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('AnalyticsTrackerTesterAgent', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
61
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
62
|
+
currentMockPage = makeMockPage();
|
|
63
|
+
mockBrowser = {
|
|
64
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
65
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('has agentId 64 and correct name', () => {
|
|
70
|
+
const agent = createAgent();
|
|
71
|
+
expect(agent.agentId).toBe(64);
|
|
72
|
+
expect(agent.agentName).toBe('Analytics Tracker Tester');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('throws when modules empty', async () => {
|
|
76
|
+
const agent = createAgent(makeConfig({ modules: [] }));
|
|
77
|
+
const result = await agent.run();
|
|
78
|
+
expect(result.status).toBe('failed');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('reports no analytics when none detected', async () => {
|
|
82
|
+
const agent = createAgent();
|
|
83
|
+
const result = await agent.run();
|
|
84
|
+
const finding = result.findings.find(f => f.id.includes('no-analytics'));
|
|
85
|
+
expect(finding).toBeDefined();
|
|
86
|
+
expect(finding!.severity).toBe('low');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('detects analytics requests via event handler', async () => {
|
|
90
|
+
currentMockPage.on.mockImplementation((event: string, handler: Function) => {
|
|
91
|
+
if (event === 'request') {
|
|
92
|
+
handler({ url: () => 'https://www.google-analytics.com/collect?v=1&t=pageview' });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const agent = createAgent();
|
|
96
|
+
const result = await agent.run();
|
|
97
|
+
const noAnalytics = result.findings.find(f => f.id.includes('no-analytics'));
|
|
98
|
+
expect(noAnalytics).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('detects duplicate pageviews', async () => {
|
|
102
|
+
currentMockPage.on.mockImplementation((event: string, handler: Function) => {
|
|
103
|
+
if (event === 'request') {
|
|
104
|
+
// Fire many pageview events
|
|
105
|
+
for (let i = 0; i < 10; i++) {
|
|
106
|
+
handler({ url: () => `https://www.google-analytics.com/collect?v=1&t=pageview&_=${i}` });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const agent = createAgent();
|
|
111
|
+
const result = await agent.run();
|
|
112
|
+
const finding = result.findings.find(f => f.id.includes('duplicate-pageviews'));
|
|
113
|
+
expect(finding).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('handles login failure', async () => {
|
|
117
|
+
loginMock.mockRejectedValue(new Error('Login failed'));
|
|
118
|
+
const agent = createAgent();
|
|
119
|
+
const result = await agent.run();
|
|
120
|
+
const finding = result.findings.find(f => f.id.includes('login-failed'));
|
|
121
|
+
expect(finding).toBeDefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('handles playwright crash', async () => {
|
|
125
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
|
|
126
|
+
const agent = createAgent();
|
|
127
|
+
const result = await agent.run();
|
|
128
|
+
const finding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
129
|
+
expect(finding).toBeDefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('closes browser in finally', async () => {
|
|
133
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
|
|
134
|
+
const agent = createAgent();
|
|
135
|
+
await agent.run();
|
|
136
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('handles nav failure for module', async () => {
|
|
140
|
+
currentMockPage.goto.mockRejectedValue(new Error('Nav failed'));
|
|
141
|
+
const agent = createAgent();
|
|
142
|
+
const result = await agent.run();
|
|
143
|
+
expect(result.status).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('registers request event listener', async () => {
|
|
147
|
+
const agent = createAgent();
|
|
148
|
+
await agent.run();
|
|
149
|
+
expect(currentMockPage.on).toHaveBeenCalledWith('request', expect.any(Function));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('no credentials continues', async () => {
|
|
153
|
+
const config = makeConfig({ auth: { loginUrl: '/login', credentials: {} } });
|
|
154
|
+
const agent = createAgent(config);
|
|
155
|
+
const result = await agent.run();
|
|
156
|
+
expect(result.status).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 1, name: 'test',
|
|
8
|
+
auth: { loginUrl: 'http://localhost/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
|
|
9
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
10
|
+
environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
|
|
11
|
+
portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
|
|
12
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
13
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
14
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
15
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
16
|
+
thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
|
|
17
|
+
disabledAgents: [], disabledAgentSet: new Set<number>(),
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const fetchMock = vi.fn();
|
|
23
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
24
|
+
|
|
25
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
26
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
27
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
28
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
function makeMockLocator(countVal = 0) {
|
|
34
|
+
const loc: Record<string, ReturnType<typeof vi.fn>> = {
|
|
35
|
+
count: vi.fn().mockResolvedValue(countVal),
|
|
36
|
+
first: vi.fn(),
|
|
37
|
+
};
|
|
38
|
+
loc.first.mockReturnValue(loc);
|
|
39
|
+
return loc;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeMockPage() {
|
|
43
|
+
return {
|
|
44
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
45
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
46
|
+
evaluate: vi.fn().mockResolvedValue(''),
|
|
47
|
+
locator: vi.fn().mockReturnValue(makeMockLocator(0)),
|
|
48
|
+
on: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let mockFreshPage: ReturnType<typeof makeMockPage>;
|
|
53
|
+
let mockMainPage: ReturnType<typeof makeMockPage>;
|
|
54
|
+
let contextCallCount: number;
|
|
55
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; newContext: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
56
|
+
|
|
57
|
+
vi.mock('playwright', async () => ({
|
|
58
|
+
chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const { GdprComplianceTesterAgent } = await import('../65-gdpr-compliance-tester');
|
|
62
|
+
|
|
63
|
+
function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
|
|
64
|
+
return new GdprComplianceTesterAgent(config, phase, '/tmp/test-run');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('GdprComplianceTesterAgent', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
70
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
71
|
+
mockFreshPage = makeMockPage();
|
|
72
|
+
mockMainPage = makeMockPage();
|
|
73
|
+
contextCallCount = 0;
|
|
74
|
+
mockBrowser = {
|
|
75
|
+
newPage: vi.fn().mockResolvedValue(mockMainPage),
|
|
76
|
+
newContext: vi.fn().mockResolvedValue({
|
|
77
|
+
newPage: vi.fn().mockResolvedValue(mockFreshPage),
|
|
78
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
79
|
+
}),
|
|
80
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('has agentId 65 and correct name', () => {
|
|
85
|
+
const agent = createAgent();
|
|
86
|
+
expect(agent.agentId).toBe(65);
|
|
87
|
+
expect(agent.agentName).toBe('GDPR Compliance Tester');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('reports no cookie consent when not found', async () => {
|
|
91
|
+
mockFreshPage.evaluate.mockResolvedValue('welcome to our app');
|
|
92
|
+
const agent = createAgent();
|
|
93
|
+
const result = await agent.run();
|
|
94
|
+
const finding = result.findings.find(f => f.id.includes('no-cookie-consent'));
|
|
95
|
+
expect(finding).toBeDefined();
|
|
96
|
+
expect(finding!.severity).toBe('high');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('detects cookie consent text', async () => {
|
|
100
|
+
mockFreshPage.evaluate.mockResolvedValue('we use cookies to improve your experience');
|
|
101
|
+
mockFreshPage.locator.mockReturnValue(makeMockLocator(1)); // accept button
|
|
102
|
+
const agent = createAgent();
|
|
103
|
+
const result = await agent.run();
|
|
104
|
+
const finding = result.findings.find(f => f.id.includes('no-cookie-consent'));
|
|
105
|
+
expect(finding).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('reports no privacy policy link', async () => {
|
|
109
|
+
mockFreshPage.evaluate.mockResolvedValue('cookie consent here');
|
|
110
|
+
mockMainPage.locator.mockReturnValue(makeMockLocator(0));
|
|
111
|
+
const agent = createAgent();
|
|
112
|
+
const result = await agent.run();
|
|
113
|
+
const finding = result.findings.find(f => f.id.includes('no-privacy-policy'));
|
|
114
|
+
expect(finding).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('reports no delete account option', async () => {
|
|
118
|
+
mockFreshPage.evaluate.mockResolvedValue('cookie consent');
|
|
119
|
+
mockMainPage.evaluate.mockResolvedValue(false); // no delete account
|
|
120
|
+
const agent = createAgent();
|
|
121
|
+
const result = await agent.run();
|
|
122
|
+
const finding = result.findings.find(f => f.id.includes('no-delete-account'));
|
|
123
|
+
expect(finding).toBeDefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('handles login failure', async () => {
|
|
127
|
+
loginMock.mockRejectedValue(new Error('Login failed'));
|
|
128
|
+
mockFreshPage.evaluate.mockResolvedValue('cookie');
|
|
129
|
+
const agent = createAgent();
|
|
130
|
+
const result = await agent.run();
|
|
131
|
+
const finding = result.findings.find(f => f.id.includes('login-failed'));
|
|
132
|
+
expect(finding).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('handles playwright crash', async () => {
|
|
136
|
+
mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
|
|
137
|
+
const agent = createAgent();
|
|
138
|
+
const result = await agent.run();
|
|
139
|
+
const finding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
140
|
+
expect(finding).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('closes browser in finally', async () => {
|
|
144
|
+
mockBrowser.newContext.mockRejectedValue(new Error('Crash'));
|
|
145
|
+
const agent = createAgent();
|
|
146
|
+
await agent.run();
|
|
147
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('reports consent text but no accept button', async () => {
|
|
151
|
+
mockFreshPage.evaluate.mockResolvedValue('we use cookies');
|
|
152
|
+
mockFreshPage.locator.mockReturnValue(makeMockLocator(0)); // no accept button
|
|
153
|
+
const agent = createAgent();
|
|
154
|
+
const result = await agent.run();
|
|
155
|
+
const finding = result.findings.find(f => f.id.includes('no-consent-button'));
|
|
156
|
+
expect(finding).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('handles nav failure', async () => {
|
|
160
|
+
mockFreshPage.goto.mockRejectedValue(new Error('Nav failed'));
|
|
161
|
+
const agent = createAgent();
|
|
162
|
+
const result = await agent.run();
|
|
163
|
+
const finding = result.findings.find(f => f.id.includes('nav-failed'));
|
|
164
|
+
expect(finding).toBeDefined();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('no credentials skips delete account check gracefully', async () => {
|
|
168
|
+
const config = makeConfig({ auth: { loginUrl: '/login', credentials: {} } });
|
|
169
|
+
mockFreshPage.evaluate.mockResolvedValue('cookie consent');
|
|
170
|
+
const agent = createAgent(config);
|
|
171
|
+
const result = await agent.run();
|
|
172
|
+
expect(result.status).toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Phase } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: 1, name: 'test',
|
|
8
|
+
projectRoot: '/fake/project',
|
|
9
|
+
auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
|
|
10
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
11
|
+
environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
|
|
12
|
+
portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
|
|
13
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
14
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
15
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
16
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
17
|
+
thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
|
|
18
|
+
disabledAgents: [], disabledAgentSet: new Set<number>(),
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
24
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
25
|
+
|
|
26
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
27
|
+
login: vi.fn().mockResolvedValue(undefined),
|
|
28
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const existsSyncMock = vi.fn();
|
|
34
|
+
const readFileSyncMock = vi.fn();
|
|
35
|
+
const readdirSyncMock = vi.fn();
|
|
36
|
+
|
|
37
|
+
vi.mock('node:fs', () => ({
|
|
38
|
+
existsSync: (...args: unknown[]) => existsSyncMock(...args),
|
|
39
|
+
readFileSync: (...args: unknown[]) => readFileSyncMock(...args),
|
|
40
|
+
readdirSync: (...args: unknown[]) => readdirSyncMock(...args),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
function makeMockPage() {
|
|
44
|
+
return {
|
|
45
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
46
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
47
|
+
evaluate: vi.fn().mockResolvedValue(''),
|
|
48
|
+
on: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let mockPage: ReturnType<typeof makeMockPage>;
|
|
53
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
54
|
+
|
|
55
|
+
vi.mock('playwright', async () => ({
|
|
56
|
+
chromium: { launch: vi.fn().mockImplementation(async () => mockBrowser) },
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const { Soc2ControlValidatorAgent } = await import('../66-soc2-control-validator');
|
|
60
|
+
|
|
61
|
+
function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
|
|
62
|
+
return new Soc2ControlValidatorAgent(config, phase, '/tmp/test-run');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe('Soc2ControlValidatorAgent', () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
existsSyncMock.mockReset().mockReturnValue(true);
|
|
68
|
+
readFileSyncMock.mockReset().mockReturnValue('');
|
|
69
|
+
readdirSyncMock.mockReset().mockReturnValue([]);
|
|
70
|
+
mockPage = makeMockPage();
|
|
71
|
+
mockBrowser = {
|
|
72
|
+
newPage: vi.fn().mockResolvedValue(mockPage),
|
|
73
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('has agentId 66 and correct name', () => {
|
|
78
|
+
const agent = createAgent();
|
|
79
|
+
expect(agent.agentId).toBe(66);
|
|
80
|
+
expect(agent.agentName).toBe('SOC2 Control Validator');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('reports no audit logging when not found', async () => {
|
|
84
|
+
readdirSyncMock.mockReturnValue([
|
|
85
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
86
|
+
]);
|
|
87
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
88
|
+
const agent = createAgent();
|
|
89
|
+
const result = await agent.run();
|
|
90
|
+
const finding = result.findings.find(f => f.id.includes('no-audit-logging'));
|
|
91
|
+
expect(finding).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('detects audit logging', async () => {
|
|
95
|
+
readdirSyncMock.mockReturnValue([
|
|
96
|
+
{ name: 'audit.ts', isFile: () => true, isDirectory: () => false },
|
|
97
|
+
]);
|
|
98
|
+
readFileSyncMock.mockReturnValue('function auditLog(event) { log(event); }');
|
|
99
|
+
const agent = createAgent();
|
|
100
|
+
const result = await agent.run();
|
|
101
|
+
const finding = result.findings.find(f => f.id.includes('no-audit-logging'));
|
|
102
|
+
expect(finding).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('reports no session timeout', async () => {
|
|
106
|
+
readdirSyncMock.mockReturnValue([
|
|
107
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
108
|
+
]);
|
|
109
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
110
|
+
const agent = createAgent();
|
|
111
|
+
const result = await agent.run();
|
|
112
|
+
const finding = result.findings.find(f => f.id.includes('no-session-timeout'));
|
|
113
|
+
expect(finding).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('detects session timeout', async () => {
|
|
117
|
+
readdirSyncMock.mockReturnValue([
|
|
118
|
+
{ name: 'session.ts', isFile: () => true, isDirectory: () => false },
|
|
119
|
+
]);
|
|
120
|
+
readFileSyncMock.mockReturnValue('const sessionTimeout = 30 * 60 * 1000;');
|
|
121
|
+
const agent = createAgent();
|
|
122
|
+
const result = await agent.run();
|
|
123
|
+
const finding = result.findings.find(f => f.id.includes('no-session-timeout'));
|
|
124
|
+
expect(finding).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('reports no MFA UI', async () => {
|
|
128
|
+
readdirSyncMock.mockReturnValue([]);
|
|
129
|
+
mockPage.evaluate.mockResolvedValue('welcome');
|
|
130
|
+
const agent = createAgent();
|
|
131
|
+
const result = await agent.run();
|
|
132
|
+
const finding = result.findings.find(f => f.id.includes('no-mfa'));
|
|
133
|
+
expect(finding).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('detects MFA UI text', async () => {
|
|
137
|
+
readdirSyncMock.mockReturnValue([]);
|
|
138
|
+
mockPage.evaluate.mockResolvedValue('enable two-factor authentication');
|
|
139
|
+
const agent = createAgent();
|
|
140
|
+
const result = await agent.run();
|
|
141
|
+
const finding = result.findings.find(f => f.id.includes('no-mfa'));
|
|
142
|
+
expect(finding).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('reports no RBAC when not found', async () => {
|
|
146
|
+
readdirSyncMock.mockReturnValue([
|
|
147
|
+
{ name: 'app.ts', isFile: () => true, isDirectory: () => false },
|
|
148
|
+
]);
|
|
149
|
+
readFileSyncMock.mockReturnValue('const x = 1;');
|
|
150
|
+
const agent = createAgent();
|
|
151
|
+
const result = await agent.run();
|
|
152
|
+
const finding = result.findings.find(f => f.id.includes('no-rbac'));
|
|
153
|
+
expect(finding).toBeDefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('detects RBAC', async () => {
|
|
157
|
+
readdirSyncMock.mockReturnValue([
|
|
158
|
+
{ name: 'guard.ts', isFile: () => true, isDirectory: () => false },
|
|
159
|
+
]);
|
|
160
|
+
readFileSyncMock.mockReturnValue('function roleGuard(role) { authorize(role); }');
|
|
161
|
+
const agent = createAgent();
|
|
162
|
+
const result = await agent.run();
|
|
163
|
+
const finding = result.findings.find(f => f.id.includes('no-rbac'));
|
|
164
|
+
expect(finding).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('handles no src directory', async () => {
|
|
168
|
+
existsSyncMock.mockReturnValue(false);
|
|
169
|
+
const agent = createAgent();
|
|
170
|
+
const result = await agent.run();
|
|
171
|
+
const finding = result.findings.find(f => f.id.includes('no-src'));
|
|
172
|
+
expect(finding).toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles playwright crash for MFA check', async () => {
|
|
176
|
+
readdirSyncMock.mockReturnValue([]);
|
|
177
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Crash'));
|
|
178
|
+
const agent = createAgent();
|
|
179
|
+
const result = await agent.run();
|
|
180
|
+
// Should not crash the whole agent
|
|
181
|
+
expect(result.status).toBeDefined();
|
|
182
|
+
});
|
|
183
|
+
});
|