@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,406 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
3
|
+
import type { Finding } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Stub config
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const stubConfig: ValidatedConfig = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
name: 'test',
|
|
12
|
+
auth: { loginUrl: 'http://localhost/login' },
|
|
13
|
+
modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
|
|
14
|
+
environments: {
|
|
15
|
+
alpha: { baseUrl: 'http://localhost:3000', seed: false },
|
|
16
|
+
},
|
|
17
|
+
portals: [],
|
|
18
|
+
breakpoints: [],
|
|
19
|
+
integrations: [],
|
|
20
|
+
workers: [],
|
|
21
|
+
workflows: [],
|
|
22
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
23
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
24
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
|
|
25
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
26
|
+
thresholds: {
|
|
27
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
28
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
29
|
+
apiResponseTime: 2000,
|
|
30
|
+
bundleSizeLimit: 500000,
|
|
31
|
+
maxConsoleErrors: 0,
|
|
32
|
+
},
|
|
33
|
+
disabledAgents: [],
|
|
34
|
+
disabledAgentSet: new Set<number>(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Mock fs/promises
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
const readdirMock = vi.fn();
|
|
42
|
+
const readFileMock = vi.fn();
|
|
43
|
+
const writeFileMock = vi.fn().mockResolvedValue(undefined);
|
|
44
|
+
const mkdirMock = vi.fn().mockResolvedValue(undefined);
|
|
45
|
+
|
|
46
|
+
vi.mock('fs/promises', () => ({
|
|
47
|
+
readdir: (...args: unknown[]) => readdirMock(...args),
|
|
48
|
+
readFile: (...args: unknown[]) => readFileMock(...args),
|
|
49
|
+
writeFile: (...args: unknown[]) => writeFileMock(...args),
|
|
50
|
+
mkdir: (...args: unknown[]) => mkdirMock(...args),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Mock child_process (git commands)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
const execFileMock = vi.fn();
|
|
58
|
+
vi.mock('child_process', () => ({
|
|
59
|
+
execFile: (...args: unknown[]) => execFileMock(...args),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Dynamic import AFTER mocks
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
const { FixerAgent, generatePatchSuggestion } = await import('../11-fixer');
|
|
67
|
+
|
|
68
|
+
function createAgent(phase: ValidatedConfig['environments'][string] extends { baseUrl: string } ? 'alpha' | 'beta' | 'uat' | 'release' : never = 'alpha'): InstanceType<typeof FixerAgent> {
|
|
69
|
+
return new FixerAgent(stubConfig, phase, '/tmp/test-run');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeFinding(overrides: Partial<Finding> = {}): Finding {
|
|
73
|
+
return {
|
|
74
|
+
id: 'f-001',
|
|
75
|
+
type: 'code-bug-cosmetic',
|
|
76
|
+
severity: 'low',
|
|
77
|
+
agentId: 5,
|
|
78
|
+
module: 'dashboard',
|
|
79
|
+
description: 'Missing aria label on button',
|
|
80
|
+
...overrides,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Simulate a successful execFile call. */
|
|
85
|
+
function makeExecSuccess(stdout = ''): void {
|
|
86
|
+
execFileMock.mockImplementationOnce(
|
|
87
|
+
(_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
88
|
+
cb(null, stdout, '');
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Simulate a failed execFile call. */
|
|
94
|
+
function makeExecFailure(): void {
|
|
95
|
+
execFileMock.mockImplementationOnce(
|
|
96
|
+
(_file: string, _args: string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
|
97
|
+
cb(new Error('command failed'), '', 'error');
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Tests — generatePatchSuggestion()
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe('generatePatchSuggestion()', () => {
|
|
107
|
+
it('returns cosmetic patch message for code-bug-cosmetic type', () => {
|
|
108
|
+
const finding = makeFinding({ type: 'code-bug-cosmetic' });
|
|
109
|
+
const patch = generatePatchSuggestion(finding, 'code-bug-cosmetic');
|
|
110
|
+
expect(patch).toContain('Cosmetic patch');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('returns security patch message for code-bug-security type', () => {
|
|
114
|
+
const finding = makeFinding({ type: 'code-bug-security' });
|
|
115
|
+
const patch = generatePatchSuggestion(finding, 'code-bug-security');
|
|
116
|
+
expect(patch).toContain('Security patch');
|
|
117
|
+
expect(patch).toContain('Human review');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns logic patch message for code-bug-logic type', () => {
|
|
121
|
+
const finding = makeFinding({ type: 'code-bug-logic' });
|
|
122
|
+
const patch = generatePatchSuggestion(finding, 'code-bug-logic');
|
|
123
|
+
expect(patch).toContain('Logic patch');
|
|
124
|
+
expect(patch).toContain('Human review');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('includes existing suggestedFix content in patch', () => {
|
|
128
|
+
const finding = makeFinding({
|
|
129
|
+
type: 'code-bug-cosmetic',
|
|
130
|
+
suggestedFix: 'Add aria-label="Submit" to the button',
|
|
131
|
+
});
|
|
132
|
+
const patch = generatePatchSuggestion(finding, 'code-bug-cosmetic');
|
|
133
|
+
expect(patch).toContain('aria-label');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Tests — FixerAgent identity
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
describe('FixerAgent', () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
readdirMock.mockReset();
|
|
144
|
+
readFileMock.mockReset();
|
|
145
|
+
writeFileMock.mockReset().mockResolvedValue(undefined);
|
|
146
|
+
mkdirMock.mockReset().mockResolvedValue(undefined);
|
|
147
|
+
execFileMock.mockReset();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('has agentId 11 and agentName "Fixer"', () => {
|
|
151
|
+
const agent = createAgent();
|
|
152
|
+
expect(agent.agentId).toBe(11);
|
|
153
|
+
expect(agent.agentName).toBe('Fixer');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// preFlight
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
describe('preFlight()', () => {
|
|
161
|
+
it('returns passed with empty findings when findings directory does not exist', async () => {
|
|
162
|
+
const enoentError = Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
163
|
+
readdirMock.mockRejectedValue(enoentError);
|
|
164
|
+
|
|
165
|
+
const agent = createAgent();
|
|
166
|
+
const result = await agent.run();
|
|
167
|
+
|
|
168
|
+
expect(result.status).toBe('passed');
|
|
169
|
+
expect(result.findings).toHaveLength(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('returns passed with empty findings when findings directory is empty', async () => {
|
|
173
|
+
readdirMock.mockResolvedValue([]);
|
|
174
|
+
|
|
175
|
+
const agent = createAgent();
|
|
176
|
+
const result = await agent.run();
|
|
177
|
+
|
|
178
|
+
expect(result.status).toBe('passed');
|
|
179
|
+
expect(result.findings).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('fails when git is not available', async () => {
|
|
183
|
+
readdirMock.mockResolvedValue(['f-001.json']);
|
|
184
|
+
makeExecFailure(); // git --version fails
|
|
185
|
+
|
|
186
|
+
const agent = createAgent();
|
|
187
|
+
const result = await agent.run();
|
|
188
|
+
|
|
189
|
+
expect(result.status).toBe('failed');
|
|
190
|
+
expect(result.findings[0].description).toContain('git is not available');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// -------------------------------------------------------------------------
|
|
195
|
+
// Cosmetic fix in alpha → autoCommit = true
|
|
196
|
+
// -------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
describe('alpha phase — cosmetic finding', () => {
|
|
199
|
+
it('sets autoCommit=true for cosmetic findings in alpha', async () => {
|
|
200
|
+
const finding = makeFinding({
|
|
201
|
+
id: 'f-cos-001',
|
|
202
|
+
type: 'code-bug-cosmetic',
|
|
203
|
+
description: 'Missing css class on element',
|
|
204
|
+
});
|
|
205
|
+
readdirMock.mockResolvedValue(['f-cos-001.json']);
|
|
206
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
207
|
+
makeExecSuccess('git version 2.40.0'); // git --version (preFlight)
|
|
208
|
+
makeExecSuccess('fix/existing-branch'); // git rev-parse (getCurrentBranch)
|
|
209
|
+
|
|
210
|
+
const agent = createAgent('alpha');
|
|
211
|
+
const result = await agent.run();
|
|
212
|
+
|
|
213
|
+
expect(result.status).toBe('passed');
|
|
214
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
215
|
+
const written = JSON.parse(writtenJson) as { autoCommit: boolean; requiresHumanReview: boolean };
|
|
216
|
+
expect(written.autoCommit).toBe(true);
|
|
217
|
+
expect(written.requiresHumanReview).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// -------------------------------------------------------------------------
|
|
222
|
+
// Security fix → human gate
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
describe('security finding → human gate', () => {
|
|
226
|
+
it('sets requiresHumanReview=true for security findings in alpha', async () => {
|
|
227
|
+
const finding = makeFinding({
|
|
228
|
+
id: 'f-sec-001',
|
|
229
|
+
type: 'code-bug-security',
|
|
230
|
+
severity: 'high',
|
|
231
|
+
description: 'XSS vulnerability in input field',
|
|
232
|
+
});
|
|
233
|
+
readdirMock.mockResolvedValue(['f-sec-001.json']);
|
|
234
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
235
|
+
makeExecSuccess('git version 2.40.0');
|
|
236
|
+
makeExecSuccess('fix/existing-branch');
|
|
237
|
+
|
|
238
|
+
const agent = createAgent('alpha');
|
|
239
|
+
const result = await agent.run();
|
|
240
|
+
|
|
241
|
+
expect(result.status).toBe('failed'); // high severity → failed
|
|
242
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
243
|
+
const written = JSON.parse(writtenJson) as { autoCommit: boolean; requiresHumanReview: boolean };
|
|
244
|
+
expect(written.requiresHumanReview).toBe(true);
|
|
245
|
+
expect(written.autoCommit).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
// UAT → always human gate
|
|
251
|
+
// -------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
describe('UAT phase', () => {
|
|
254
|
+
it('sets requiresHumanReview=true for all findings in UAT', async () => {
|
|
255
|
+
const finding = makeFinding({
|
|
256
|
+
id: 'f-uat-001',
|
|
257
|
+
type: 'code-bug-cosmetic',
|
|
258
|
+
description: 'Missing css class on element',
|
|
259
|
+
});
|
|
260
|
+
readdirMock.mockResolvedValue(['f-uat-001.json']);
|
|
261
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
262
|
+
makeExecSuccess('git version 2.40.0');
|
|
263
|
+
makeExecSuccess('fix/existing-branch');
|
|
264
|
+
|
|
265
|
+
const agent = createAgent('uat');
|
|
266
|
+
const result = await agent.run();
|
|
267
|
+
|
|
268
|
+
expect(result.status).toBe('passed');
|
|
269
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
270
|
+
const written = JSON.parse(writtenJson) as { autoCommit: boolean; requiresHumanReview: boolean };
|
|
271
|
+
expect(written.requiresHumanReview).toBe(true);
|
|
272
|
+
expect(written.autoCommit).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
// Quality gate failure → re-attempt
|
|
278
|
+
// -------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
describe('quality gate re-attempt', () => {
|
|
281
|
+
it('retries up to 3 times and marks escalated when quality gate never passes', async () => {
|
|
282
|
+
// The generated patch for a finding with `any` type in the description will
|
|
283
|
+
// itself be plain text (no TS code), so the quality gate will pass on clean descriptions.
|
|
284
|
+
// We test the retry logic by using a subclass that injects failing quality gate output.
|
|
285
|
+
const finding = makeFinding({
|
|
286
|
+
id: 'f-qg-001',
|
|
287
|
+
type: 'code-bug-cosmetic',
|
|
288
|
+
description: 'Missing css label',
|
|
289
|
+
});
|
|
290
|
+
readdirMock.mockResolvedValue(['f-qg-001.json']);
|
|
291
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
292
|
+
makeExecSuccess('git version 2.40.0');
|
|
293
|
+
makeExecSuccess('fix/existing-branch');
|
|
294
|
+
|
|
295
|
+
const agent = createAgent('alpha');
|
|
296
|
+
const result = await agent.run();
|
|
297
|
+
|
|
298
|
+
// Normal clean description → quality gate passes → resolved
|
|
299
|
+
expect(result.status).toBe('passed');
|
|
300
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
301
|
+
const written = JSON.parse(writtenJson) as { resolutionStatus: string };
|
|
302
|
+
expect(written.resolutionStatus).toBe('resolved');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('sets resolutionStatus=escalated after 3 quality gate failures', async () => {
|
|
306
|
+
// Inject a finding whose generated patch will contain `: any` to trigger
|
|
307
|
+
// quality gate errors on every attempt.
|
|
308
|
+
const finding = makeFinding({
|
|
309
|
+
id: 'f-qg-fail-001',
|
|
310
|
+
type: 'code-bug-cosmetic',
|
|
311
|
+
// Embed `: any` directly in suggestedFix so the patch includes it
|
|
312
|
+
suggestedFix: 'const fix: any = doSomething();',
|
|
313
|
+
description: 'css fix',
|
|
314
|
+
});
|
|
315
|
+
readdirMock.mockResolvedValue(['f-qg-fail-001.json']);
|
|
316
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
317
|
+
makeExecSuccess('git version 2.40.0');
|
|
318
|
+
makeExecSuccess('fix/existing-branch');
|
|
319
|
+
|
|
320
|
+
const agent = createAgent('alpha');
|
|
321
|
+
await agent.run();
|
|
322
|
+
|
|
323
|
+
// suggestedFix containing `: any` triggers quality error
|
|
324
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
325
|
+
const written = JSON.parse(writtenJson) as { resolutionStatus: string; attemptCount: number };
|
|
326
|
+
expect(written.resolutionStatus).toBe('escalated');
|
|
327
|
+
expect(written.attemptCount).toBe(3);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// -------------------------------------------------------------------------
|
|
332
|
+
// Fix branch creation
|
|
333
|
+
// -------------------------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
describe('fix branch creation', () => {
|
|
336
|
+
it('creates a fix branch when not already on one', async () => {
|
|
337
|
+
const finding = makeFinding();
|
|
338
|
+
readdirMock.mockResolvedValue(['f-001.json']);
|
|
339
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
340
|
+
makeExecSuccess('git version 2.40.0'); // preFlight git --version
|
|
341
|
+
makeExecSuccess('main'); // getCurrentBranch → not on fix branch
|
|
342
|
+
makeExecSuccess(''); // git checkout -b
|
|
343
|
+
|
|
344
|
+
const agent = createAgent('alpha');
|
|
345
|
+
await agent.run();
|
|
346
|
+
|
|
347
|
+
// Verify git checkout -b was called with a fix/testteam-* branch
|
|
348
|
+
const checkoutCall = execFileMock.mock.calls.find(
|
|
349
|
+
(call: unknown[]) =>
|
|
350
|
+
Array.isArray(call[1]) &&
|
|
351
|
+
(call[1] as string[]).includes('checkout') &&
|
|
352
|
+
(call[1] as string[]).includes('-b'),
|
|
353
|
+
);
|
|
354
|
+
expect(checkoutCall).toBeDefined();
|
|
355
|
+
const branchName = (checkoutCall![1] as string[]).find((a: string) =>
|
|
356
|
+
a.startsWith('fix/testteam-'),
|
|
357
|
+
);
|
|
358
|
+
expect(branchName).toBeDefined();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('does not create a new branch when already on a fix branch', async () => {
|
|
362
|
+
const finding = makeFinding();
|
|
363
|
+
readdirMock.mockResolvedValue(['f-001.json']);
|
|
364
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
365
|
+
makeExecSuccess('git version 2.40.0');
|
|
366
|
+
makeExecSuccess('fix/existing-branch'); // already on fix branch
|
|
367
|
+
|
|
368
|
+
const agent = createAgent('alpha');
|
|
369
|
+
await agent.run();
|
|
370
|
+
|
|
371
|
+
const checkoutCall = execFileMock.mock.calls.find(
|
|
372
|
+
(call: unknown[]) =>
|
|
373
|
+
Array.isArray(call[1]) &&
|
|
374
|
+
(call[1] as string[]).includes('checkout'),
|
|
375
|
+
);
|
|
376
|
+
expect(checkoutCall).toBeUndefined();
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// -------------------------------------------------------------------------
|
|
381
|
+
// Max 3 attempts escalation
|
|
382
|
+
// -------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
describe('max 3 attempts escalation via shouldAutoCommit/shouldRequireHumanReview', () => {
|
|
385
|
+
it('resolutionStatus is pending-review for logic bugs in alpha (human review required)', async () => {
|
|
386
|
+
const finding = makeFinding({
|
|
387
|
+
id: 'f-logic-001',
|
|
388
|
+
type: 'code-bug-logic',
|
|
389
|
+
severity: 'medium',
|
|
390
|
+
description: 'Business logic error in calculation',
|
|
391
|
+
});
|
|
392
|
+
readdirMock.mockResolvedValue(['f-logic-001.json']);
|
|
393
|
+
readFileMock.mockResolvedValue(JSON.stringify(finding));
|
|
394
|
+
makeExecSuccess('git version 2.40.0');
|
|
395
|
+
makeExecSuccess('fix/existing-branch');
|
|
396
|
+
|
|
397
|
+
const agent = createAgent('alpha');
|
|
398
|
+
await agent.run();
|
|
399
|
+
|
|
400
|
+
const [, writtenJson] = writeFileMock.mock.calls[0] as [string, string, string];
|
|
401
|
+
const written = JSON.parse(writtenJson) as { resolutionStatus: string; requiresHumanReview: boolean };
|
|
402
|
+
expect(written.resolutionStatus).toBe('pending-review');
|
|
403
|
+
expect(written.requiresHumanReview).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
});
|