@avi770/testteam 1.2.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 +54 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/agents/01-analyst.ts +100 -0
- package/agents/02-seed-architect.ts +59 -0
- package/agents/03-test-generator.ts +191 -0
- package/agents/04-unit-runner.ts +160 -0
- package/agents/05-browser-crawler.ts +790 -0
- package/agents/06-api-exerciser.ts +311 -0
- package/agents/07-security-scout.ts +188 -0
- package/agents/08-a11y-guardian.ts +212 -0
- package/agents/09-healer.ts +228 -0
- package/agents/10-reporter.ts +266 -0
- package/agents/11-fixer.ts +253 -0
- package/agents/12-ux-inspector.ts +444 -0
- package/agents/13-performance-profiler.ts +271 -0
- package/agents/14-data-integrity-auditor.ts +417 -0
- package/agents/15-regression-sentinel.ts +307 -0
- package/agents/16-chaos-agent.ts +228 -0
- package/agents/17-documentation-validator.ts +266 -0
- package/agents/18-integration-watchdog.ts +178 -0
- package/agents/19-tenant-isolation-auditor.ts +199 -0
- package/agents/20-workflow-completion-tester.ts +203 -0
- package/agents/21-state-session-tester.ts +262 -0
- package/agents/22-email-notification-verifier.ts +244 -0
- package/agents/23-migration-tester.ts +80 -0
- package/agents/__tests__/01-analyst.test.ts +188 -0
- package/agents/__tests__/02-seed-architect.test.ts +152 -0
- package/agents/__tests__/03-test-generator-full.test.ts +321 -0
- package/agents/__tests__/03-test-generator.test.ts +318 -0
- package/agents/__tests__/04-unit-runner.test.ts +320 -0
- package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
- package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
- package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
- package/agents/__tests__/05-browser-crawler.test.ts +518 -0
- package/agents/__tests__/06-api-exerciser.test.ts +619 -0
- package/agents/__tests__/07-security-scout.test.ts +382 -0
- package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
- package/agents/__tests__/09-healer.test.ts +384 -0
- package/agents/__tests__/10-reporter.test.ts +366 -0
- package/agents/__tests__/11-fixer.test.ts +406 -0
- package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
- package/agents/__tests__/12-ux-inspector.test.ts +443 -0
- package/agents/__tests__/13-performance-profiler.test.ts +411 -0
- package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
- package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
- package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
- package/agents/__tests__/16-chaos-agent.test.ts +427 -0
- package/agents/__tests__/17-documentation-validator.test.ts +402 -0
- package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
- package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
- package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
- package/agents/__tests__/21-state-session-tester.test.ts +374 -0
- package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
- package/agents/__tests__/23-migration-tester.test.ts +145 -0
- package/agents/__tests__/base-agent.test.ts +188 -0
- package/agents/__tests__/registry.test.ts +218 -0
- package/agents/base-agent.ts +77 -0
- package/agents/registry.ts +136 -0
- package/baselines/api-schemas/.gitkeep +0 -0
- package/baselines/performance/.gitkeep +0 -0
- package/baselines/screenshots/.gitkeep +0 -0
- package/bin/testteam.js +10 -0
- package/core/__tests__/ci-output.test.ts +430 -0
- package/core/__tests__/cli.test.ts +387 -0
- package/core/__tests__/config.test.ts +78 -0
- package/core/__tests__/cost-tracker.test.ts +158 -0
- package/core/__tests__/evidence.test.ts +265 -0
- package/core/__tests__/fix-loop.test.ts +210 -0
- package/core/__tests__/health-check.test.ts +44 -0
- package/core/__tests__/init.test.ts +609 -0
- package/core/__tests__/integration.test.ts +204 -0
- package/core/__tests__/license-gen.test.ts +227 -0
- package/core/__tests__/license.test.ts +326 -0
- package/core/__tests__/multi-browser.test.ts +278 -0
- package/core/__tests__/orchestrator.test.ts +519 -0
- package/core/__tests__/phase-gate.test.ts +43 -0
- package/core/__tests__/report-html.test.ts +398 -0
- package/core/__tests__/report-upload.test.ts +325 -0
- package/core/__tests__/run-counter.test.ts +234 -0
- package/core/ci-output.ts +240 -0
- package/core/cli.ts +232 -0
- package/core/config.ts +178 -0
- package/core/cost-tracker.ts +59 -0
- package/core/evidence.ts +132 -0
- package/core/fix-loop.ts +85 -0
- package/core/health-check.ts +54 -0
- package/core/init.ts +546 -0
- package/core/license-gen.ts +212 -0
- package/core/license.ts +211 -0
- package/core/messages.ts +67 -0
- package/core/multi-browser.ts +136 -0
- package/core/orchestrator.ts +354 -0
- package/core/phase-gate.ts +55 -0
- package/core/report-html.ts +657 -0
- package/core/report-upload.ts +188 -0
- package/core/run-counter.ts +175 -0
- package/core/types.ts +57 -0
- package/dist/agents/01-analyst.d.ts +11 -0
- package/dist/agents/01-analyst.d.ts.map +1 -0
- package/dist/agents/01-analyst.js +75 -0
- package/dist/agents/01-analyst.js.map +1 -0
- package/dist/agents/02-seed-architect.d.ts +11 -0
- package/dist/agents/02-seed-architect.d.ts.map +1 -0
- package/dist/agents/02-seed-architect.js +51 -0
- package/dist/agents/02-seed-architect.js.map +1 -0
- package/dist/agents/03-test-generator.d.ts +9 -0
- package/dist/agents/03-test-generator.d.ts.map +1 -0
- package/dist/agents/03-test-generator.js +167 -0
- package/dist/agents/03-test-generator.js.map +1 -0
- package/dist/agents/04-unit-runner.d.ts +9 -0
- package/dist/agents/04-unit-runner.d.ts.map +1 -0
- package/dist/agents/04-unit-runner.js +113 -0
- package/dist/agents/04-unit-runner.js.map +1 -0
- package/dist/agents/05-browser-crawler.d.ts +30 -0
- package/dist/agents/05-browser-crawler.d.ts.map +1 -0
- package/dist/agents/05-browser-crawler.js +685 -0
- package/dist/agents/05-browser-crawler.js.map +1 -0
- package/dist/agents/06-api-exerciser.d.ts +23 -0
- package/dist/agents/06-api-exerciser.d.ts.map +1 -0
- package/dist/agents/06-api-exerciser.js +253 -0
- package/dist/agents/06-api-exerciser.js.map +1 -0
- package/dist/agents/07-security-scout.d.ts +11 -0
- package/dist/agents/07-security-scout.d.ts.map +1 -0
- package/dist/agents/07-security-scout.js +142 -0
- package/dist/agents/07-security-scout.js.map +1 -0
- package/dist/agents/08-a11y-guardian.d.ts +13 -0
- package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
- package/dist/agents/08-a11y-guardian.js +176 -0
- package/dist/agents/08-a11y-guardian.js.map +1 -0
- package/dist/agents/09-healer.d.ts +33 -0
- package/dist/agents/09-healer.d.ts.map +1 -0
- package/dist/agents/09-healer.js +167 -0
- package/dist/agents/09-healer.js.map +1 -0
- package/dist/agents/10-reporter.d.ts +26 -0
- package/dist/agents/10-reporter.d.ts.map +1 -0
- package/dist/agents/10-reporter.js +215 -0
- package/dist/agents/10-reporter.js.map +1 -0
- package/dist/agents/11-fixer.d.ts +26 -0
- package/dist/agents/11-fixer.d.ts.map +1 -0
- package/dist/agents/11-fixer.js +195 -0
- package/dist/agents/11-fixer.js.map +1 -0
- package/dist/agents/12-ux-inspector.d.ts +15 -0
- package/dist/agents/12-ux-inspector.d.ts.map +1 -0
- package/dist/agents/12-ux-inspector.js +364 -0
- package/dist/agents/12-ux-inspector.js.map +1 -0
- package/dist/agents/13-performance-profiler.d.ts +13 -0
- package/dist/agents/13-performance-profiler.d.ts.map +1 -0
- package/dist/agents/13-performance-profiler.js +216 -0
- package/dist/agents/13-performance-profiler.js.map +1 -0
- package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
- package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
- package/dist/agents/14-data-integrity-auditor.js +356 -0
- package/dist/agents/14-data-integrity-auditor.js.map +1 -0
- package/dist/agents/15-regression-sentinel.d.ts +25 -0
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
- package/dist/agents/15-regression-sentinel.js +251 -0
- package/dist/agents/15-regression-sentinel.js.map +1 -0
- package/dist/agents/16-chaos-agent.d.ts +9 -0
- package/dist/agents/16-chaos-agent.d.ts.map +1 -0
- package/dist/agents/16-chaos-agent.js +207 -0
- package/dist/agents/16-chaos-agent.js.map +1 -0
- package/dist/agents/17-documentation-validator.d.ts +31 -0
- package/dist/agents/17-documentation-validator.d.ts.map +1 -0
- package/dist/agents/17-documentation-validator.js +246 -0
- package/dist/agents/17-documentation-validator.js.map +1 -0
- package/dist/agents/18-integration-watchdog.d.ts +10 -0
- package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
- package/dist/agents/18-integration-watchdog.js +138 -0
- package/dist/agents/18-integration-watchdog.js.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.js +166 -0
- package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
- package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
- package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
- package/dist/agents/20-workflow-completion-tester.js +159 -0
- package/dist/agents/20-workflow-completion-tester.js.map +1 -0
- package/dist/agents/21-state-session-tester.d.ts +10 -0
- package/dist/agents/21-state-session-tester.d.ts.map +1 -0
- package/dist/agents/21-state-session-tester.js +233 -0
- package/dist/agents/21-state-session-tester.js.map +1 -0
- package/dist/agents/22-email-notification-verifier.d.ts +11 -0
- package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
- package/dist/agents/22-email-notification-verifier.js +199 -0
- package/dist/agents/22-email-notification-verifier.js.map +1 -0
- package/dist/agents/23-migration-tester.d.ts +10 -0
- package/dist/agents/23-migration-tester.d.ts.map +1 -0
- package/dist/agents/23-migration-tester.js +74 -0
- package/dist/agents/23-migration-tester.js.map +1 -0
- package/dist/agents/base-agent.d.ts +19 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +67 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/registry.d.ts +29 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +117 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/core/ci-output.d.ts +35 -0
- package/dist/core/ci-output.d.ts.map +1 -0
- package/dist/core/ci-output.js +193 -0
- package/dist/core/ci-output.js.map +1 -0
- package/dist/core/cli.d.ts +11 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +197 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +111 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +42 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +22 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +41 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/evidence.d.ts +28 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +95 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fix-loop.d.ts +29 -0
- package/dist/core/fix-loop.d.ts.map +1 -0
- package/dist/core/fix-loop.js +70 -0
- package/dist/core/fix-loop.js.map +1 -0
- package/dist/core/health-check.d.ts +21 -0
- package/dist/core/health-check.d.ts.map +1 -0
- package/dist/core/health-check.js +26 -0
- package/dist/core/health-check.js.map +1 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +435 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/license-gen.d.ts +12 -0
- package/dist/core/license-gen.d.ts.map +1 -0
- package/dist/core/license-gen.js +169 -0
- package/dist/core/license-gen.js.map +1 -0
- package/dist/core/license.d.ts +33 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +170 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +47 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/multi-browser.d.ts +36 -0
- package/dist/core/multi-browser.d.ts.map +1 -0
- package/dist/core/multi-browser.js +88 -0
- package/dist/core/multi-browser.js.map +1 -0
- package/dist/core/orchestrator.d.ts +48 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +291 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/phase-gate.d.ts +4 -0
- package/dist/core/phase-gate.d.ts.map +1 -0
- package/dist/core/phase-gate.js +39 -0
- package/dist/core/phase-gate.js.map +1 -0
- package/dist/core/report-html.d.ts +9 -0
- package/dist/core/report-html.d.ts.map +1 -0
- package/dist/core/report-html.js +617 -0
- package/dist/core/report-html.js.map +1 -0
- package/dist/core/report-upload.d.ts +16 -0
- package/dist/core/report-upload.d.ts.map +1 -0
- package/dist/core/report-upload.js +124 -0
- package/dist/core/report-upload.js.map +1 -0
- package/dist/core/run-counter.d.ts +40 -0
- package/dist/core/run-counter.d.ts.map +1 -0
- package/dist/core/run-counter.js +120 -0
- package/dist/core/run-counter.js.map +1 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/helpers/api-client.d.ts +30 -0
- package/dist/helpers/api-client.d.ts.map +1 -0
- package/dist/helpers/api-client.js +77 -0
- package/dist/helpers/api-client.js.map +1 -0
- package/dist/helpers/element-discovery.d.ts +18 -0
- package/dist/helpers/element-discovery.d.ts.map +1 -0
- package/dist/helpers/element-discovery.js +82 -0
- package/dist/helpers/element-discovery.js.map +1 -0
- package/dist/helpers/env-resolver.d.ts +29 -0
- package/dist/helpers/env-resolver.d.ts.map +1 -0
- package/dist/helpers/env-resolver.js +51 -0
- package/dist/helpers/env-resolver.js.map +1 -0
- package/dist/helpers/form-filler.d.ts +13 -0
- package/dist/helpers/form-filler.d.ts.map +1 -0
- package/dist/helpers/form-filler.js +98 -0
- package/dist/helpers/form-filler.js.map +1 -0
- package/dist/helpers/modal-handler.d.ts +16 -0
- package/dist/helpers/modal-handler.d.ts.map +1 -0
- package/dist/helpers/modal-handler.js +95 -0
- package/dist/helpers/modal-handler.js.map +1 -0
- package/dist/helpers/navigation.d.ts +37 -0
- package/dist/helpers/navigation.d.ts.map +1 -0
- package/dist/helpers/navigation.js +83 -0
- package/dist/helpers/navigation.js.map +1 -0
- package/dist/helpers/quality-gate.d.ts +17 -0
- package/dist/helpers/quality-gate.d.ts.map +1 -0
- package/dist/helpers/quality-gate.js +144 -0
- package/dist/helpers/quality-gate.js.map +1 -0
- package/dist/helpers/screenshot.d.ts +24 -0
- package/dist/helpers/screenshot.d.ts.map +1 -0
- package/dist/helpers/screenshot.js +76 -0
- package/dist/helpers/screenshot.js.map +1 -0
- package/dist/helpers/seed-validator.d.ts +15 -0
- package/dist/helpers/seed-validator.d.ts.map +1 -0
- package/dist/helpers/seed-validator.js +53 -0
- package/dist/helpers/seed-validator.js.map +1 -0
- package/helpers/__tests__/api-client.test.ts +199 -0
- package/helpers/__tests__/element-discovery.test.ts +202 -0
- package/helpers/__tests__/form-filler-extended.test.ts +212 -0
- package/helpers/__tests__/form-filler.test.ts +99 -0
- package/helpers/__tests__/modal-handler.test.ts +152 -0
- package/helpers/__tests__/navigation.test.ts +214 -0
- package/helpers/__tests__/quality-gate.test.ts +117 -0
- package/helpers/__tests__/screenshot.test.ts +139 -0
- package/helpers/__tests__/seed-validator.test.ts +114 -0
- package/helpers/api-client.ts +111 -0
- package/helpers/element-discovery.ts +105 -0
- package/helpers/env-resolver.ts +69 -0
- package/helpers/form-filler.ts +126 -0
- package/helpers/modal-handler.ts +108 -0
- package/helpers/navigation.ts +100 -0
- package/helpers/quality-gate.ts +180 -0
- package/helpers/screenshot.ts +111 -0
- package/helpers/seed-validator.ts +70 -0
- package/package.json +88 -0
|
@@ -0,0 +1,427 @@
|
|
|
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
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const stubConfig: ValidatedConfig = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
name: 'test',
|
|
12
|
+
auth: {
|
|
13
|
+
loginUrl: 'http://localhost/login',
|
|
14
|
+
credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
|
|
15
|
+
},
|
|
16
|
+
modules: [
|
|
17
|
+
{ id: 'dashboard', route: '/dashboard', sidebarIcon: true },
|
|
18
|
+
{ id: 'invoices', route: '/invoices', sidebarIcon: true },
|
|
19
|
+
{ id: 'clients', route: '/clients', sidebarIcon: true },
|
|
20
|
+
],
|
|
21
|
+
environments: {
|
|
22
|
+
beta: { baseUrl: 'http://localhost:3000', seed: false },
|
|
23
|
+
},
|
|
24
|
+
portals: [],
|
|
25
|
+
breakpoints: [],
|
|
26
|
+
integrations: [],
|
|
27
|
+
workers: [],
|
|
28
|
+
workflows: [],
|
|
29
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
30
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
31
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
32
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
33
|
+
thresholds: {
|
|
34
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
35
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
36
|
+
apiResponseTime: 2000,
|
|
37
|
+
bundleSizeLimit: 500000,
|
|
38
|
+
maxConsoleErrors: 0,
|
|
39
|
+
},
|
|
40
|
+
disabledAgents: [],
|
|
41
|
+
disabledAgentSet: new Set<number>(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Mock global fetch (preFlight URL reachability + large data scenarios)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const fetchMock = vi.fn();
|
|
49
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Mock Playwright
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
type PageErrorListener = (err: Error) => void;
|
|
56
|
+
|
|
57
|
+
interface MockPage {
|
|
58
|
+
goto: ReturnType<typeof vi.fn>;
|
|
59
|
+
evaluate: ReturnType<typeof vi.fn>;
|
|
60
|
+
route: ReturnType<typeof vi.fn>;
|
|
61
|
+
url: ReturnType<typeof vi.fn>;
|
|
62
|
+
content: ReturnType<typeof vi.fn>;
|
|
63
|
+
close: ReturnType<typeof vi.fn>;
|
|
64
|
+
on: ReturnType<typeof vi.fn>;
|
|
65
|
+
_pageErrorListeners: PageErrorListener[];
|
|
66
|
+
emitPageError(err: Error): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeMockPage(overrides: Partial<MockPage> = {}): MockPage {
|
|
70
|
+
const pageErrorListeners: PageErrorListener[] = [];
|
|
71
|
+
const page: MockPage = {
|
|
72
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
73
|
+
evaluate: vi.fn().mockResolvedValue(undefined),
|
|
74
|
+
route: vi.fn().mockResolvedValue(undefined),
|
|
75
|
+
url: vi.fn().mockReturnValue('http://localhost:3000/login'),
|
|
76
|
+
content: vi.fn().mockResolvedValue('<html>login</html>'),
|
|
77
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
78
|
+
on: vi.fn().mockImplementation((event: string, listener: PageErrorListener) => {
|
|
79
|
+
if (event === 'pageerror') pageErrorListeners.push(listener);
|
|
80
|
+
}),
|
|
81
|
+
_pageErrorListeners: pageErrorListeners,
|
|
82
|
+
emitPageError(err: Error) {
|
|
83
|
+
for (const l of pageErrorListeners) l(err);
|
|
84
|
+
},
|
|
85
|
+
...overrides,
|
|
86
|
+
};
|
|
87
|
+
return page;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let currentMockPage: MockPage;
|
|
91
|
+
let mockContext: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
92
|
+
let mockBrowser: { newContext: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
|
|
93
|
+
|
|
94
|
+
vi.mock('playwright', async () => ({
|
|
95
|
+
chromium: {
|
|
96
|
+
launch: vi.fn().mockImplementation(async () => mockBrowser),
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Import after mocks
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
const { ChaosAgent } = await import('../16-chaos-agent');
|
|
105
|
+
|
|
106
|
+
function createAgent(phase: Phase = 'beta'): InstanceType<typeof ChaosAgent> {
|
|
107
|
+
return new ChaosAgent(stubConfig, phase, '/tmp/test-run');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Tests
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
describe('ChaosAgent', () => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
fetchMock.mockReset();
|
|
117
|
+
|
|
118
|
+
currentMockPage = makeMockPage();
|
|
119
|
+
mockContext = {
|
|
120
|
+
newPage: vi.fn().mockResolvedValue(currentMockPage),
|
|
121
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
122
|
+
};
|
|
123
|
+
mockBrowser = {
|
|
124
|
+
newContext: vi.fn().mockResolvedValue(mockContext),
|
|
125
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Default: base URL reachable
|
|
129
|
+
fetchMock.mockResolvedValue({ ok: true, status: 200 });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('has agentId 16 and agentName "Chaos Agent"', () => {
|
|
133
|
+
const agent = createAgent();
|
|
134
|
+
expect(agent.agentId).toBe(16);
|
|
135
|
+
expect(agent.agentName).toBe('Chaos Agent');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// -------------------------------------------------------------------------
|
|
139
|
+
// preFlight()
|
|
140
|
+
// -------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
describe('preFlight()', () => {
|
|
143
|
+
it('throws when phase is not "beta" or "uat"', async () => {
|
|
144
|
+
const agent = createAgent('alpha' as Phase);
|
|
145
|
+
const result = await agent.run();
|
|
146
|
+
|
|
147
|
+
expect(result.status).toBe('failed');
|
|
148
|
+
expect(result.findings[0].description).toContain('beta');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('does not throw when base URL is not reachable (resolveEnvironment handles fallback)', async () => {
|
|
152
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
153
|
+
|
|
154
|
+
const agent = createAgent('beta');
|
|
155
|
+
const result = await agent.run();
|
|
156
|
+
|
|
157
|
+
// resolveEnvironment falls back gracefully — preFlight no longer throws
|
|
158
|
+
const preflightError = result.findings.find(
|
|
159
|
+
(f) => f.description.includes('not reachable'),
|
|
160
|
+
);
|
|
161
|
+
expect(preflightError).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('passes for phase "beta" when base URL is reachable', async () => {
|
|
165
|
+
const agent = createAgent('beta');
|
|
166
|
+
const result = await agent.run();
|
|
167
|
+
|
|
168
|
+
expect(result.findings.some((f) => f.description.includes('requires phase'))).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('passes for phase "uat" when base URL is reachable', async () => {
|
|
172
|
+
const uatConfig: ValidatedConfig = {
|
|
173
|
+
...stubConfig,
|
|
174
|
+
environments: {
|
|
175
|
+
uat: { baseUrl: 'http://localhost:3000', seed: false },
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const agent = new ChaosAgent(uatConfig, 'uat', '/tmp/test-run');
|
|
179
|
+
const result = await agent.run();
|
|
180
|
+
|
|
181
|
+
expect(result.findings.some((f) => f.description.includes('requires phase'))).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// -------------------------------------------------------------------------
|
|
186
|
+
// execute() — token expiry scenario
|
|
187
|
+
// -------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
describe('token expiry scenario', () => {
|
|
190
|
+
it('creates a critical security finding when app does not redirect to login after expired token', async () => {
|
|
191
|
+
// url() returns a non-login URL after goto with expired JWT
|
|
192
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/dashboard');
|
|
193
|
+
|
|
194
|
+
const agent = createAgent('beta');
|
|
195
|
+
const result = await agent.run();
|
|
196
|
+
|
|
197
|
+
const tokenFinding = result.findings.find((f) => f.id.includes('token-expiry'));
|
|
198
|
+
expect(tokenFinding).toBeDefined();
|
|
199
|
+
expect(tokenFinding!.severity).toBe('critical');
|
|
200
|
+
expect(tokenFinding!.type).toBe('code-bug-security');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('creates no finding when app redirects to login after expired token', async () => {
|
|
204
|
+
// url() returns a login URL — correct behaviour
|
|
205
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
206
|
+
|
|
207
|
+
const agent = createAgent('beta');
|
|
208
|
+
const result = await agent.run();
|
|
209
|
+
|
|
210
|
+
const tokenFinding = result.findings.find((f) => f.id.includes('token-expiry'));
|
|
211
|
+
expect(tokenFinding).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// -------------------------------------------------------------------------
|
|
216
|
+
// execute() — network throttle scenario
|
|
217
|
+
// -------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
describe('network throttle scenario', () => {
|
|
220
|
+
it('creates a high finding when uncaught JS errors occur during network abort', async () => {
|
|
221
|
+
// Simulate the page emitting a pageerror after route.abort
|
|
222
|
+
currentMockPage.goto.mockImplementation(async () => {
|
|
223
|
+
currentMockPage.emitPageError(new Error('TypeError: Failed to fetch'));
|
|
224
|
+
return undefined;
|
|
225
|
+
});
|
|
226
|
+
// Make first goto (token scenario) return login url to avoid token-expiry finding
|
|
227
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
228
|
+
|
|
229
|
+
const agent = createAgent('beta');
|
|
230
|
+
const result = await agent.run();
|
|
231
|
+
|
|
232
|
+
const throttleFinding = result.findings.find((f) => f.id.includes('network-throttle'));
|
|
233
|
+
expect(throttleFinding).toBeDefined();
|
|
234
|
+
expect(throttleFinding!.severity).toBe('high');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('creates no finding when app handles network abort gracefully', async () => {
|
|
238
|
+
// No pageerror events emitted
|
|
239
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
240
|
+
|
|
241
|
+
const agent = createAgent('beta');
|
|
242
|
+
const result = await agent.run();
|
|
243
|
+
|
|
244
|
+
const throttleFinding = result.findings.find((f) => f.id.includes('network-throttle'));
|
|
245
|
+
expect(throttleFinding).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
// execute() — large data scenario
|
|
251
|
+
// -------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
describe('large data scenario', () => {
|
|
254
|
+
it('creates a high finding when a list endpoint returns 500 for large requests', async () => {
|
|
255
|
+
// First call: preFlight HEAD (ok), subsequent calls: large-data endpoint returns 500
|
|
256
|
+
fetchMock
|
|
257
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
258
|
+
.mockResolvedValue({ ok: false, status: 500 }); // list endpoints
|
|
259
|
+
|
|
260
|
+
// token scenario: url = /login (no finding)
|
|
261
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
262
|
+
|
|
263
|
+
const agent = createAgent('beta');
|
|
264
|
+
const result = await agent.run();
|
|
265
|
+
|
|
266
|
+
const largeDataFinding = result.findings.find((f) => f.id.includes('large-data'));
|
|
267
|
+
expect(largeDataFinding).toBeDefined();
|
|
268
|
+
expect(largeDataFinding!.severity).toBe('high');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('creates no finding when list endpoints respond normally to large requests', async () => {
|
|
272
|
+
// All fetches succeed
|
|
273
|
+
fetchMock.mockResolvedValue({ ok: true, status: 200 });
|
|
274
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
275
|
+
|
|
276
|
+
const agent = createAgent('beta');
|
|
277
|
+
const result = await agent.run();
|
|
278
|
+
|
|
279
|
+
const largeDataFindings = result.findings.filter((f) => f.id.includes('large-data'));
|
|
280
|
+
expect(largeDataFindings).toHaveLength(0);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// -------------------------------------------------------------------------
|
|
285
|
+
// UAT mode — only non-destructive scenarios
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
describe('UAT mode', () => {
|
|
289
|
+
it('skips large data scenario in UAT mode', async () => {
|
|
290
|
+
const uatConfig: ValidatedConfig = {
|
|
291
|
+
...stubConfig,
|
|
292
|
+
environments: {
|
|
293
|
+
uat: { baseUrl: 'http://localhost:3000', seed: false },
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// First call: preFlight HEAD (ok), subsequent calls: large-data endpoint returns 500
|
|
298
|
+
fetchMock
|
|
299
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
300
|
+
.mockResolvedValue({ ok: false, status: 500 }); // list endpoints
|
|
301
|
+
|
|
302
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
303
|
+
|
|
304
|
+
const agent = new ChaosAgent(uatConfig, 'uat', '/tmp/test-run');
|
|
305
|
+
const result = await agent.run();
|
|
306
|
+
|
|
307
|
+
const largeDataFindings = result.findings.filter((f) => f.id.includes('large-data'));
|
|
308
|
+
expect(largeDataFindings).toHaveLength(0);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// No environment configured
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe('no environment', () => {
|
|
317
|
+
it('fails or produces finding when no environment is configured', async () => {
|
|
318
|
+
const noEnvConfig: ValidatedConfig = {
|
|
319
|
+
...stubConfig,
|
|
320
|
+
environments: {},
|
|
321
|
+
};
|
|
322
|
+
fetchMock.mockRejectedValue(new Error('no env'));
|
|
323
|
+
const agent = new ChaosAgent(noEnvConfig, 'beta', '/tmp/test-run');
|
|
324
|
+
const result = await agent.run();
|
|
325
|
+
|
|
326
|
+
// Either preFlight throws (failed status) or execute finds no env
|
|
327
|
+
expect(
|
|
328
|
+
result.status === 'failed' ||
|
|
329
|
+
result.findings.some(f => f.severity === 'critical' || f.description.includes('No environment')),
|
|
330
|
+
).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// -------------------------------------------------------------------------
|
|
335
|
+
// Token expiry scenario: outer try/catch (chromium.launch fails)
|
|
336
|
+
// -------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
describe('token expiry scenario — browser failure', () => {
|
|
339
|
+
it('creates a skipped finding when token scenario browser launch fails', async () => {
|
|
340
|
+
mockBrowser.newContext.mockRejectedValue(new Error('Browser not available'));
|
|
341
|
+
|
|
342
|
+
const agent = createAgent('beta');
|
|
343
|
+
const result = await agent.run();
|
|
344
|
+
|
|
345
|
+
const skippedFinding = result.findings.find(f => f.id.includes('token-expiry-skipped'));
|
|
346
|
+
expect(skippedFinding).toBeDefined();
|
|
347
|
+
expect(skippedFinding!.severity).toBe('medium');
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// -------------------------------------------------------------------------
|
|
352
|
+
// Large data: 502 and 503 status codes
|
|
353
|
+
// -------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
describe('large data scenario — 502/503', () => {
|
|
356
|
+
it('creates a finding for 502 status on large data request', async () => {
|
|
357
|
+
fetchMock
|
|
358
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
359
|
+
.mockResolvedValue({ ok: false, status: 502 });
|
|
360
|
+
|
|
361
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
362
|
+
|
|
363
|
+
const agent = createAgent('beta');
|
|
364
|
+
const result = await agent.run();
|
|
365
|
+
|
|
366
|
+
const largeFinding = result.findings.find(f => f.id.includes('large-data'));
|
|
367
|
+
expect(largeFinding).toBeDefined();
|
|
368
|
+
expect(largeFinding!.severity).toBe('high');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('creates a finding for 503 status on large data request', async () => {
|
|
372
|
+
fetchMock
|
|
373
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
374
|
+
.mockResolvedValue({ ok: false, status: 503 });
|
|
375
|
+
|
|
376
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
377
|
+
|
|
378
|
+
const agent = createAgent('beta');
|
|
379
|
+
const result = await agent.run();
|
|
380
|
+
|
|
381
|
+
const largeFinding = result.findings.find(f => f.id.includes('large-data'));
|
|
382
|
+
expect(largeFinding).toBeDefined();
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// -------------------------------------------------------------------------
|
|
387
|
+
// Large data: fetch throws (endpoint does not exist)
|
|
388
|
+
// -------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
describe('large data scenario — fetch throws', () => {
|
|
391
|
+
it('skips endpoints where fetch throws (no finding)', async () => {
|
|
392
|
+
fetchMock
|
|
393
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
394
|
+
.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
395
|
+
|
|
396
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
397
|
+
|
|
398
|
+
const agent = createAgent('beta');
|
|
399
|
+
const result = await agent.run();
|
|
400
|
+
|
|
401
|
+
const largeFindings = result.findings.filter(f =>
|
|
402
|
+
f.id.includes('large-data') && !f.id.includes('error'),
|
|
403
|
+
);
|
|
404
|
+
expect(largeFindings).toHaveLength(0);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// -------------------------------------------------------------------------
|
|
409
|
+
// Large data: outer catch (unexpected error)
|
|
410
|
+
// -------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
describe('large data scenario — 200 OK responses', () => {
|
|
413
|
+
it('creates no finding when endpoints return 200 OK for large requests', async () => {
|
|
414
|
+
fetchMock
|
|
415
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // preFlight
|
|
416
|
+
.mockResolvedValue({ ok: true, status: 200 }); // all endpoints succeed
|
|
417
|
+
|
|
418
|
+
currentMockPage.url.mockReturnValue('http://localhost:3000/login');
|
|
419
|
+
|
|
420
|
+
const agent = createAgent('beta');
|
|
421
|
+
const result = await agent.run();
|
|
422
|
+
|
|
423
|
+
const largeFindings = result.findings.filter(f => f.id.includes('large-data'));
|
|
424
|
+
expect(largeFindings).toHaveLength(0);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|