@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,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 03-test-generator-full.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Tests for Task 42: Agent 3 quality gate integration.
|
|
5
|
+
* Covers:
|
|
6
|
+
* - validateCodeQuality called on generated test file content
|
|
7
|
+
* - `any` type annotation triggers a quality-violation finding
|
|
8
|
+
* - clean file passes quality gate (no violation findings)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
+
import type { ValidatedConfig } from '../../core/config';
|
|
13
|
+
import type { Phase } from '../../core/types';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Stub config
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
const stubConfig = {
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
name: 'test',
|
|
22
|
+
auth: { loginUrl: 'http://localhost' },
|
|
23
|
+
modules: [{ id: 'mod', route: '/mod', sidebarIcon: true }],
|
|
24
|
+
environments: {},
|
|
25
|
+
portals: [],
|
|
26
|
+
breakpoints: [],
|
|
27
|
+
integrations: [],
|
|
28
|
+
workers: [],
|
|
29
|
+
workflows: [],
|
|
30
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
31
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
32
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
|
|
33
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
34
|
+
thresholds: {
|
|
35
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
36
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
37
|
+
apiResponseTime: 2000,
|
|
38
|
+
bundleSizeLimit: 500000,
|
|
39
|
+
maxConsoleErrors: 0,
|
|
40
|
+
},
|
|
41
|
+
disabledAgents: [],
|
|
42
|
+
disabledAgentSet: new Set<number>(),
|
|
43
|
+
} satisfies ValidatedConfig;
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Mock node:fs/promises
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
const statMock = vi.fn();
|
|
50
|
+
const readdirMock = vi.fn();
|
|
51
|
+
const readFileMock = vi.fn();
|
|
52
|
+
|
|
53
|
+
vi.mock('node:fs/promises', async (importOriginal) => {
|
|
54
|
+
const actual = await importOriginal<typeof import('node:fs/promises')>();
|
|
55
|
+
return {
|
|
56
|
+
...actual,
|
|
57
|
+
stat: (...args: unknown[]) => statMock(...args),
|
|
58
|
+
readdir: (...args: unknown[]) => readdirMock(...args),
|
|
59
|
+
readFile: (...args: unknown[]) => readFileMock(...args),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Dynamic import AFTER mocks
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
const { TestGeneratorAgent } = await import('../03-test-generator');
|
|
68
|
+
|
|
69
|
+
function createAgent(phase: Phase = 'alpha'): InstanceType<typeof TestGeneratorAgent> {
|
|
70
|
+
return new TestGeneratorAgent(stubConfig, phase, '/tmp/test-run');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Helpers
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/** Minimal valid generated test file with no quality violations. */
|
|
78
|
+
const CLEAN_GENERATED_TEST = `
|
|
79
|
+
import { describe, it, expect } from 'vitest';
|
|
80
|
+
|
|
81
|
+
describe('SomeService', () => {
|
|
82
|
+
it('returns true', () => {
|
|
83
|
+
const result: boolean = true;
|
|
84
|
+
expect(result).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
`.trim();
|
|
88
|
+
|
|
89
|
+
/** Generated test file that uses \`any\` type — should trigger a quality violation. */
|
|
90
|
+
const DIRTY_GENERATED_TEST_ANY = `
|
|
91
|
+
import { describe, it, expect } from 'vitest';
|
|
92
|
+
|
|
93
|
+
describe('SomeService', () => {
|
|
94
|
+
it('works with any typed value', () => {
|
|
95
|
+
const value: any = 'hello';
|
|
96
|
+
expect(value).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
`.trim();
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Tests
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
describe('TestGeneratorAgent — quality gate (Task 42)', () => {
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
statMock.mockReset();
|
|
108
|
+
readdirMock.mockReset();
|
|
109
|
+
readFileMock.mockReset();
|
|
110
|
+
|
|
111
|
+
// Default: no vitest config on disk, coverage dir throws, generated dir empty
|
|
112
|
+
statMock.mockRejectedValue(new Error('ENOENT'));
|
|
113
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
114
|
+
if (dir === 'tests/generated') return Promise.resolve([]);
|
|
115
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
// Quality gate is invoked on generated test content
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
describe('quality gate invocation on generated tests', () => {
|
|
124
|
+
it('calls quality gate on each file in tests/generated/', async () => {
|
|
125
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
126
|
+
if (dir === 'tests/generated') return Promise.resolve(['some-service.test.ts']);
|
|
127
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
128
|
+
});
|
|
129
|
+
readFileMock.mockResolvedValue(CLEAN_GENERATED_TEST);
|
|
130
|
+
|
|
131
|
+
const agent = createAgent();
|
|
132
|
+
const result = await agent.run();
|
|
133
|
+
|
|
134
|
+
// Quality gate passed → evidence log added, no quality-violation findings
|
|
135
|
+
const qualityFindings = result.findings.filter((f) => f.id.includes('quality-violation'));
|
|
136
|
+
expect(qualityFindings).toHaveLength(0);
|
|
137
|
+
|
|
138
|
+
const passedEvidence = result.evidence.filter(
|
|
139
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
140
|
+
);
|
|
141
|
+
expect(passedEvidence).toHaveLength(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('processes multiple generated test files', async () => {
|
|
145
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
146
|
+
if (dir === 'tests/generated') return Promise.resolve(['service-a.test.ts', 'service-b.test.ts']);
|
|
147
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
148
|
+
});
|
|
149
|
+
readFileMock.mockResolvedValue(CLEAN_GENERATED_TEST);
|
|
150
|
+
|
|
151
|
+
const agent = createAgent();
|
|
152
|
+
const result = await agent.run();
|
|
153
|
+
|
|
154
|
+
const passedEvidence = result.evidence.filter(
|
|
155
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
156
|
+
);
|
|
157
|
+
expect(passedEvidence).toHaveLength(2);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
// `any` type triggers a quality violation finding
|
|
163
|
+
// -------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
describe('quality violation: any type', () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
168
|
+
if (dir === 'tests/generated') return Promise.resolve(['dirty-test.ts']);
|
|
169
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
170
|
+
});
|
|
171
|
+
readFileMock.mockResolvedValue(DIRTY_GENERATED_TEST_ANY);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('creates a quality-violation finding when generated test contains `any` type', async () => {
|
|
175
|
+
const agent = createAgent();
|
|
176
|
+
const result = await agent.run();
|
|
177
|
+
|
|
178
|
+
const qualityFindings = result.findings.filter((f) => f.id.includes('quality-violation'));
|
|
179
|
+
expect(qualityFindings.length).toBeGreaterThan(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('quality violation finding has high severity (error-level rule)', async () => {
|
|
183
|
+
const agent = createAgent();
|
|
184
|
+
const result = await agent.run();
|
|
185
|
+
|
|
186
|
+
const anyViolation = result.findings.find(
|
|
187
|
+
(f) => f.id.includes('quality-violation') && f.description.includes('no-any'),
|
|
188
|
+
);
|
|
189
|
+
expect(anyViolation).toBeDefined();
|
|
190
|
+
expect(anyViolation!.severity).toBe('high');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('quality violation finding mentions the rule name in the description', async () => {
|
|
194
|
+
const agent = createAgent();
|
|
195
|
+
const result = await agent.run();
|
|
196
|
+
|
|
197
|
+
const anyViolation = result.findings.find(
|
|
198
|
+
(f) => f.description.includes('no-any'),
|
|
199
|
+
);
|
|
200
|
+
expect(anyViolation).toBeDefined();
|
|
201
|
+
expect(anyViolation!.description).toContain('no-any');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('quality violation finding has type code-bug-logic', async () => {
|
|
205
|
+
const agent = createAgent();
|
|
206
|
+
const result = await agent.run();
|
|
207
|
+
|
|
208
|
+
const qualityFinding = result.findings.find((f) => f.id.includes('quality-violation'));
|
|
209
|
+
expect(qualityFinding!.type).toBe('code-bug-logic');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('quality violation finding references the generated test file', async () => {
|
|
213
|
+
const agent = createAgent();
|
|
214
|
+
const result = await agent.run();
|
|
215
|
+
|
|
216
|
+
const qualityFinding = result.findings.find((f) => f.id.includes('quality-violation'));
|
|
217
|
+
expect(qualityFinding!.file).toContain('dirty-test.ts');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('does NOT add quality-gate-passed evidence when violations are found', async () => {
|
|
221
|
+
const agent = createAgent();
|
|
222
|
+
const result = await agent.run();
|
|
223
|
+
|
|
224
|
+
const passedEvidence = result.evidence.filter(
|
|
225
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
226
|
+
);
|
|
227
|
+
expect(passedEvidence).toHaveLength(0);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// -------------------------------------------------------------------------
|
|
232
|
+
// Clean file passes quality gate
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
describe('clean generated file passes quality gate', () => {
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
238
|
+
if (dir === 'tests/generated') return Promise.resolve(['clean-test.ts']);
|
|
239
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
240
|
+
});
|
|
241
|
+
readFileMock.mockResolvedValue(CLEAN_GENERATED_TEST);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('produces no quality-violation findings for a clean generated test file', async () => {
|
|
245
|
+
const agent = createAgent();
|
|
246
|
+
const result = await agent.run();
|
|
247
|
+
|
|
248
|
+
const qualityFindings = result.findings.filter((f) => f.id.includes('quality-violation'));
|
|
249
|
+
expect(qualityFindings).toHaveLength(0);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('adds quality-gate-passed evidence for a clean generated test file', async () => {
|
|
253
|
+
const agent = createAgent();
|
|
254
|
+
const result = await agent.run();
|
|
255
|
+
|
|
256
|
+
const passedEvidence = result.evidence.find(
|
|
257
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
258
|
+
);
|
|
259
|
+
expect(passedEvidence).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('passed evidence references the file path', async () => {
|
|
263
|
+
const agent = createAgent();
|
|
264
|
+
const result = await agent.run();
|
|
265
|
+
|
|
266
|
+
const passedEvidence = result.evidence.find(
|
|
267
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
268
|
+
);
|
|
269
|
+
expect(passedEvidence!.description).toContain('clean-test.ts');
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// -------------------------------------------------------------------------
|
|
274
|
+
// No generated test files — quality gate section produces no findings
|
|
275
|
+
// -------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
describe('when no generated tests exist', () => {
|
|
278
|
+
it('produces no quality-violation findings when tests/generated/ is empty', async () => {
|
|
279
|
+
// readdirMock already returns [] for tests/generated in beforeEach
|
|
280
|
+
const agent = createAgent();
|
|
281
|
+
const result = await agent.run();
|
|
282
|
+
|
|
283
|
+
const qualityFindings = result.findings.filter((f) => f.id.includes('quality-violation'));
|
|
284
|
+
expect(qualityFindings).toHaveLength(0);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('produces no quality-gate-passed evidence when tests/generated/ is empty', async () => {
|
|
288
|
+
const agent = createAgent();
|
|
289
|
+
const result = await agent.run();
|
|
290
|
+
|
|
291
|
+
const passedEvidence = result.evidence.filter(
|
|
292
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
293
|
+
);
|
|
294
|
+
expect(passedEvidence).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// -------------------------------------------------------------------------
|
|
299
|
+
// Only .ts/.tsx/.js files are scanned — non-test files ignored
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
describe('file filtering in tests/generated/', () => {
|
|
303
|
+
it('ignores non-TypeScript/JavaScript files in tests/generated/', async () => {
|
|
304
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
305
|
+
if (dir === 'tests/generated') return Promise.resolve(['readme.md', 'notes.txt', 'clean.ts']);
|
|
306
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
307
|
+
});
|
|
308
|
+
// readFile only called for the .ts file
|
|
309
|
+
readFileMock.mockResolvedValue(CLEAN_GENERATED_TEST);
|
|
310
|
+
|
|
311
|
+
const agent = createAgent();
|
|
312
|
+
const result = await agent.run();
|
|
313
|
+
|
|
314
|
+
// Only clean.ts was scanned — 1 passed-evidence entry
|
|
315
|
+
const passedEvidence = result.evidence.filter(
|
|
316
|
+
(e) => e.type === 'log' && e.description.includes('Quality gate passed'),
|
|
317
|
+
);
|
|
318
|
+
expect(passedEvidence).toHaveLength(1);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
@@ -0,0 +1,318 @@
|
|
|
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 = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
name: 'test',
|
|
12
|
+
auth: { loginUrl: 'http://localhost' },
|
|
13
|
+
modules: [{ id: 'mod', route: '/mod', sidebarIcon: true }],
|
|
14
|
+
environments: {},
|
|
15
|
+
portals: [],
|
|
16
|
+
breakpoints: [],
|
|
17
|
+
integrations: [],
|
|
18
|
+
workers: [],
|
|
19
|
+
workflows: [],
|
|
20
|
+
accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
21
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
22
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
|
|
23
|
+
agentClassification: { blocking: [], advisory: [] },
|
|
24
|
+
thresholds: {
|
|
25
|
+
unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
|
|
26
|
+
lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
|
|
27
|
+
apiResponseTime: 2000,
|
|
28
|
+
bundleSizeLimit: 500000,
|
|
29
|
+
maxConsoleErrors: 0,
|
|
30
|
+
},
|
|
31
|
+
disabledAgents: [],
|
|
32
|
+
disabledAgentSet: new Set<number>(),
|
|
33
|
+
} satisfies ValidatedConfig;
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Mock node:fs/promises
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const statMock = vi.fn();
|
|
40
|
+
const readdirMock = vi.fn();
|
|
41
|
+
const readFileMock = vi.fn();
|
|
42
|
+
|
|
43
|
+
vi.mock('node:fs/promises', async (importOriginal) => {
|
|
44
|
+
const actual = await importOriginal<typeof import('node:fs/promises')>();
|
|
45
|
+
return {
|
|
46
|
+
...actual,
|
|
47
|
+
stat: (...args: unknown[]) => statMock(...args),
|
|
48
|
+
readdir: (...args: unknown[]) => readdirMock(...args),
|
|
49
|
+
readFile: (...args: unknown[]) => readFileMock(...args),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Coverage fixture helpers
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
interface FileCoverage {
|
|
58
|
+
lines: { pct: number };
|
|
59
|
+
functions: { pct: number };
|
|
60
|
+
branches: { pct: number };
|
|
61
|
+
statements: { pct: number };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type CoverageSummaryFixture = Record<string, FileCoverage>;
|
|
65
|
+
|
|
66
|
+
function makeCoverageSummary(
|
|
67
|
+
files: Array<{ path: string; lines: number; functions: number; branches: number; statements: number }>,
|
|
68
|
+
): CoverageSummaryFixture {
|
|
69
|
+
const summary: CoverageSummaryFixture = {
|
|
70
|
+
total: {
|
|
71
|
+
lines: { pct: 100 },
|
|
72
|
+
functions: { pct: 100 },
|
|
73
|
+
branches: { pct: 100 },
|
|
74
|
+
statements: { pct: 100 },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
for (const f of files) {
|
|
78
|
+
summary[f.path] = {
|
|
79
|
+
lines: { pct: f.lines },
|
|
80
|
+
functions: { pct: f.functions },
|
|
81
|
+
branches: { pct: f.branches },
|
|
82
|
+
statements: { pct: f.statements },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return summary;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Dynamic import AFTER mocks
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
const { TestGeneratorAgent } = await import('../03-test-generator');
|
|
93
|
+
|
|
94
|
+
function createAgent(phase: Phase = 'alpha'): InstanceType<typeof TestGeneratorAgent> {
|
|
95
|
+
return new TestGeneratorAgent(stubConfig, phase, '/tmp/test-run');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Tests
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
describe('TestGeneratorAgent', () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
statMock.mockReset();
|
|
105
|
+
readdirMock.mockReset();
|
|
106
|
+
readFileMock.mockReset();
|
|
107
|
+
|
|
108
|
+
// Default: no vitest config exists, no generated tests dir
|
|
109
|
+
statMock.mockRejectedValue(new Error('ENOENT'));
|
|
110
|
+
// Default: tests/generated/ is empty
|
|
111
|
+
readdirMock.mockImplementation((dir: string) => {
|
|
112
|
+
if (dir === 'tests/generated') return Promise.resolve([]);
|
|
113
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('has agentId 3 and agentName "Test Generator"', () => {
|
|
118
|
+
const agent = createAgent();
|
|
119
|
+
expect(agent.agentId).toBe(3);
|
|
120
|
+
expect(agent.agentName).toBe('Test Generator');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
// No coverage data
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
describe('when no coverage JSON files exist', () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
readdirMock.mockImplementation(() => {
|
|
130
|
+
return Promise.reject(new Error('ENOENT: no such file or directory'));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('creates a single advisory medium-severity finding', async () => {
|
|
135
|
+
const agent = createAgent();
|
|
136
|
+
const result = await agent.run();
|
|
137
|
+
|
|
138
|
+
expect(result.findings).toHaveLength(1);
|
|
139
|
+
expect(result.findings[0].severity).toBe('medium');
|
|
140
|
+
expect(result.findings[0].type).toBe('code-bug-logic');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('the finding description mentions coverage analysis not available', async () => {
|
|
144
|
+
const agent = createAgent();
|
|
145
|
+
const result = await agent.run();
|
|
146
|
+
|
|
147
|
+
expect(result.findings[0].description).toContain('Coverage analysis not available');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns passed status (medium finding does not block)', async () => {
|
|
151
|
+
const agent = createAgent();
|
|
152
|
+
const result = await agent.run();
|
|
153
|
+
|
|
154
|
+
expect(result.status).toBe('passed');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('when coverage directory exists but contains no JSON files', () => {
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
readdirMock.mockResolvedValue(['lcov.info', 'index.html']);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('creates an advisory finding noting coverage not available', async () => {
|
|
164
|
+
const agent = createAgent();
|
|
165
|
+
const result = await agent.run();
|
|
166
|
+
|
|
167
|
+
expect(result.findings).toHaveLength(1);
|
|
168
|
+
expect(result.findings[0].description).toContain('Coverage analysis not available');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// -------------------------------------------------------------------------
|
|
173
|
+
// Coverage data present — all above threshold
|
|
174
|
+
// -------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
describe('when coverage data shows all files above threshold', () => {
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
readdirMock.mockResolvedValue(['coverage-summary.json']);
|
|
179
|
+
const summary = makeCoverageSummary([
|
|
180
|
+
{ path: 'src/utils/format.ts', lines: 95, functions: 90, branches: 85, statements: 92 },
|
|
181
|
+
{ path: 'src/services/billing.ts', lines: 88, functions: 82, branches: 80, statements: 87 },
|
|
182
|
+
]);
|
|
183
|
+
readFileMock.mockResolvedValue(JSON.stringify(summary));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns no coverage-gap findings', async () => {
|
|
187
|
+
const agent = createAgent();
|
|
188
|
+
const result = await agent.run();
|
|
189
|
+
|
|
190
|
+
const coverageFindings = result.findings.filter((f) => f.id.startsWith('3-coverage-gap'));
|
|
191
|
+
expect(coverageFindings).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('returns passed status when all files meet thresholds', async () => {
|
|
195
|
+
const agent = createAgent();
|
|
196
|
+
const result = await agent.run();
|
|
197
|
+
|
|
198
|
+
expect(result.status).toBe('passed');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('adds report evidence for the coverage file', async () => {
|
|
202
|
+
const agent = createAgent();
|
|
203
|
+
const result = await agent.run();
|
|
204
|
+
|
|
205
|
+
expect(result.evidence.some((e) => e.type === 'report')).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// -------------------------------------------------------------------------
|
|
210
|
+
// Coverage data present — files below threshold
|
|
211
|
+
// -------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
describe('when coverage data shows files below threshold', () => {
|
|
214
|
+
beforeEach(() => {
|
|
215
|
+
readdirMock.mockResolvedValue(['coverage-summary.json']);
|
|
216
|
+
const summary = makeCoverageSummary([
|
|
217
|
+
// This file has all metrics below 80%
|
|
218
|
+
{ path: 'src/services/payroll.ts', lines: 60, functions: 55, branches: 50, statements: 58 },
|
|
219
|
+
// This file is above threshold
|
|
220
|
+
{ path: 'src/utils/helpers.ts', lines: 95, functions: 90, branches: 85, statements: 92 },
|
|
221
|
+
]);
|
|
222
|
+
readFileMock.mockResolvedValue(JSON.stringify(summary));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('creates a finding for the file below threshold', async () => {
|
|
226
|
+
const agent = createAgent();
|
|
227
|
+
const result = await agent.run();
|
|
228
|
+
|
|
229
|
+
const gapFindings = result.findings.filter((f) => f.id.startsWith('3-coverage-gap'));
|
|
230
|
+
expect(gapFindings).toHaveLength(1);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('the finding has medium severity', async () => {
|
|
234
|
+
const agent = createAgent();
|
|
235
|
+
const result = await agent.run();
|
|
236
|
+
|
|
237
|
+
const gapFinding = result.findings.find((f) => f.id.startsWith('3-coverage-gap'));
|
|
238
|
+
expect(gapFinding!.severity).toBe('medium');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('the finding has type code-bug-logic', async () => {
|
|
242
|
+
const agent = createAgent();
|
|
243
|
+
const result = await agent.run();
|
|
244
|
+
|
|
245
|
+
const gapFinding = result.findings.find((f) => f.id.startsWith('3-coverage-gap'));
|
|
246
|
+
expect(gapFinding!.type).toBe('code-bug-logic');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('the finding description mentions the specific thresholds not met', async () => {
|
|
250
|
+
const agent = createAgent();
|
|
251
|
+
const result = await agent.run();
|
|
252
|
+
|
|
253
|
+
const gapFinding = result.findings.find((f) => f.id.startsWith('3-coverage-gap'));
|
|
254
|
+
expect(gapFinding!.description).toContain('lines');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('the finding file property points to the uncovered file', async () => {
|
|
258
|
+
const agent = createAgent();
|
|
259
|
+
const result = await agent.run();
|
|
260
|
+
|
|
261
|
+
const gapFinding = result.findings.find((f) => f.id.startsWith('3-coverage-gap'));
|
|
262
|
+
expect(gapFinding!.file).toBe('src/services/payroll.ts');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('does not create a finding for the file above threshold', async () => {
|
|
266
|
+
const agent = createAgent();
|
|
267
|
+
const result = await agent.run();
|
|
268
|
+
|
|
269
|
+
const gapFindings = result.findings.filter((f) => f.id.startsWith('3-coverage-gap'));
|
|
270
|
+
const helpersFinding = gapFindings.find((f) => f.file === 'src/utils/helpers.ts');
|
|
271
|
+
expect(helpersFinding).toBeUndefined();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// -------------------------------------------------------------------------
|
|
276
|
+
// Multiple files below threshold
|
|
277
|
+
// -------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
describe('when multiple files are below threshold', () => {
|
|
280
|
+
beforeEach(() => {
|
|
281
|
+
readdirMock.mockResolvedValue(['coverage-summary.json']);
|
|
282
|
+
const summary = makeCoverageSummary([
|
|
283
|
+
{ path: 'src/a.ts', lines: 50, functions: 50, branches: 50, statements: 50 },
|
|
284
|
+
{ path: 'src/b.ts', lines: 40, functions: 40, branches: 40, statements: 40 },
|
|
285
|
+
{ path: 'src/c.ts', lines: 30, functions: 30, branches: 30, statements: 30 },
|
|
286
|
+
]);
|
|
287
|
+
readFileMock.mockResolvedValue(JSON.stringify(summary));
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('creates one finding per uncovered file', async () => {
|
|
291
|
+
const agent = createAgent();
|
|
292
|
+
const result = await agent.run();
|
|
293
|
+
|
|
294
|
+
const gapFindings = result.findings.filter((f) => f.id.startsWith('3-coverage-gap'));
|
|
295
|
+
expect(gapFindings).toHaveLength(3);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// -------------------------------------------------------------------------
|
|
300
|
+
// Malformed JSON
|
|
301
|
+
// -------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
describe('when coverage JSON is malformed', () => {
|
|
304
|
+
beforeEach(() => {
|
|
305
|
+
readdirMock.mockResolvedValue(['coverage-summary.json']);
|
|
306
|
+
readFileMock.mockResolvedValue('not valid json {{{');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('returns no findings (skips malformed file gracefully)', async () => {
|
|
310
|
+
const agent = createAgent();
|
|
311
|
+
const result = await agent.run();
|
|
312
|
+
|
|
313
|
+
// Malformed file produces no findings — this is silent skip behaviour
|
|
314
|
+
const gapFindings = result.findings.filter((f) => f.id.startsWith('3-coverage-gap'));
|
|
315
|
+
expect(gapFindings).toHaveLength(0);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|