@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.
Files changed (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. 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
+ });