@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,573 @@
|
|
|
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: {
|
|
13
|
+
loginUrl: 'http://localhost/login',
|
|
14
|
+
credentials: { admin: { email: 'admin@test.com', password: 'secret' } },
|
|
15
|
+
},
|
|
16
|
+
modules: [{ id: 'clients', route: '/clients', 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: true, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
|
|
26
|
+
tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
|
|
27
|
+
costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
|
|
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
|
+
} satisfies ValidatedConfig;
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Mock fetch
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const fetchMock = vi.fn();
|
|
45
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Mock ApiClient
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
type MockApiResponse = { status: number; body: unknown; headers: Record<string, string>; durationMs: number };
|
|
52
|
+
|
|
53
|
+
interface MockApiClientInstance {
|
|
54
|
+
get: ReturnType<typeof vi.fn>;
|
|
55
|
+
post: ReturnType<typeof vi.fn>;
|
|
56
|
+
put: ReturnType<typeof vi.fn>;
|
|
57
|
+
delete: ReturnType<typeof vi.fn>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const mockApiClientInstance: MockApiClientInstance = {
|
|
61
|
+
get: vi.fn(),
|
|
62
|
+
post: vi.fn(),
|
|
63
|
+
put: vi.fn(),
|
|
64
|
+
delete: vi.fn(),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const createAuthenticatedMock = vi.fn().mockResolvedValue(mockApiClientInstance);
|
|
68
|
+
|
|
69
|
+
vi.mock('../../helpers/api-client', () => ({
|
|
70
|
+
ApiClient: {
|
|
71
|
+
createAuthenticated: (...args: unknown[]) => createAuthenticatedMock(...args),
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Response helpers
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
function ok(body: unknown): MockApiResponse {
|
|
80
|
+
return { status: 200, body, headers: {}, durationMs: 10 };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function created(body: unknown): MockApiResponse {
|
|
84
|
+
return { status: 201, body, headers: {}, durationMs: 10 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function noContent(): MockApiResponse {
|
|
88
|
+
return { status: 204, body: null, headers: {}, durationMs: 10 };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function notFound(): MockApiResponse {
|
|
92
|
+
return { status: 404, body: { message: 'Not Found' }, headers: {}, durationMs: 10 };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function serverError(): MockApiResponse {
|
|
96
|
+
return { status: 500, body: { message: 'Error' }, headers: {}, durationMs: 10 };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Dynamic import
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
const { DataIntegrityAuditorAgent } = await import('../14-data-integrity-auditor');
|
|
104
|
+
|
|
105
|
+
function createAgent(
|
|
106
|
+
config: ValidatedConfig = stubConfig,
|
|
107
|
+
phase: Phase = 'beta',
|
|
108
|
+
): InstanceType<typeof DataIntegrityAuditorAgent> {
|
|
109
|
+
return new DataIntegrityAuditorAgent(config, phase, '/tmp/test-run');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Tests — extended coverage
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe('DataIntegrityAuditorAgent — extended coverage', () => {
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
fetchMock.mockReset().mockResolvedValue({ ok: true, status: 200 });
|
|
119
|
+
createAuthenticatedMock.mockReset().mockResolvedValue(mockApiClientInstance);
|
|
120
|
+
mockApiClientInstance.get.mockReset();
|
|
121
|
+
mockApiClientInstance.post.mockReset();
|
|
122
|
+
mockApiClientInstance.put.mockReset();
|
|
123
|
+
mockApiClientInstance.delete.mockReset();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// -----------------------------------------------------------------------
|
|
127
|
+
// CRUD — no environment
|
|
128
|
+
// -----------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
describe('no environment configured', () => {
|
|
131
|
+
it('returns critical finding when no environment exists', async () => {
|
|
132
|
+
const noEnvConfig: ValidatedConfig = {
|
|
133
|
+
...stubConfig,
|
|
134
|
+
environments: {},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// preFlight resolveEnvironment returns no env — throws
|
|
138
|
+
const agent = createAgent(noEnvConfig);
|
|
139
|
+
const result = await agent.run();
|
|
140
|
+
|
|
141
|
+
expect(result.status).toBe('failed');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
// CRUD — no credentials
|
|
147
|
+
// -----------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
describe('no credentials', () => {
|
|
150
|
+
it('returns critical finding when no credentials configured', async () => {
|
|
151
|
+
const noCredConfig: ValidatedConfig = {
|
|
152
|
+
...stubConfig,
|
|
153
|
+
auth: { loginUrl: '/login', credentials: {} },
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const agent = createAgent(noCredConfig);
|
|
157
|
+
const result = await agent.run();
|
|
158
|
+
|
|
159
|
+
const finding = result.findings.find((f) => f.id === '14-no-credentials');
|
|
160
|
+
expect(finding).toBeDefined();
|
|
161
|
+
expect(finding!.severity).toBe('critical');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// -----------------------------------------------------------------------
|
|
166
|
+
// CRUD — POST fails
|
|
167
|
+
// -----------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
describe('CRUD create errors', () => {
|
|
170
|
+
it('creates critical finding when POST throws', async () => {
|
|
171
|
+
mockApiClientInstance.post.mockRejectedValue(new Error('Connection refused'));
|
|
172
|
+
|
|
173
|
+
const agent = createAgent();
|
|
174
|
+
const result = await agent.run();
|
|
175
|
+
|
|
176
|
+
const finding = result.findings.find((f) => f.id === '14-crud-create-error');
|
|
177
|
+
expect(finding).toBeDefined();
|
|
178
|
+
expect(finding!.severity).toBe('critical');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('creates high finding when POST returns non-200/201 status', async () => {
|
|
182
|
+
mockApiClientInstance.post.mockResolvedValue(serverError());
|
|
183
|
+
|
|
184
|
+
const agent = createAgent();
|
|
185
|
+
const result = await agent.run();
|
|
186
|
+
|
|
187
|
+
const finding = result.findings.find((f) => f.id === '14-crud-create-status');
|
|
188
|
+
expect(finding).toBeDefined();
|
|
189
|
+
expect(finding!.severity).toBe('high');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('creates high finding when POST response has no id field', async () => {
|
|
193
|
+
mockApiClientInstance.post.mockResolvedValue(created({ name: 'test' }));
|
|
194
|
+
|
|
195
|
+
const agent = createAgent();
|
|
196
|
+
const result = await agent.run();
|
|
197
|
+
|
|
198
|
+
const finding = result.findings.find((f) => f.id === '14-crud-create-no-id');
|
|
199
|
+
expect(finding).toBeDefined();
|
|
200
|
+
expect(finding!.severity).toBe('high');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// -----------------------------------------------------------------------
|
|
205
|
+
// CRUD — GET (read) errors
|
|
206
|
+
// -----------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
describe('CRUD read errors', () => {
|
|
209
|
+
it('creates critical finding when GET after create throws', async () => {
|
|
210
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid' };
|
|
211
|
+
mockApiClientInstance.post.mockResolvedValue(created(record));
|
|
212
|
+
mockApiClientInstance.get.mockRejectedValue(new Error('Timeout'));
|
|
213
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
214
|
+
|
|
215
|
+
const agent = createAgent();
|
|
216
|
+
const result = await agent.run();
|
|
217
|
+
|
|
218
|
+
const finding = result.findings.find((f) => f.id.includes('crud-read-error'));
|
|
219
|
+
expect(finding).toBeDefined();
|
|
220
|
+
expect(finding!.severity).toBe('critical');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('creates high finding when GET returns non-200 status after create', async () => {
|
|
224
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
225
|
+
let postCount = 0;
|
|
226
|
+
let getCount = 0;
|
|
227
|
+
|
|
228
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
229
|
+
postCount++;
|
|
230
|
+
const b = body as Record<string, unknown>;
|
|
231
|
+
if (postCount === 1) return created({ id: 'abc-123', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
232
|
+
return serverError(); // decimal/tz tests get 500
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
236
|
+
getCount++;
|
|
237
|
+
if (getCount === 1) return notFound(); // read after create returns 404 — NOT 200
|
|
238
|
+
return notFound();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
mockApiClientInstance.put.mockResolvedValue(ok(record));
|
|
242
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
243
|
+
|
|
244
|
+
const agent = createAgent();
|
|
245
|
+
const result = await agent.run();
|
|
246
|
+
|
|
247
|
+
const finding = result.findings.find((f) => f.id.includes('crud-read-status'));
|
|
248
|
+
expect(finding).toBeDefined();
|
|
249
|
+
expect(finding!.severity).toBe('high');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// -----------------------------------------------------------------------
|
|
254
|
+
// CRUD — UPDATE errors
|
|
255
|
+
// -----------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
describe('CRUD update errors', () => {
|
|
258
|
+
it('creates high finding when PUT throws', async () => {
|
|
259
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
260
|
+
let getCount = 0;
|
|
261
|
+
mockApiClientInstance.post
|
|
262
|
+
.mockResolvedValueOnce(created(record))
|
|
263
|
+
.mockResolvedValue(serverError());
|
|
264
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
265
|
+
getCount++;
|
|
266
|
+
if (getCount === 1) return ok(record);
|
|
267
|
+
if (getCount === 2) return ok({ ...record, name: 'TF-DataIntegrity-Updated' });
|
|
268
|
+
return notFound();
|
|
269
|
+
});
|
|
270
|
+
mockApiClientInstance.put.mockRejectedValue(new Error('Connection reset'));
|
|
271
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
272
|
+
|
|
273
|
+
const agent = createAgent();
|
|
274
|
+
const result = await agent.run();
|
|
275
|
+
|
|
276
|
+
const finding = result.findings.find((f) => f.id.includes('crud-update-error'));
|
|
277
|
+
expect(finding).toBeDefined();
|
|
278
|
+
expect(finding!.severity).toBe('high');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('creates high finding when update read-back shows name mismatch', async () => {
|
|
282
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
283
|
+
let getCount = 0;
|
|
284
|
+
mockApiClientInstance.post
|
|
285
|
+
.mockResolvedValueOnce(created(record))
|
|
286
|
+
.mockResolvedValue(serverError());
|
|
287
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
288
|
+
getCount++;
|
|
289
|
+
if (getCount === 1) return ok(record); // read after create
|
|
290
|
+
if (getCount === 2) return ok({ ...record, name: 'WRONG-NAME' }); // read after update — name not updated!
|
|
291
|
+
return notFound(); // after delete
|
|
292
|
+
});
|
|
293
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ ...record, name: 'TF-DataIntegrity-Updated' }));
|
|
294
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
295
|
+
|
|
296
|
+
const agent = createAgent();
|
|
297
|
+
const result = await agent.run();
|
|
298
|
+
|
|
299
|
+
const finding = result.findings.find((f) => f.id.includes('crud-update-mismatch'));
|
|
300
|
+
expect(finding).toBeDefined();
|
|
301
|
+
expect(finding!.severity).toBe('high');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// -----------------------------------------------------------------------
|
|
306
|
+
// CRUD — DELETE errors
|
|
307
|
+
// -----------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
describe('CRUD delete errors', () => {
|
|
310
|
+
it('creates high finding when DELETE throws', async () => {
|
|
311
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
312
|
+
let getCount = 0;
|
|
313
|
+
mockApiClientInstance.post
|
|
314
|
+
.mockResolvedValueOnce(created(record))
|
|
315
|
+
.mockResolvedValue(serverError());
|
|
316
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
317
|
+
getCount++;
|
|
318
|
+
if (getCount === 1) return ok(record);
|
|
319
|
+
if (getCount === 2) return ok({ ...record, name: 'TF-DataIntegrity-Updated' });
|
|
320
|
+
return notFound();
|
|
321
|
+
});
|
|
322
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ ...record, name: 'TF-DataIntegrity-Updated' }));
|
|
323
|
+
mockApiClientInstance.delete.mockRejectedValue(new Error('DB locked'));
|
|
324
|
+
|
|
325
|
+
const agent = createAgent();
|
|
326
|
+
const result = await agent.run();
|
|
327
|
+
|
|
328
|
+
const finding = result.findings.find((f) => f.id.includes('crud-delete-error'));
|
|
329
|
+
expect(finding).toBeDefined();
|
|
330
|
+
expect(finding!.severity).toBe('high');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('creates medium finding when DELETE returns non-200/204 status', async () => {
|
|
334
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
335
|
+
let getCount = 0;
|
|
336
|
+
mockApiClientInstance.post
|
|
337
|
+
.mockResolvedValueOnce(created(record))
|
|
338
|
+
.mockResolvedValue(serverError());
|
|
339
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
340
|
+
getCount++;
|
|
341
|
+
if (getCount === 1) return ok(record);
|
|
342
|
+
if (getCount === 2) return ok({ ...record, name: 'TF-DataIntegrity-Updated' });
|
|
343
|
+
return notFound();
|
|
344
|
+
});
|
|
345
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ ...record, name: 'TF-DataIntegrity-Updated' }));
|
|
346
|
+
mockApiClientInstance.delete.mockResolvedValue(serverError());
|
|
347
|
+
|
|
348
|
+
const agent = createAgent();
|
|
349
|
+
const result = await agent.run();
|
|
350
|
+
|
|
351
|
+
const finding = result.findings.find((f) => f.id.includes('crud-delete-status'));
|
|
352
|
+
expect(finding).toBeDefined();
|
|
353
|
+
expect(finding!.severity).toBe('medium');
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// -----------------------------------------------------------------------
|
|
358
|
+
// Timezone tests
|
|
359
|
+
// -----------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
describe('timezone validation', () => {
|
|
362
|
+
it('creates finding when createdAt does not match configured timezone or UTC', async () => {
|
|
363
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
364
|
+
let postCount = 0;
|
|
365
|
+
let getCount = 0;
|
|
366
|
+
|
|
367
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
368
|
+
postCount++;
|
|
369
|
+
const b = body as Record<string, unknown>;
|
|
370
|
+
if (postCount === 1) return created({ id: 'crud-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
371
|
+
if (postCount === 2) return created({ id: 'decimal-id', ...b });
|
|
372
|
+
// timezone test
|
|
373
|
+
return created({ id: 'tz-id', ...b, createdAt: '2026-03-27T10:00:00+05:30' });
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
377
|
+
getCount++;
|
|
378
|
+
if (getCount === 1) return ok({ ...record, id: 'crud-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
379
|
+
if (getCount === 2) return ok({ ...record, id: 'crud-id', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
380
|
+
if (getCount === 3) return notFound(); // after delete
|
|
381
|
+
if (getCount === 4) return ok({ id: 'decimal-id', amount: 1000.11 }); // decimal - correct
|
|
382
|
+
if (getCount === 5) return ok({ id: 'tz-id', createdAt: '2026-03-27T10:00:00+05:30' }); // non-UTC, non-configured
|
|
383
|
+
return notFound();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ id: 'crud-id', name: 'TF-DataIntegrity-Updated' }));
|
|
387
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
388
|
+
|
|
389
|
+
const agent = createAgent();
|
|
390
|
+
const result = await agent.run();
|
|
391
|
+
|
|
392
|
+
const tzFinding = result.findings.find((f) => f.id.includes('timezone-mismatch'));
|
|
393
|
+
expect(tzFinding).toBeDefined();
|
|
394
|
+
expect(tzFinding!.severity).toBe('medium');
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('does NOT create timezone finding when createdAt ends with Z (UTC)', async () => {
|
|
398
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
399
|
+
let postCount = 0;
|
|
400
|
+
let getCount = 0;
|
|
401
|
+
|
|
402
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
403
|
+
postCount++;
|
|
404
|
+
const b = body as Record<string, unknown>;
|
|
405
|
+
if (postCount === 1) return created({ id: 'crud-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
406
|
+
if (postCount === 2) return created({ id: 'decimal-id', ...b });
|
|
407
|
+
return created({ id: 'tz-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
411
|
+
getCount++;
|
|
412
|
+
if (getCount === 1) return ok({ ...record, id: 'crud-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
413
|
+
if (getCount === 2) return ok({ ...record, id: 'crud-id', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
414
|
+
if (getCount === 3) return notFound();
|
|
415
|
+
if (getCount === 4) return ok({ id: 'decimal-id', amount: 1000.11 });
|
|
416
|
+
if (getCount === 5) return ok({ id: 'tz-id', createdAt: '2026-03-27T10:00:00Z' }); // UTC — OK
|
|
417
|
+
return notFound();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ id: 'crud-id', name: 'TF-DataIntegrity-Updated' }));
|
|
421
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
422
|
+
|
|
423
|
+
const agent = createAgent();
|
|
424
|
+
const result = await agent.run();
|
|
425
|
+
|
|
426
|
+
const tzFindings = result.findings.filter((f) => f.id.includes('timezone-mismatch'));
|
|
427
|
+
expect(tzFindings).toHaveLength(0);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// -----------------------------------------------------------------------
|
|
432
|
+
// Decimal precision — API doesn't support amount
|
|
433
|
+
// -----------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
describe('decimal precision edge cases', () => {
|
|
436
|
+
it('skips decimal test when API does not support amount field (non-200 create)', async () => {
|
|
437
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
438
|
+
let postCount = 0;
|
|
439
|
+
let getCount = 0;
|
|
440
|
+
|
|
441
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
442
|
+
postCount++;
|
|
443
|
+
const b = body as Record<string, unknown>;
|
|
444
|
+
if (postCount === 1) return created({ id: 'crud-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
445
|
+
// decimal test — endpoint rejects
|
|
446
|
+
if (postCount === 2) return serverError();
|
|
447
|
+
return created({ id: 'tz-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
451
|
+
getCount++;
|
|
452
|
+
if (getCount === 1) return ok({ ...record, id: 'crud-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
453
|
+
if (getCount === 2) return ok({ ...record, id: 'crud-id', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
454
|
+
if (getCount === 3) return notFound();
|
|
455
|
+
if (getCount === 4) return ok({ id: 'tz-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
456
|
+
return notFound();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ id: 'crud-id', name: 'TF-DataIntegrity-Updated' }));
|
|
460
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
461
|
+
|
|
462
|
+
const agent = createAgent();
|
|
463
|
+
const result = await agent.run();
|
|
464
|
+
|
|
465
|
+
const decimalFindings = result.findings.filter((f) => f.id.includes('decimal'));
|
|
466
|
+
expect(decimalFindings).toHaveLength(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('skips decimal check when amount is undefined in response', async () => {
|
|
470
|
+
const record = { id: 'abc-123', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' };
|
|
471
|
+
let postCount = 0;
|
|
472
|
+
let getCount = 0;
|
|
473
|
+
|
|
474
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
475
|
+
postCount++;
|
|
476
|
+
const b = body as Record<string, unknown>;
|
|
477
|
+
if (postCount === 1) return created({ id: 'crud-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
478
|
+
if (postCount === 2) return created({ id: 'decimal-id', ...b });
|
|
479
|
+
return created({ id: 'tz-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
483
|
+
getCount++;
|
|
484
|
+
if (getCount === 1) return ok({ ...record, id: 'crud-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
485
|
+
if (getCount === 2) return ok({ ...record, id: 'crud-id', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
486
|
+
if (getCount === 3) return notFound();
|
|
487
|
+
// decimal read — amount not returned
|
|
488
|
+
if (getCount === 4) return ok({ id: 'decimal-id' /* no amount */ });
|
|
489
|
+
if (getCount === 5) return ok({ id: 'tz-id', createdAt: '2026-03-27T10:00:00Z' });
|
|
490
|
+
return notFound();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
mockApiClientInstance.put.mockResolvedValue(ok({ id: 'crud-id', name: 'TF-DataIntegrity-Updated' }));
|
|
494
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
495
|
+
|
|
496
|
+
const agent = createAgent();
|
|
497
|
+
const result = await agent.run();
|
|
498
|
+
|
|
499
|
+
const decimalFindings = result.findings.filter((f) => f.id.includes('decimal'));
|
|
500
|
+
expect(decimalFindings).toHaveLength(0);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// -----------------------------------------------------------------------
|
|
505
|
+
// Email mismatch in CRUD
|
|
506
|
+
// -----------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
describe('CRUD email mismatch', () => {
|
|
509
|
+
it('creates finding when email field does not match after create', async () => {
|
|
510
|
+
let capturedBody: Record<string, unknown> = {};
|
|
511
|
+
let getCount = 0;
|
|
512
|
+
|
|
513
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
514
|
+
capturedBody = body as Record<string, unknown>;
|
|
515
|
+
return created({ id: 'abc-123', ...capturedBody, createdAt: '2026-03-27T10:00:00Z' });
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
519
|
+
getCount++;
|
|
520
|
+
if (getCount === 1) {
|
|
521
|
+
return ok({ id: 'abc-123', ...capturedBody, email: 'WRONG@test.invalid', createdAt: '2026-03-27T10:00:00Z' });
|
|
522
|
+
}
|
|
523
|
+
if (getCount === 2) return ok({ id: 'abc-123', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
524
|
+
return notFound();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
mockApiClientInstance.put.mockResolvedValue(ok({}));
|
|
528
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
529
|
+
|
|
530
|
+
const agent = createAgent();
|
|
531
|
+
const result = await agent.run();
|
|
532
|
+
|
|
533
|
+
const mismatchFinding = result.findings.find((f) => f.id.includes('crud-field-mismatch'));
|
|
534
|
+
expect(mismatchFinding).toBeDefined();
|
|
535
|
+
expect(mismatchFinding!.description).toContain('email');
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// -----------------------------------------------------------------------
|
|
540
|
+
// Cleanup after decimal/timezone tests
|
|
541
|
+
// -----------------------------------------------------------------------
|
|
542
|
+
|
|
543
|
+
describe('test cleanup', () => {
|
|
544
|
+
it('deletes test records even when decimal/timezone checks fail', async () => {
|
|
545
|
+
let postCount = 0;
|
|
546
|
+
|
|
547
|
+
mockApiClientInstance.post.mockImplementation(async (_path: string, body: unknown) => {
|
|
548
|
+
postCount++;
|
|
549
|
+
const b = body as Record<string, unknown>;
|
|
550
|
+
if (postCount === 1) return created({ id: 'crud-id', ...b, createdAt: '2026-03-27T10:00:00Z' });
|
|
551
|
+
return created({ id: `test-${postCount}`, ...b });
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
let getCount = 0;
|
|
555
|
+
mockApiClientInstance.get.mockImplementation(async () => {
|
|
556
|
+
getCount++;
|
|
557
|
+
if (getCount === 1) return ok({ id: 'crud-id', name: 'TF-DataIntegrity-Test', email: 'tf@test.invalid', createdAt: '2026-03-27T10:00:00Z' });
|
|
558
|
+
if (getCount === 2) return ok({ id: 'crud-id', name: 'TF-DataIntegrity-Updated', createdAt: '2026-03-27T10:00:00Z' });
|
|
559
|
+
if (getCount === 3) return notFound();
|
|
560
|
+
return ok({ id: 'test-2', amount: 1000.11, createdAt: '2026-03-27T10:00:00Z' });
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
mockApiClientInstance.put.mockResolvedValue(ok({}));
|
|
564
|
+
mockApiClientInstance.delete.mockResolvedValue(noContent());
|
|
565
|
+
|
|
566
|
+
const agent = createAgent();
|
|
567
|
+
await agent.run();
|
|
568
|
+
|
|
569
|
+
// delete should have been called for crud record + decimal record + timezone record
|
|
570
|
+
expect(mockApiClientInstance.delete).toHaveBeenCalled();
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
});
|