@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,441 @@
|
|
|
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: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
17
|
+
environments: {
|
|
18
|
+
beta: { baseUrl: 'http://localhost:3000', seed: false },
|
|
19
|
+
},
|
|
20
|
+
portals: [],
|
|
21
|
+
breakpoints: [],
|
|
22
|
+
integrations: [],
|
|
23
|
+
workers: [],
|
|
24
|
+
workflows: [],
|
|
25
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
26
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
27
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
28
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
29
|
+
thresholds: {
|
|
30
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
31
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
32
|
+
apiResponseTime: 2000,
|
|
33
|
+
bundleSizeLimit: 500000,
|
|
34
|
+
maxConsoleErrors: 0,
|
|
35
|
+
},
|
|
36
|
+
disabledAgents: [],
|
|
37
|
+
disabledAgentSet: new Set<number>(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Mock global fetch (Mailhog health, Mailhog API, link resolution)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const fetchMock = vi.fn();
|
|
45
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Mock ApiClient (password reset)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
const apiClientPostMock = vi.fn();
|
|
52
|
+
|
|
53
|
+
vi.mock('../../helpers/api-client', () => ({
|
|
54
|
+
ApiClient: class MockApiClient {
|
|
55
|
+
post(...args: unknown[]) {
|
|
56
|
+
return apiClientPostMock(...args);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Helpers for building Mailhog message objects
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
interface MailhogMessage {
|
|
66
|
+
ID: string;
|
|
67
|
+
Content: { Headers: Record<string, string[]>; Body: string };
|
|
68
|
+
Created: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function makeMailhogMessage(body: string, id = 'msg-001'): MailhogMessage {
|
|
72
|
+
return {
|
|
73
|
+
ID: id,
|
|
74
|
+
Content: {
|
|
75
|
+
Headers: {
|
|
76
|
+
Subject: ['Password Reset'],
|
|
77
|
+
To: ['admin@test.com'],
|
|
78
|
+
},
|
|
79
|
+
Body: body,
|
|
80
|
+
},
|
|
81
|
+
Created: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mailhogApiResponse(messages: MailhogMessage[]) {
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
status: 200,
|
|
89
|
+
json: async () => ({
|
|
90
|
+
items: messages,
|
|
91
|
+
total: messages.length,
|
|
92
|
+
count: messages.length,
|
|
93
|
+
start: 0,
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Import after mocks
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
const { EmailNotificationVerifierAgent } = await import('../22-email-notification-verifier');
|
|
103
|
+
|
|
104
|
+
function createAgent(phase: Phase = 'beta'): InstanceType<typeof EmailNotificationVerifierAgent> {
|
|
105
|
+
return new EmailNotificationVerifierAgent(stubConfig, phase, '/tmp/test-run');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Tests
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
describe('EmailNotificationVerifierAgent', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
vi.resetAllMocks();
|
|
115
|
+
// Default: password reset API returns success
|
|
116
|
+
apiClientPostMock.mockResolvedValue({ status: 200, body: {} });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('has agentId 22 and agentName "Email & Notification Verifier"', () => {
|
|
120
|
+
const agent = createAgent();
|
|
121
|
+
expect(agent.agentId).toBe(22);
|
|
122
|
+
expect(agent.agentName).toBe('Email & Notification Verifier');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
// preFlight() — Mailhog availability (advisory, never throws)
|
|
127
|
+
// -------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
describe('preFlight() — Mailhog advisory', () => {
|
|
130
|
+
it('does not throw when Mailhog is unreachable (advisory only)', async () => {
|
|
131
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
132
|
+
|
|
133
|
+
const agent = createAgent();
|
|
134
|
+
// run() must not throw even when Mailhog is down
|
|
135
|
+
const result = await agent.run();
|
|
136
|
+
|
|
137
|
+
// Should complete with a low advisory finding
|
|
138
|
+
expect(result.status).toBe('passed'); // low severity does not fail
|
|
139
|
+
const advisoryFinding = result.findings.find((f) => f.id.includes('mailhog-unavailable'));
|
|
140
|
+
expect(advisoryFinding).toBeDefined();
|
|
141
|
+
expect(advisoryFinding!.severity).toBe('low');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// execute() — Mailhog unavailable: returns advisory and stops
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('execute() when Mailhog is unavailable', () => {
|
|
150
|
+
it('returns advisory finding and does not call password reset endpoint', async () => {
|
|
151
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
152
|
+
|
|
153
|
+
const agent = createAgent();
|
|
154
|
+
await agent.run();
|
|
155
|
+
|
|
156
|
+
expect(apiClientPostMock).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// -------------------------------------------------------------------------
|
|
161
|
+
// execute() — password reset triggering
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
describe('execute() — password reset', () => {
|
|
165
|
+
it('creates a medium finding when password reset returns non-2xx', async () => {
|
|
166
|
+
// Mailhog reachable
|
|
167
|
+
fetchMock
|
|
168
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // HEAD to Mailhog health
|
|
169
|
+
.mockResolvedValue(mailhogApiResponse([])); // subsequent calls
|
|
170
|
+
|
|
171
|
+
apiClientPostMock.mockResolvedValue({ status: 500, body: { error: 'Internal error' } });
|
|
172
|
+
|
|
173
|
+
const agent = createAgent();
|
|
174
|
+
const result = await agent.run();
|
|
175
|
+
|
|
176
|
+
const resetFinding = result.findings.find((f) => f.id.includes('password-reset'));
|
|
177
|
+
expect(resetFinding).toBeDefined();
|
|
178
|
+
expect(resetFinding!.severity).toBe('medium');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// execute() — email delivery
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
describe('execute() — email delivery', () => {
|
|
187
|
+
it('creates a high finding when no email arrives in Mailhog within timeout', async () => {
|
|
188
|
+
// Mailhog reachable but no messages
|
|
189
|
+
fetchMock
|
|
190
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // HEAD to Mailhog health
|
|
191
|
+
.mockResolvedValue(mailhogApiResponse([])); // always empty
|
|
192
|
+
|
|
193
|
+
const agent = createAgent();
|
|
194
|
+
const result = await agent.run();
|
|
195
|
+
|
|
196
|
+
const deliveryFinding = result.findings.find((f) => f.id.includes('email-not-delivered'));
|
|
197
|
+
expect(deliveryFinding).toBeDefined();
|
|
198
|
+
expect(deliveryFinding!.severity).toBe('high');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('creates no delivery finding when email arrives in Mailhog', async () => {
|
|
202
|
+
const msg = makeMailhogMessage(
|
|
203
|
+
'Click here to reset: http://localhost:3000/reset?token=abc123',
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
fetchMock
|
|
207
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // HEAD to Mailhog health
|
|
208
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // first poll — has messages
|
|
209
|
+
.mockResolvedValue({ ok: true, status: 200 }); // link HEAD check
|
|
210
|
+
|
|
211
|
+
const agent = createAgent();
|
|
212
|
+
const result = await agent.run();
|
|
213
|
+
|
|
214
|
+
const deliveryFinding = result.findings.find((f) => f.id.includes('email-not-delivered'));
|
|
215
|
+
expect(deliveryFinding).toBeUndefined();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// -------------------------------------------------------------------------
|
|
220
|
+
// execute() — template variable detection
|
|
221
|
+
// -------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
describe('execute() — template variable detection', () => {
|
|
224
|
+
it('creates a high finding when email body contains raw template variables', async () => {
|
|
225
|
+
const msg = makeMailhogMessage(
|
|
226
|
+
'Hello {{firstName}}, reset your password: http://localhost:3000/reset?token={{resetToken}}',
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
fetchMock
|
|
230
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
231
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll returns message
|
|
232
|
+
.mockResolvedValue({ ok: true, status: 200 }); // link resolution
|
|
233
|
+
|
|
234
|
+
const agent = createAgent();
|
|
235
|
+
const result = await agent.run();
|
|
236
|
+
|
|
237
|
+
const templateFinding = result.findings.find((f) => f.id.includes('template-vars'));
|
|
238
|
+
expect(templateFinding).toBeDefined();
|
|
239
|
+
expect(templateFinding!.severity).toBe('high');
|
|
240
|
+
expect(templateFinding!.description).toContain('{{firstName}}');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('creates no finding for an email body with no template variables', async () => {
|
|
244
|
+
const msg = makeMailhogMessage(
|
|
245
|
+
'Hello Admin, click http://localhost:3000/reset?token=abc to reset your password.',
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
fetchMock
|
|
249
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
250
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll
|
|
251
|
+
.mockResolvedValue({ ok: true, status: 200 }); // link resolution
|
|
252
|
+
|
|
253
|
+
const agent = createAgent();
|
|
254
|
+
const result = await agent.run();
|
|
255
|
+
|
|
256
|
+
const templateFindings = result.findings.filter((f) => f.id.includes('template-vars'));
|
|
257
|
+
expect(templateFindings).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// -------------------------------------------------------------------------
|
|
262
|
+
// execute() — broken link detection
|
|
263
|
+
// -------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
describe('execute() — broken link detection', () => {
|
|
266
|
+
it('creates a medium finding when a link in the email body returns 404', async () => {
|
|
267
|
+
const msg = makeMailhogMessage(
|
|
268
|
+
'Reset here: http://localhost:3000/reset?token=abc123',
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
fetchMock
|
|
272
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
273
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll
|
|
274
|
+
.mockResolvedValue({ ok: false, status: 404 }); // HEAD link check → 404
|
|
275
|
+
|
|
276
|
+
const agent = createAgent();
|
|
277
|
+
const result = await agent.run();
|
|
278
|
+
|
|
279
|
+
const brokenLinkFinding = result.findings.find((f) => f.id.includes('broken-link'));
|
|
280
|
+
expect(brokenLinkFinding).toBeDefined();
|
|
281
|
+
expect(brokenLinkFinding!.severity).toBe('medium');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('creates no broken-link finding when all links in the email body resolve to 200', async () => {
|
|
285
|
+
const msg = makeMailhogMessage(
|
|
286
|
+
'Reset here: http://localhost:3000/reset?token=abc123',
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
fetchMock
|
|
290
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
291
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll
|
|
292
|
+
.mockResolvedValue({ ok: true, status: 200 }); // HEAD link check → 200
|
|
293
|
+
|
|
294
|
+
const agent = createAgent();
|
|
295
|
+
const result = await agent.run();
|
|
296
|
+
|
|
297
|
+
const brokenLinkFindings = result.findings.filter((f) => f.id.includes('broken-link'));
|
|
298
|
+
expect(brokenLinkFindings).toHaveLength(0);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// -------------------------------------------------------------------------
|
|
303
|
+
// Missing environment
|
|
304
|
+
// -------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
describe('execute() — no environment', () => {
|
|
307
|
+
it('creates a critical finding when no environment is configured', async () => {
|
|
308
|
+
const noEnvConfig: ValidatedConfig = {
|
|
309
|
+
...stubConfig,
|
|
310
|
+
environments: {},
|
|
311
|
+
};
|
|
312
|
+
// Mailhog unavailable
|
|
313
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
314
|
+
|
|
315
|
+
const agent = new EmailNotificationVerifierAgent(noEnvConfig, 'beta', '/tmp/test-run');
|
|
316
|
+
const result = await agent.run();
|
|
317
|
+
|
|
318
|
+
const finding = result.findings.find(f => f.id.includes('no-env'));
|
|
319
|
+
expect(finding).toBeDefined();
|
|
320
|
+
expect(finding!.severity).toBe('critical');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// -------------------------------------------------------------------------
|
|
325
|
+
// Password reset trigger error (fetch throws)
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
describe('execute() — password reset error', () => {
|
|
329
|
+
it('creates a medium finding when password reset API throws', async () => {
|
|
330
|
+
fetchMock
|
|
331
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
332
|
+
.mockResolvedValue(mailhogApiResponse([]));
|
|
333
|
+
|
|
334
|
+
apiClientPostMock.mockRejectedValue(new Error('Network error'));
|
|
335
|
+
|
|
336
|
+
const agent = createAgent();
|
|
337
|
+
const result = await agent.run();
|
|
338
|
+
|
|
339
|
+
const errorFinding = result.findings.find(f => f.id.includes('password-reset-error'));
|
|
340
|
+
expect(errorFinding).toBeDefined();
|
|
341
|
+
expect(errorFinding!.severity).toBe('medium');
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// -------------------------------------------------------------------------
|
|
346
|
+
// Link resolution error (fetch throws for link)
|
|
347
|
+
// -------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
describe('execute() — link resolution error', () => {
|
|
350
|
+
it('creates a low finding when link verification fetch throws', async () => {
|
|
351
|
+
const msg = makeMailhogMessage(
|
|
352
|
+
'Visit http://localhost:3000/reset?token=xyz to reset.',
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
fetchMock
|
|
356
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
357
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll
|
|
358
|
+
.mockRejectedValue(new Error('DNS resolution failed')); // link check
|
|
359
|
+
|
|
360
|
+
const agent = createAgent();
|
|
361
|
+
const result = await agent.run();
|
|
362
|
+
|
|
363
|
+
const linkErrorFinding = result.findings.find(f => f.id.includes('link-error'));
|
|
364
|
+
expect(linkErrorFinding).toBeDefined();
|
|
365
|
+
expect(linkErrorFinding!.severity).toBe('low');
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// -------------------------------------------------------------------------
|
|
370
|
+
// Email body with no URLs
|
|
371
|
+
// -------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
describe('execute() — email with no URLs', () => {
|
|
374
|
+
it('creates no broken-link findings when email body has no URLs', async () => {
|
|
375
|
+
const msg = makeMailhogMessage('Your password has been reset. No links here.');
|
|
376
|
+
|
|
377
|
+
fetchMock
|
|
378
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
379
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])); // poll
|
|
380
|
+
|
|
381
|
+
const agent = createAgent();
|
|
382
|
+
const result = await agent.run();
|
|
383
|
+
|
|
384
|
+
const linkFindings = result.findings.filter(f =>
|
|
385
|
+
f.id.includes('broken-link') || f.id.includes('link-error'),
|
|
386
|
+
);
|
|
387
|
+
expect(linkFindings).toHaveLength(0);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// -------------------------------------------------------------------------
|
|
392
|
+
// Password reset returns 202/204
|
|
393
|
+
// -------------------------------------------------------------------------
|
|
394
|
+
|
|
395
|
+
describe('execute() — accepted status codes', () => {
|
|
396
|
+
it('proceeds when password reset returns 202', async () => {
|
|
397
|
+
const msg = makeMailhogMessage('Reset here: http://localhost:3000/reset');
|
|
398
|
+
|
|
399
|
+
fetchMock
|
|
400
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
401
|
+
.mockResolvedValueOnce(mailhogApiResponse([msg])) // poll
|
|
402
|
+
.mockResolvedValue({ ok: true, status: 200 }); // link check
|
|
403
|
+
|
|
404
|
+
apiClientPostMock.mockResolvedValue({ status: 202, body: {} });
|
|
405
|
+
|
|
406
|
+
const agent = createAgent();
|
|
407
|
+
const result = await agent.run();
|
|
408
|
+
|
|
409
|
+
const resetFinding = result.findings.find(f => f.id.includes('password-reset-failed'));
|
|
410
|
+
expect(resetFinding).toBeUndefined();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// -------------------------------------------------------------------------
|
|
415
|
+
// Mailhog poll retry
|
|
416
|
+
// -------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
describe('execute() — Mailhog poll retry', () => {
|
|
419
|
+
it('retries polling when first attempts return empty and eventually succeeds', async () => {
|
|
420
|
+
const msg = makeMailhogMessage('Reset at http://localhost:3000/reset');
|
|
421
|
+
|
|
422
|
+
let pollCount = 0;
|
|
423
|
+
fetchMock
|
|
424
|
+
.mockResolvedValueOnce({ ok: true, status: 200 }) // Mailhog health
|
|
425
|
+
.mockImplementation(async (url: string) => {
|
|
426
|
+
if (url.includes('messages')) {
|
|
427
|
+
pollCount++;
|
|
428
|
+
if (pollCount < 3) return mailhogApiResponse([]);
|
|
429
|
+
return mailhogApiResponse([msg]);
|
|
430
|
+
}
|
|
431
|
+
return { ok: true, status: 200 };
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const agent = createAgent();
|
|
435
|
+
const result = await agent.run();
|
|
436
|
+
|
|
437
|
+
const deliveryFinding = result.findings.find(f => f.id.includes('email-not-delivered'));
|
|
438
|
+
expect(deliveryFinding).toBeUndefined();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
|
|
4
|
+
vi.mock('node:child_process', () => ({
|
|
5
|
+
execFile: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { MigrationTesterAgent } from '../23-migration-tester';
|
|
10
|
+
|
|
11
|
+
const mockedExecFile = vi.mocked(execFile);
|
|
12
|
+
|
|
13
|
+
type ExecFileCallback = (err: Error | null, stdout: string, stderr: string) => void;
|
|
14
|
+
|
|
15
|
+
function makeExecFileSuccess(stdout = '') {
|
|
16
|
+
return (...args: unknown[]) => {
|
|
17
|
+
// execFile may be called with or without an options object:
|
|
18
|
+
// execFile(file, args, cb) OR execFile(file, args, options, cb)
|
|
19
|
+
const cb = args[args.length - 1] as ExecFileCallback;
|
|
20
|
+
cb(null, stdout, '');
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeExecFileFailure(stderr: string) {
|
|
25
|
+
const error = Object.assign(new Error('Command failed'), { stderr });
|
|
26
|
+
return (...args: unknown[]) => {
|
|
27
|
+
const cb = args[args.length - 1] as ExecFileCallback;
|
|
28
|
+
cb(error, '', stderr);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const stubConfig: ValidatedConfig = {
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
name: 'test',
|
|
35
|
+
auth: { loginUrl: 'http://localhost' },
|
|
36
|
+
modules: [{ id: 'mod', route: '/mod', sidebarIcon: true }],
|
|
37
|
+
environments: {},
|
|
38
|
+
portals: [],
|
|
39
|
+
breakpoints: [],
|
|
40
|
+
integrations: [],
|
|
41
|
+
workers: [],
|
|
42
|
+
workflows: [],
|
|
43
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
44
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
45
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
46
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
47
|
+
thresholds: {
|
|
48
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
49
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
50
|
+
apiResponseTime: 2000,
|
|
51
|
+
bundleSizeLimit: 500000,
|
|
52
|
+
maxConsoleErrors: 0,
|
|
53
|
+
},
|
|
54
|
+
disabledAgents: [],
|
|
55
|
+
disabledAgentSet: new Set<number>(),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function createAgent(): MigrationTesterAgent {
|
|
59
|
+
return new MigrationTesterAgent(stubConfig, 'alpha', '/tmp/test-run');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('MigrationTesterAgent', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.resetAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('has agentId 23 and agentName Migration Tester', () => {
|
|
68
|
+
const agent = createAgent();
|
|
69
|
+
expect(agent.agentId).toBe(23);
|
|
70
|
+
expect(agent.agentName).toBe('Migration Tester');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('returns no findings when all migration steps succeed', async () => {
|
|
74
|
+
// preFlight: prisma --version, then 3 execute steps
|
|
75
|
+
mockedExecFile
|
|
76
|
+
.mockImplementationOnce(makeExecFileSuccess('prisma: 5.0.0') as never)
|
|
77
|
+
.mockImplementationOnce(makeExecFileSuccess('All migrations have been applied.') as never)
|
|
78
|
+
.mockImplementationOnce(makeExecFileSuccess('Migrations applied successfully.') as never)
|
|
79
|
+
.mockImplementationOnce(makeExecFileSuccess('Seeded database successfully.') as never);
|
|
80
|
+
|
|
81
|
+
const agent = createAgent();
|
|
82
|
+
const result = await agent.run();
|
|
83
|
+
|
|
84
|
+
expect(result.status).toBe('passed');
|
|
85
|
+
expect(result.findings).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns medium infra-issue finding when migrate status fails in alpha', async () => {
|
|
89
|
+
mockedExecFile
|
|
90
|
+
.mockImplementationOnce(makeExecFileSuccess('prisma: 5.0.0') as never)
|
|
91
|
+
.mockImplementationOnce(makeExecFileFailure('Error: P1001: Can\'t reach database server') as never);
|
|
92
|
+
|
|
93
|
+
const agent = createAgent();
|
|
94
|
+
const result = await agent.run();
|
|
95
|
+
|
|
96
|
+
expect(result.status).toBe('passed'); // medium severity doesn't trigger failure
|
|
97
|
+
expect(result.findings).toHaveLength(1);
|
|
98
|
+
expect(result.findings[0].type).toBe('infra-issue');
|
|
99
|
+
expect(result.findings[0].severity).toBe('medium'); // alpha phase → medium
|
|
100
|
+
expect(result.findings[0].description).toContain('migrate status failed');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('returns critical infra-issue finding when migrate deploy fails', async () => {
|
|
104
|
+
mockedExecFile
|
|
105
|
+
.mockImplementationOnce(makeExecFileSuccess('prisma: 5.0.0') as never)
|
|
106
|
+
.mockImplementationOnce(makeExecFileSuccess('') as never)
|
|
107
|
+
.mockImplementationOnce(makeExecFileFailure('Error: Migration failed to apply') as never);
|
|
108
|
+
|
|
109
|
+
const agent = createAgent();
|
|
110
|
+
const result = await agent.run();
|
|
111
|
+
|
|
112
|
+
expect(result.status).toBe('failed');
|
|
113
|
+
expect(result.findings).toHaveLength(1);
|
|
114
|
+
expect(result.findings[0].type).toBe('infra-issue');
|
|
115
|
+
expect(result.findings[0].severity).toBe('critical');
|
|
116
|
+
expect(result.findings[0].description).toContain('migrate deploy failed');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('returns critical infra-issue finding when db seed fails', async () => {
|
|
120
|
+
mockedExecFile
|
|
121
|
+
.mockImplementationOnce(makeExecFileSuccess('prisma: 5.0.0') as never)
|
|
122
|
+
.mockImplementationOnce(makeExecFileSuccess('') as never)
|
|
123
|
+
.mockImplementationOnce(makeExecFileSuccess('') as never)
|
|
124
|
+
.mockImplementationOnce(makeExecFileFailure('Error: Seed script exited with code 1') as never);
|
|
125
|
+
|
|
126
|
+
const agent = createAgent();
|
|
127
|
+
const result = await agent.run();
|
|
128
|
+
|
|
129
|
+
expect(result.status).toBe('failed');
|
|
130
|
+
expect(result.findings).toHaveLength(1);
|
|
131
|
+
expect(result.findings[0].type).toBe('infra-issue');
|
|
132
|
+
expect(result.findings[0].severity).toBe('critical');
|
|
133
|
+
expect(result.findings[0].description).toContain('db seed failed');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('returns infra-issue finding when preFlight (prisma --version) fails', async () => {
|
|
137
|
+
mockedExecFile.mockImplementationOnce(makeExecFileFailure('npx: command not found') as never);
|
|
138
|
+
|
|
139
|
+
const agent = createAgent();
|
|
140
|
+
const result = await agent.run();
|
|
141
|
+
|
|
142
|
+
expect(result.status).toBe('failed');
|
|
143
|
+
expect(result.findings[0].type).toBe('infra-issue');
|
|
144
|
+
});
|
|
145
|
+
});
|