@avi770/testteam 1.2.0 → 2.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 +29 -1
- package/README.md +53 -6
- 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/registry.ts +146 -5
- package/core/__tests__/integration.test.ts +4 -4
- package/core/__tests__/orchestrator.test.ts +17 -16
- package/core/license.ts +208 -211
- package/core/orchestrator.ts +6 -4
- 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/registry.d.ts +3 -3
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +146 -5
- package/dist/agents/registry.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.map +1 -1
- package/dist/core/orchestrator.js +6 -4
- package/dist/core/orchestrator.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: { loginUrl: 'http://localhost/login', credentials: {} },
|
|
14
|
+
modules: [],
|
|
15
|
+
environments: {
|
|
16
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
17
|
+
},
|
|
18
|
+
portals: [],
|
|
19
|
+
breakpoints: [],
|
|
20
|
+
integrations: [],
|
|
21
|
+
workers: [],
|
|
22
|
+
workflows: [],
|
|
23
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
24
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
25
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
26
|
+
agentClassification: { blocking: [], advisory: [44] },
|
|
27
|
+
thresholds: {
|
|
28
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
29
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
30
|
+
apiResponseTime: 2000,
|
|
31
|
+
bundleSizeLimit: 500000,
|
|
32
|
+
maxConsoleErrors: 0,
|
|
33
|
+
},
|
|
34
|
+
disabledAgents: [],
|
|
35
|
+
disabledAgentSet: new Set<number>(),
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Mock fs and child_process
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const mockStat = vi.fn();
|
|
45
|
+
vi.mock('node:fs/promises', () => ({
|
|
46
|
+
stat: (...args: unknown[]) => mockStat(...args),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
const execFileMock = vi.fn();
|
|
50
|
+
vi.mock('node:util', async (importOriginal) => {
|
|
51
|
+
const orig = await importOriginal() as Record<string, unknown>;
|
|
52
|
+
return {
|
|
53
|
+
...orig,
|
|
54
|
+
promisify: () => execFileMock,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
vi.mock('node:child_process', () => ({
|
|
59
|
+
execFile: vi.fn(),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Dynamic import AFTER mocks
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
const { BackupRecoveryTesterAgent } = await import('../44-backup-recovery-tester');
|
|
67
|
+
|
|
68
|
+
function createAgent(
|
|
69
|
+
config: ValidatedConfig = makeConfig(),
|
|
70
|
+
phase: Phase = 'beta',
|
|
71
|
+
): InstanceType<typeof BackupRecoveryTesterAgent> {
|
|
72
|
+
return new BackupRecoveryTesterAgent(config, phase, '/tmp/test-run');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Tests
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
describe('BackupRecoveryTesterAgent', () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
mockStat.mockReset();
|
|
82
|
+
execFileMock.mockReset();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('has agentId 44 and correct agentName', () => {
|
|
86
|
+
const agent = createAgent();
|
|
87
|
+
expect(agent.agentId).toBe(44);
|
|
88
|
+
expect(agent.agentName).toBe('Backup Recovery Tester');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('produces low finding when no prisma schema exists', async () => {
|
|
92
|
+
mockStat.mockRejectedValue(new Error('ENOENT'));
|
|
93
|
+
|
|
94
|
+
const agent = createAgent();
|
|
95
|
+
const result = await agent.run();
|
|
96
|
+
|
|
97
|
+
const noSchema = result.findings.find(f => f.id.includes('no-prisma-schema'));
|
|
98
|
+
expect(noSchema).toBeDefined();
|
|
99
|
+
expect(noSchema!.severity).toBe('low');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('detects schema pull errors', async () => {
|
|
103
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
104
|
+
execFileMock
|
|
105
|
+
.mockResolvedValueOnce({ stdout: '', stderr: 'Error: could not connect' })
|
|
106
|
+
.mockResolvedValueOnce({ stdout: 'all good', stderr: '' });
|
|
107
|
+
|
|
108
|
+
const agent = createAgent();
|
|
109
|
+
const result = await agent.run();
|
|
110
|
+
|
|
111
|
+
const pullError = result.findings.find(f => f.id.includes('schema-pull-error'));
|
|
112
|
+
expect(pullError).toBeDefined();
|
|
113
|
+
expect(pullError!.severity).toBe('medium');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('detects pending migrations', async () => {
|
|
117
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
118
|
+
execFileMock
|
|
119
|
+
.mockResolvedValueOnce({ stdout: 'schema output', stderr: '' })
|
|
120
|
+
.mockResolvedValueOnce({ stdout: 'Migration pending and not yet applied', stderr: '' });
|
|
121
|
+
|
|
122
|
+
const agent = createAgent();
|
|
123
|
+
const result = await agent.run();
|
|
124
|
+
|
|
125
|
+
const pending = result.findings.find(f => f.id.includes('pending-migrations'));
|
|
126
|
+
expect(pending).toBeDefined();
|
|
127
|
+
expect(pending!.severity).toBe('high');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('detects schema drift', async () => {
|
|
131
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
132
|
+
execFileMock
|
|
133
|
+
.mockResolvedValueOnce({ stdout: 'schema output', stderr: '' })
|
|
134
|
+
.mockResolvedValueOnce({ stdout: 'database drift detected', stderr: '' });
|
|
135
|
+
|
|
136
|
+
const agent = createAgent();
|
|
137
|
+
const result = await agent.run();
|
|
138
|
+
|
|
139
|
+
const drift = result.findings.find(f => f.id.includes('schema-drift'));
|
|
140
|
+
expect(drift).toBeDefined();
|
|
141
|
+
expect(drift!.severity).toBe('high');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('passes when no issues found', async () => {
|
|
145
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
146
|
+
execFileMock
|
|
147
|
+
.mockResolvedValueOnce({ stdout: 'schema output', stderr: '' })
|
|
148
|
+
.mockResolvedValueOnce({ stdout: 'Database schema is in sync', stderr: '' });
|
|
149
|
+
|
|
150
|
+
const agent = createAgent();
|
|
151
|
+
const result = await agent.run();
|
|
152
|
+
|
|
153
|
+
const findings = result.findings.filter(f =>
|
|
154
|
+
f.id.includes('pending') || f.id.includes('drift') || f.id.includes('pull-error'),
|
|
155
|
+
);
|
|
156
|
+
expect(findings.length).toBe(0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('handles schema pull failure gracefully', async () => {
|
|
160
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
161
|
+
execFileMock
|
|
162
|
+
.mockRejectedValueOnce(new Error('npx not found'))
|
|
163
|
+
.mockResolvedValueOnce({ stdout: '', stderr: '' });
|
|
164
|
+
|
|
165
|
+
const agent = createAgent();
|
|
166
|
+
const result = await agent.run();
|
|
167
|
+
|
|
168
|
+
const pullFailed = result.findings.find(f => f.id.includes('schema-pull-failed'));
|
|
169
|
+
expect(pullFailed).toBeDefined();
|
|
170
|
+
expect(pullFailed!.severity).toBe('medium');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('handles migration status failure gracefully without crashing', async () => {
|
|
174
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
175
|
+
execFileMock
|
|
176
|
+
.mockResolvedValueOnce({ stdout: 'ok', stderr: '' })
|
|
177
|
+
.mockRejectedValueOnce(new Error('prisma not found'));
|
|
178
|
+
|
|
179
|
+
const agent = createAgent();
|
|
180
|
+
const result = await agent.run();
|
|
181
|
+
|
|
182
|
+
// The inner catch consumes the error and sets stdout/stderr to empty strings
|
|
183
|
+
// so the agent doesn't crash — it gracefully handles the failure
|
|
184
|
+
expect(result.status).toBeDefined();
|
|
185
|
+
expect(result.status).not.toBe('failed');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('records evidence for schema pull', async () => {
|
|
189
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
190
|
+
execFileMock
|
|
191
|
+
.mockResolvedValueOnce({ stdout: 'model User { id Int }', stderr: '' })
|
|
192
|
+
.mockResolvedValueOnce({ stdout: 'all synced', stderr: '' });
|
|
193
|
+
|
|
194
|
+
const agent = createAgent();
|
|
195
|
+
const result = await agent.run();
|
|
196
|
+
|
|
197
|
+
expect(result.evidence.some(e => e.description.includes('prisma db pull'))).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('uses projectRoot from config', async () => {
|
|
201
|
+
const config = makeConfig({ projectRoot: '/custom/root' });
|
|
202
|
+
mockStat.mockRejectedValue(new Error('ENOENT'));
|
|
203
|
+
|
|
204
|
+
const agent = createAgent(config);
|
|
205
|
+
await agent.run();
|
|
206
|
+
|
|
207
|
+
expect(mockStat).toHaveBeenCalledWith(expect.stringContaining('custom'));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('handles migrate status non-zero exit with stdout', async () => {
|
|
211
|
+
mockStat.mockResolvedValue({ isFile: () => true });
|
|
212
|
+
execFileMock
|
|
213
|
+
.mockResolvedValueOnce({ stdout: 'ok', stderr: '' })
|
|
214
|
+
.mockRejectedValueOnce({ stdout: 'pending migrations', stderr: '' });
|
|
215
|
+
|
|
216
|
+
const agent = createAgent();
|
|
217
|
+
const result = await agent.run();
|
|
218
|
+
|
|
219
|
+
// Should handle the non-zero exit (which has stdout)
|
|
220
|
+
expect(result.status).toBeDefined();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: {
|
|
14
|
+
loginUrl: 'http://localhost/login',
|
|
15
|
+
credentials: {
|
|
16
|
+
admin: { email: 'admin@test.com', password: 'secret' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
modules: [{ id: 'clients', route: '/clients', sidebarIcon: true }],
|
|
20
|
+
environments: {
|
|
21
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
22
|
+
},
|
|
23
|
+
portals: [],
|
|
24
|
+
breakpoints: [],
|
|
25
|
+
integrations: [],
|
|
26
|
+
workers: [],
|
|
27
|
+
workflows: [],
|
|
28
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
29
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
30
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
31
|
+
agentClassification: { blocking: [], advisory: [45] },
|
|
32
|
+
thresholds: {
|
|
33
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
34
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
35
|
+
apiResponseTime: 2000,
|
|
36
|
+
bundleSizeLimit: 500000,
|
|
37
|
+
maxConsoleErrors: 0,
|
|
38
|
+
},
|
|
39
|
+
disabledAgents: [],
|
|
40
|
+
disabledAgentSet: new Set<number>(),
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Mock fetch
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
const fetchMock = vi.fn();
|
|
50
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Mock ApiClient
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const mockGet = vi.fn();
|
|
57
|
+
|
|
58
|
+
vi.mock('../../helpers/api-client', () => ({
|
|
59
|
+
ApiClient: {
|
|
60
|
+
createAuthenticated: vi.fn().mockImplementation(async () => ({
|
|
61
|
+
get: mockGet,
|
|
62
|
+
post: vi.fn(),
|
|
63
|
+
put: vi.fn(),
|
|
64
|
+
delete: vi.fn(),
|
|
65
|
+
})),
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Dynamic import AFTER mocks
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const { DataPrivacyScannerAgent } = await import('../45-data-privacy-scanner');
|
|
74
|
+
|
|
75
|
+
function createAgent(
|
|
76
|
+
config: ValidatedConfig = makeConfig(),
|
|
77
|
+
phase: Phase = 'beta',
|
|
78
|
+
): InstanceType<typeof DataPrivacyScannerAgent> {
|
|
79
|
+
return new DataPrivacyScannerAgent(config, phase, '/tmp/test-run');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Tests
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
describe('DataPrivacyScannerAgent', () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
89
|
+
mockGet.mockReset();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('has agentId 45 and correct agentName', () => {
|
|
93
|
+
const agent = createAgent();
|
|
94
|
+
expect(agent.agentId).toBe(45);
|
|
95
|
+
expect(agent.agentName).toBe('Data Privacy Scanner');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('throws when modules array is empty', async () => {
|
|
99
|
+
const config = makeConfig({ modules: [] });
|
|
100
|
+
const agent = createAgent(config);
|
|
101
|
+
const result = await agent.run();
|
|
102
|
+
expect(result.status).toBe('failed');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('detects unmasked email addresses', async () => {
|
|
106
|
+
mockGet.mockResolvedValue({
|
|
107
|
+
status: 200,
|
|
108
|
+
body: [{ name: 'John', email: 'john@example.com' }],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const agent = createAgent();
|
|
112
|
+
const result = await agent.run();
|
|
113
|
+
|
|
114
|
+
const emailFinding = result.findings.find(f => f.description.includes('Email'));
|
|
115
|
+
expect(emailFinding).toBeDefined();
|
|
116
|
+
expect(emailFinding!.severity).toBe('high');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('detects unmasked SSN patterns', async () => {
|
|
120
|
+
mockGet.mockResolvedValue({
|
|
121
|
+
status: 200,
|
|
122
|
+
body: [{ name: 'John', ssn: '123-45-6789' }],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const agent = createAgent();
|
|
126
|
+
const result = await agent.run();
|
|
127
|
+
|
|
128
|
+
const ssnFinding = result.findings.find(f => f.description.includes('SSN'));
|
|
129
|
+
expect(ssnFinding).toBeDefined();
|
|
130
|
+
expect(ssnFinding!.severity).toBe('high');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('detects unmasked credit card numbers', async () => {
|
|
134
|
+
mockGet.mockResolvedValue({
|
|
135
|
+
status: 200,
|
|
136
|
+
body: [{ name: 'John', card: '4111-1111-1111-1111' }],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const agent = createAgent();
|
|
140
|
+
const result = await agent.run();
|
|
141
|
+
|
|
142
|
+
const ccFinding = result.findings.find(f => f.description.includes('Credit Card'));
|
|
143
|
+
expect(ccFinding).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('detects unmasked phone numbers', async () => {
|
|
147
|
+
mockGet.mockResolvedValue({
|
|
148
|
+
status: 200,
|
|
149
|
+
body: [{ name: 'John', phone: '(555) 123-4567' }],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const agent = createAgent();
|
|
153
|
+
const result = await agent.run();
|
|
154
|
+
|
|
155
|
+
const phoneFinding = result.findings.find(f => f.description.includes('Phone'));
|
|
156
|
+
expect(phoneFinding).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('does not flag when no PII present', async () => {
|
|
160
|
+
mockGet.mockResolvedValue({
|
|
161
|
+
status: 200,
|
|
162
|
+
body: [{ id: 1, status: 'active', count: 42 }],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const agent = createAgent();
|
|
166
|
+
const result = await agent.run();
|
|
167
|
+
|
|
168
|
+
const piiFindings = result.findings.filter(f => f.id.includes('unmasked'));
|
|
169
|
+
expect(piiFindings.length).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('handles non-200 responses', async () => {
|
|
173
|
+
mockGet.mockResolvedValue({ status: 404, body: {} });
|
|
174
|
+
|
|
175
|
+
const agent = createAgent();
|
|
176
|
+
const result = await agent.run();
|
|
177
|
+
|
|
178
|
+
const piiFindings = result.findings.filter(f => f.id.includes('unmasked'));
|
|
179
|
+
expect(piiFindings.length).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('handles request errors gracefully', async () => {
|
|
183
|
+
mockGet.mockRejectedValue(new Error('Connection refused'));
|
|
184
|
+
|
|
185
|
+
const agent = createAgent();
|
|
186
|
+
const result = await agent.run();
|
|
187
|
+
|
|
188
|
+
const scanError = result.findings.find(f => f.id.includes('scan-error'));
|
|
189
|
+
expect(scanError).toBeDefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('handles auth failure gracefully', async () => {
|
|
193
|
+
const { ApiClient } = await import('../../helpers/api-client');
|
|
194
|
+
(ApiClient.createAuthenticated as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
|
195
|
+
new Error('Auth failed'),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const agent = createAgent();
|
|
199
|
+
const result = await agent.run();
|
|
200
|
+
|
|
201
|
+
const authFailed = result.findings.find(f => f.id.includes('auth-failed'));
|
|
202
|
+
expect(authFailed).toBeDefined();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('records evidence with scan summary', async () => {
|
|
206
|
+
mockGet.mockResolvedValue({
|
|
207
|
+
status: 200,
|
|
208
|
+
body: [{ id: 1, name: 'Test' }],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const agent = createAgent();
|
|
212
|
+
const result = await agent.run();
|
|
213
|
+
|
|
214
|
+
expect(result.evidence.some(e => e.description.includes('PII scan'))).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('resolves environment in preFlight', async () => {
|
|
218
|
+
mockGet.mockResolvedValue({ status: 200, body: [] });
|
|
219
|
+
const agent = createAgent();
|
|
220
|
+
const result = await agent.run();
|
|
221
|
+
expect(result.findings.every(f => !f.description.includes('preFlight'))).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
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
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
name: 'test',
|
|
13
|
+
auth: {
|
|
14
|
+
loginUrl: 'http://localhost/login',
|
|
15
|
+
credentials: {
|
|
16
|
+
admin: { email: 'admin@test.com', password: 'secret' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
modules: [{ id: 'home', route: '/', sidebarIcon: true }],
|
|
20
|
+
environments: {
|
|
21
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
22
|
+
},
|
|
23
|
+
portals: [],
|
|
24
|
+
breakpoints: [],
|
|
25
|
+
integrations: [],
|
|
26
|
+
workers: [],
|
|
27
|
+
workflows: [],
|
|
28
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
29
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
30
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
31
|
+
agentClassification: { blocking: [], advisory: [46] },
|
|
32
|
+
thresholds: {
|
|
33
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
34
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
35
|
+
apiResponseTime: 2000,
|
|
36
|
+
bundleSizeLimit: 500000,
|
|
37
|
+
maxConsoleErrors: 0,
|
|
38
|
+
},
|
|
39
|
+
disabledAgents: [],
|
|
40
|
+
disabledAgentSet: new Set<number>(),
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Mock fetch
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
const fetchMock = vi.fn();
|
|
50
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Mock helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
const loginMock = vi.fn().mockResolvedValue(undefined);
|
|
57
|
+
|
|
58
|
+
vi.mock('../../helpers/navigation', () => ({
|
|
59
|
+
login: (...args: unknown[]) => loginMock(...args),
|
|
60
|
+
waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
|
|
61
|
+
navigateToSection: vi.fn().mockResolvedValue(undefined),
|
|
62
|
+
switchPortal: vi.fn().mockResolvedValue(undefined),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Mock Playwright
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
interface MockLocator {
|
|
70
|
+
count: ReturnType<typeof vi.fn>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function makeMockLocator(countVal = 0): MockLocator {
|
|
74
|
+
return { count: vi.fn().mockResolvedValue(countVal) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface MockPage {
|
|
78
|
+
close: ReturnType<typeof vi.fn>;
|
|
79
|
+
goto: ReturnType<typeof vi.fn>;
|
|
80
|
+
evaluate: ReturnType<typeof vi.fn>;
|
|
81
|
+
locator: ReturnType<typeof vi.fn>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeMockPage(): MockPage {
|
|
85
|
+
return {
|
|
86
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
87
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
88
|
+
evaluate: vi.fn().mockResolvedValue(null),
|
|
89
|
+
locator: vi.fn().mockReturnValue(makeMockLocator(1)),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let currentMockPage: MockPage;
|
|
94
|
+
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
95
|
+
|
|
96
|
+
vi.mock('playwright', async () => ({
|
|
97
|
+
chromium: {
|
|
98
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Dynamic import AFTER mocks
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
const { SeoAuditorAgent } = await import('../46-seo-auditor');
|
|
107
|
+
|
|
108
|
+
function createAgent(
|
|
109
|
+
config: ValidatedConfig = makeConfig(),
|
|
110
|
+
phase: Phase = 'beta',
|
|
111
|
+
): InstanceType<typeof SeoAuditorAgent> {
|
|
112
|
+
return new SeoAuditorAgent(config, phase, '/tmp/test-run');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Tests
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
describe('SeoAuditorAgent', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
122
|
+
loginMock.mockReset().mockResolvedValue(undefined);
|
|
123
|
+
|
|
124
|
+
currentMockPage = makeMockPage();
|
|
125
|
+
mockBrowser = {
|
|
126
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
127
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('has agentId 46 and correct agentName', () => {
|
|
132
|
+
const agent = createAgent();
|
|
133
|
+
expect(agent.agentId).toBe(46);
|
|
134
|
+
expect(agent.agentName).toBe('SEO Auditor');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('throws when modules array is empty', async () => {
|
|
138
|
+
const config = makeConfig({ modules: [] });
|
|
139
|
+
const agent = createAgent(config);
|
|
140
|
+
const result = await agent.run();
|
|
141
|
+
expect(result.status).toBe('failed');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('detects missing title', async () => {
|
|
145
|
+
currentMockPage.evaluate.mockImplementation((fn: unknown) => {
|
|
146
|
+
const fnStr = String(fn);
|
|
147
|
+
if (fnStr.includes('document.title')) return Promise.resolve('');
|
|
148
|
+
return Promise.resolve(null);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const agent = createAgent();
|
|
152
|
+
const result = await agent.run();
|
|
153
|
+
|
|
154
|
+
const noTitle = result.findings.find(f => f.id.includes('no-title'));
|
|
155
|
+
expect(noTitle).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('detects missing meta description', async () => {
|
|
159
|
+
currentMockPage.evaluate.mockImplementation((fn: unknown) => {
|
|
160
|
+
const fnStr = String(fn);
|
|
161
|
+
if (fnStr.includes('document.title')) return Promise.resolve('My Page');
|
|
162
|
+
if (fnStr.includes('meta[name="description"]')) return Promise.resolve(null);
|
|
163
|
+
return Promise.resolve(null);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const agent = createAgent();
|
|
167
|
+
const result = await agent.run();
|
|
168
|
+
|
|
169
|
+
const noMeta = result.findings.find(f => f.id.includes('no-meta-desc'));
|
|
170
|
+
expect(noMeta).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('detects missing h1', async () => {
|
|
174
|
+
currentMockPage.evaluate.mockResolvedValue('Page Title');
|
|
175
|
+
currentMockPage.locator.mockReturnValue(makeMockLocator(0));
|
|
176
|
+
|
|
177
|
+
const agent = createAgent();
|
|
178
|
+
const result = await agent.run();
|
|
179
|
+
|
|
180
|
+
const noH1 = result.findings.find(f => f.id.includes('no-h1'));
|
|
181
|
+
expect(noH1).toBeDefined();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('detects multiple h1 tags', async () => {
|
|
185
|
+
currentMockPage.evaluate.mockResolvedValue('Page Title');
|
|
186
|
+
currentMockPage.locator.mockReturnValue(makeMockLocator(3));
|
|
187
|
+
|
|
188
|
+
const agent = createAgent();
|
|
189
|
+
const result = await agent.run();
|
|
190
|
+
|
|
191
|
+
const multiH1 = result.findings.find(f => f.id.includes('multiple-h1'));
|
|
192
|
+
expect(multiH1).toBeDefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('checks robots.txt', async () => {
|
|
196
|
+
fetchMock.mockImplementation((url: string) => {
|
|
197
|
+
if (typeof url === 'string' && url.includes('robots.txt')) {
|
|
198
|
+
return Promise.resolve({ ok: false, status: 404 });
|
|
199
|
+
}
|
|
200
|
+
return Promise.resolve({ ok: true, status: 200 });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const agent = createAgent();
|
|
204
|
+
const result = await agent.run();
|
|
205
|
+
|
|
206
|
+
const noRobots = result.findings.find(f => f.id.includes('no-robots-txt'));
|
|
207
|
+
expect(noRobots).toBeDefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('checks sitemap.xml', async () => {
|
|
211
|
+
fetchMock.mockImplementation((url: string) => {
|
|
212
|
+
if (typeof url === 'string' && url.includes('sitemap.xml')) {
|
|
213
|
+
return Promise.resolve({ ok: false, status: 404 });
|
|
214
|
+
}
|
|
215
|
+
return Promise.resolve({ ok: true, status: 200 });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const agent = createAgent();
|
|
219
|
+
const result = await agent.run();
|
|
220
|
+
|
|
221
|
+
const noSitemap = result.findings.find(f => f.id.includes('no-sitemap-xml'));
|
|
222
|
+
expect(noSitemap).toBeDefined();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('produces medium finding when login fails', async () => {
|
|
226
|
+
loginMock.mockRejectedValue(new Error('Auth failed'));
|
|
227
|
+
|
|
228
|
+
const agent = createAgent();
|
|
229
|
+
const result = await agent.run();
|
|
230
|
+
|
|
231
|
+
const loginFinding = result.findings.find(f => f.id.includes('login-failed'));
|
|
232
|
+
expect(loginFinding).toBeDefined();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('produces medium finding when Playwright crashes', async () => {
|
|
236
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Browser crashed'));
|
|
237
|
+
|
|
238
|
+
const agent = createAgent();
|
|
239
|
+
const result = await agent.run();
|
|
240
|
+
|
|
241
|
+
const pwFinding = result.findings.find(f => f.id.includes('playwright-failure'));
|
|
242
|
+
expect(pwFinding).toBeDefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('closes browser even when an error occurs', async () => {
|
|
246
|
+
mockBrowser.newPage.mockRejectedValue(new Error('Launch error'));
|
|
247
|
+
const agent = createAgent();
|
|
248
|
+
await agent.run();
|
|
249
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('produces medium finding when navigation fails', async () => {
|
|
253
|
+
currentMockPage.goto.mockRejectedValue(new Error('Navigation failed'));
|
|
254
|
+
|
|
255
|
+
const agent = createAgent();
|
|
256
|
+
const result = await agent.run();
|
|
257
|
+
|
|
258
|
+
const navFinding = result.findings.find(f => f.id.includes('nav-failed'));
|
|
259
|
+
expect(navFinding).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
});
|