@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,271 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { Finding } from '../core/types';
3
+ import { BaseAgent } from './base-agent';
4
+ import { login, navigateToSection } from '../helpers/navigation';
5
+ import { resolveEnvironment, isUrlReachable } from '../helpers/env-resolver';
6
+
7
+ /** LCP threshold in milliseconds (Core Web Vitals "Good" boundary). */
8
+ const LCP_THRESHOLD_MS = 2500;
9
+
10
+ /** FCP threshold in milliseconds (Core Web Vitals "Good" boundary). */
11
+ const FCP_THRESHOLD_MS = 1800;
12
+
13
+ /** JS heap size threshold: 100 MB in bytes. */
14
+ const HEAP_SIZE_THRESHOLD_BYTES = 100 * 1024 * 1024;
15
+
16
+ interface NavigationEntry {
17
+ loadEventEnd: number;
18
+ startTime: number;
19
+ }
20
+
21
+ interface PaintEntry {
22
+ name: string;
23
+ startTime: number;
24
+ }
25
+
26
+ interface MemoryInfo {
27
+ usedJSHeapSize: number;
28
+ }
29
+
30
+ interface ExtendedPerformance extends Performance {
31
+ memory?: MemoryInfo;
32
+ }
33
+
34
+ export class PerformanceProfilerAgent extends BaseAgent {
35
+ readonly agentId = 13;
36
+ readonly agentName = 'Performance Profiler';
37
+ private baseUrl = '';
38
+
39
+ protected async preFlight(): Promise<void> {
40
+ const { env } = await resolveEnvironment(this.config, this.phase);
41
+ this.baseUrl = env.baseUrl;
42
+ }
43
+
44
+ protected async execute(): Promise<Finding[]> {
45
+ const findings: Finding[] = [];
46
+
47
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
48
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
49
+ const credentials =
50
+ this.config.auth.credentials?.['admin'] ??
51
+ Object.values(this.config.auth.credentials ?? {})[0];
52
+
53
+ if (!credentials) {
54
+ findings.push({
55
+ id: '13-no-credentials',
56
+ type: 'infra-issue',
57
+ severity: 'critical',
58
+ agentId: this.agentId,
59
+ module: 'performance-profiler',
60
+ description: 'No credentials configured in auth.credentials — cannot log in',
61
+ });
62
+ return findings;
63
+ }
64
+
65
+ const { chromium } = await import('playwright');
66
+
67
+ let browser: Browser | null = null;
68
+ let page: Page | null = null;
69
+
70
+ try {
71
+ browser = await chromium.launch({ headless: true });
72
+ page = await browser.newPage();
73
+
74
+ await login(page, credentials, loginUrl);
75
+
76
+ for (const module of this.config.modules) {
77
+ const moduleFindings = await this.profileModule(page, module.id);
78
+ findings.push(...moduleFindings);
79
+ }
80
+
81
+ // Check memory after navigating all sections (Chromium only)
82
+ const memoryFindings = await this.checkMemoryUsage(page);
83
+ findings.push(...memoryFindings);
84
+
85
+ // Run Lighthouse if available
86
+ const lighthouseFindings = await this.runLighthouse(this.baseUrl);
87
+ findings.push(...lighthouseFindings);
88
+ } finally {
89
+ if (page) await page.close().catch(() => undefined);
90
+ if (browser) await browser.close().catch(() => undefined);
91
+ }
92
+
93
+ return findings;
94
+ }
95
+
96
+ private async profileModule(page: Page, moduleId: string): Promise<Finding[]> {
97
+ const findings: Finding[] = [];
98
+
99
+ try {
100
+ await navigateToSection(page, moduleId, this.config);
101
+ } catch (navErr) {
102
+ findings.push({
103
+ id: `13-nav-${moduleId}`,
104
+ type: 'infra-issue',
105
+ severity: 'high',
106
+ agentId: this.agentId,
107
+ module: moduleId,
108
+ description: `Navigation to module "${moduleId}" failed: ${navErr instanceof Error ? navErr.message : String(navErr)}`,
109
+ });
110
+ return findings;
111
+ }
112
+
113
+ // Measure navigation load time
114
+ const loadTimeMs = await page
115
+ .evaluate((): number => {
116
+ const entries = performance.getEntriesByType('navigation') as unknown as NavigationEntry[];
117
+ if (entries.length === 0) return 0;
118
+ const nav = entries[0];
119
+ return nav.loadEventEnd - nav.startTime;
120
+ })
121
+ .catch(() => 0);
122
+
123
+ this.addEvidence({
124
+ type: 'log',
125
+ path: `${this.runDir}/perf-${moduleId}.json`,
126
+ description: `Performance metrics for module "${moduleId}" — loadTime: ${loadTimeMs}ms`,
127
+ });
128
+
129
+ // Measure paint timings (FCP / LCP)
130
+ const paintEntriesRaw = await page
131
+ .evaluate((): PaintEntry[] => {
132
+ return performance.getEntriesByType('paint').map((e) => ({
133
+ name: e.name,
134
+ startTime: e.startTime,
135
+ }));
136
+ })
137
+ .catch(() => [] as PaintEntry[]);
138
+
139
+ const paintEntries: PaintEntry[] = Array.isArray(paintEntriesRaw) ? paintEntriesRaw : [];
140
+ const fcpEntry = paintEntries.find((e) => e.name === 'first-contentful-paint');
141
+ const fcpMs = fcpEntry?.startTime ?? 0;
142
+
143
+ // LCP is reported via PerformanceObserver; use largest-contentful-paint entries if present
144
+ const lcpMs = await page
145
+ .evaluate((): number => {
146
+ const entries = performance.getEntriesByType(
147
+ 'largest-contentful-paint',
148
+ ) as Array<{ startTime: number }>;
149
+ if (entries.length === 0) return 0;
150
+ return entries[entries.length - 1].startTime;
151
+ })
152
+ .catch(() => 0);
153
+
154
+ if (fcpMs > FCP_THRESHOLD_MS) {
155
+ findings.push({
156
+ id: `13-fcp-${moduleId}`,
157
+ type: 'code-bug-logic',
158
+ severity: 'medium',
159
+ agentId: this.agentId,
160
+ module: moduleId,
161
+ description: `First Contentful Paint for module "${moduleId}" is ${fcpMs.toFixed(0)}ms (threshold: ${FCP_THRESHOLD_MS}ms)`,
162
+ suggestedFix:
163
+ 'Reduce server response time, eliminate render-blocking resources, or defer non-critical scripts',
164
+ });
165
+ }
166
+
167
+ if (lcpMs > LCP_THRESHOLD_MS) {
168
+ findings.push({
169
+ id: `13-lcp-${moduleId}`,
170
+ type: 'code-bug-logic',
171
+ severity: 'high',
172
+ agentId: this.agentId,
173
+ module: moduleId,
174
+ description: `Largest Contentful Paint for module "${moduleId}" is ${lcpMs.toFixed(0)}ms (threshold: ${LCP_THRESHOLD_MS}ms)`,
175
+ suggestedFix:
176
+ 'Optimise hero images, preload critical assets, or reduce Time To First Byte',
177
+ });
178
+ }
179
+
180
+ if (loadTimeMs > 0 && loadTimeMs > this.config.thresholds.apiResponseTime * 2) {
181
+ findings.push({
182
+ id: `13-load-time-${moduleId}`,
183
+ type: 'code-bug-logic',
184
+ severity: 'medium',
185
+ agentId: this.agentId,
186
+ module: moduleId,
187
+ description: `Page load time for module "${moduleId}" is ${loadTimeMs.toFixed(0)}ms (threshold: ${this.config.thresholds.apiResponseTime * 2}ms)`,
188
+ suggestedFix: 'Review waterfall of network requests and defer non-critical loading',
189
+ });
190
+ }
191
+
192
+ return findings;
193
+ }
194
+
195
+ private async checkMemoryUsage(page: Page): Promise<Finding[]> {
196
+ const findings: Finding[] = [];
197
+
198
+ try {
199
+ const usedHeap = await page
200
+ .evaluate((): number => {
201
+ const perf = performance as ExtendedPerformance;
202
+ return perf.memory?.usedJSHeapSize ?? 0;
203
+ })
204
+ .catch(() => 0);
205
+
206
+ if (usedHeap > HEAP_SIZE_THRESHOLD_BYTES) {
207
+ const usedMb = (usedHeap / 1024 / 1024).toFixed(1);
208
+ const thresholdMb = (HEAP_SIZE_THRESHOLD_BYTES / 1024 / 1024).toFixed(0);
209
+ findings.push({
210
+ id: '13-memory-heap',
211
+ type: 'code-bug-logic',
212
+ severity: 'high',
213
+ agentId: this.agentId,
214
+ module: 'memory',
215
+ description: `JS heap size after navigating all modules is ${usedMb}MB (threshold: ${thresholdMb}MB). Possible memory leak.`,
216
+ suggestedFix:
217
+ 'Profile with Chrome DevTools Memory panel, check for event listener leaks and large retained objects',
218
+ });
219
+ }
220
+ } catch {
221
+ // memory API is Chromium-only; ignore on other browsers
222
+ }
223
+
224
+ return findings;
225
+ }
226
+
227
+ private async runLighthouse(baseUrl: string): Promise<Finding[]> {
228
+ const findings: Finding[] = [];
229
+
230
+ try {
231
+ // Dynamically import lighthouse — it is an optional peer dependency.
232
+ // Use a variable to prevent TypeScript from statically resolving the module.
233
+ const lighthouseModuleName = 'lighthouse';
234
+ const lighthouse = await import(/* webpackIgnore: true */ lighthouseModuleName).catch((): null => null) as { default?: (...args: unknown[]) => Promise<unknown> } | null;
235
+ if (!lighthouse?.default) return findings;
236
+
237
+ const run = lighthouse.default as (url: string, opts: Record<string, unknown>) => Promise<{ lhr: { categories: Record<string, { score: number | null }> } } | undefined>;
238
+
239
+ const result = await run(baseUrl, {
240
+ onlyCategories: ['performance'],
241
+ output: 'json',
242
+ logLevel: 'silent',
243
+ });
244
+
245
+ if (!result) return findings;
246
+
247
+ const perfScore = (result.lhr.categories['performance']?.score ?? null);
248
+ if (perfScore === null) return findings;
249
+
250
+ const perfPercent = Math.round(perfScore * 100);
251
+ const threshold = this.config.thresholds.lighthouse.performance;
252
+
253
+ if (perfPercent < threshold) {
254
+ findings.push({
255
+ id: '13-lighthouse-performance',
256
+ type: 'code-bug-logic',
257
+ severity: 'medium',
258
+ agentId: this.agentId,
259
+ module: 'lighthouse',
260
+ description: `Lighthouse performance score is ${perfPercent} (threshold: ${threshold})`,
261
+ suggestedFix:
262
+ 'Address Lighthouse performance opportunities: reduce unused JavaScript, serve next-gen images, avoid large layout shifts',
263
+ });
264
+ }
265
+ } catch {
266
+ // Lighthouse not installed or audit failed — skip without failing
267
+ }
268
+
269
+ return findings;
270
+ }
271
+ }
@@ -0,0 +1,417 @@
1
+ import type { Finding } from '../core/types';
2
+ import { BaseAgent } from './base-agent';
3
+ import { ApiClient } from '../helpers/api-client';
4
+ import { resolveEnvironment, isUrlReachable } from '../helpers/env-resolver';
5
+
6
+ /** Shape returned by the /api/clients endpoint for a single client record. */
7
+ interface ClientRecord {
8
+ id: string;
9
+ name: string;
10
+ email: string;
11
+ amount?: number;
12
+ createdAt?: string;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ /** Payload used when creating a test client. */
17
+ interface CreateClientPayload {
18
+ name: string;
19
+ email: string;
20
+ amount?: number;
21
+ testRecord?: boolean;
22
+ timezone?: string;
23
+ }
24
+
25
+ export class DataIntegrityAuditorAgent extends BaseAgent {
26
+ readonly agentId = 14;
27
+ readonly agentName = 'Data Integrity Auditor';
28
+
29
+ protected async preFlight(): Promise<void> {
30
+ if (!this.config.accuracy.enabled) {
31
+ throw new Error(
32
+ 'config.accuracy.enabled is false — Data Integrity Auditor requires accuracy checks to be enabled',
33
+ );
34
+ }
35
+
36
+ const { env, fallback } = await resolveEnvironment(this.config, this.phase);
37
+
38
+ if (!env) {
39
+ throw new Error('No environment configured — cannot verify API reachability');
40
+ }
41
+
42
+ // If we fell back to alpha, don't throw on unreachable — execute() will handle it gracefully
43
+ if (!fallback) {
44
+ const reachable = await isUrlReachable(env.baseUrl);
45
+ if (!reachable) {
46
+ throw new Error(`API base URL is not reachable: ${env.baseUrl}`);
47
+ }
48
+ }
49
+ }
50
+
51
+ protected async execute(): Promise<Finding[]> {
52
+ const findings: Finding[] = [];
53
+
54
+ const { env, fallback } = await resolveEnvironment(this.config, this.phase);
55
+
56
+ if (!env) {
57
+ findings.push({
58
+ id: '14-no-env',
59
+ type: 'infra-issue',
60
+ severity: 'critical',
61
+ agentId: this.agentId,
62
+ module: 'data-integrity',
63
+ description: 'No environment configured — cannot determine base URL',
64
+ });
65
+ return findings;
66
+ }
67
+
68
+ const credentials =
69
+ this.config.auth.credentials?.['admin'] ??
70
+ Object.values(this.config.auth.credentials ?? {})[0];
71
+
72
+ if (!credentials) {
73
+ findings.push({
74
+ id: '14-no-credentials',
75
+ type: 'infra-issue',
76
+ severity: 'critical',
77
+ agentId: this.agentId,
78
+ module: 'data-integrity',
79
+ description: 'No credentials configured in auth.credentials — cannot authenticate',
80
+ });
81
+ return findings;
82
+ }
83
+
84
+ let client: ApiClient;
85
+ try {
86
+ client = await ApiClient.createAuthenticated(env.baseUrl, credentials);
87
+ } catch (authErr) {
88
+ // Auth failure is always medium — indicates missing API or wrong credentials, not a data integrity bug
89
+ const authSeverity = 'medium' as const;
90
+ findings.push({
91
+ id: '14-auth-failed',
92
+ type: 'infra-issue',
93
+ severity: authSeverity,
94
+ agentId: this.agentId,
95
+ module: 'data-integrity',
96
+ description: `Authentication failed: ${authErr instanceof Error ? authErr.message : String(authErr)}`,
97
+ });
98
+ return findings;
99
+ }
100
+
101
+ // CRUD round-trip tests
102
+ const crudFindings = await this.testCrudRoundTrip(client);
103
+ findings.push(...crudFindings);
104
+
105
+ // Decimal precision test
106
+ const decimalFindings = await this.testDecimalPrecision(client);
107
+ findings.push(...decimalFindings);
108
+
109
+ // Timezone test
110
+ const timezoneFindings = await this.testTimezone(client);
111
+ findings.push(...timezoneFindings);
112
+
113
+ return findings;
114
+ }
115
+
116
+ private async testCrudRoundTrip(client: ApiClient): Promise<Finding[]> {
117
+ const findings: Finding[] = [];
118
+ let createdId: string | null = null;
119
+
120
+ const createPayload: CreateClientPayload = {
121
+ name: 'TF-DataIntegrity-Test',
122
+ email: `tf-integrity-${Date.now()}@test.invalid`,
123
+ testRecord: true,
124
+ };
125
+
126
+ // CREATE
127
+ let createResp;
128
+ try {
129
+ createResp = await client.post('/api/clients', createPayload);
130
+ } catch (err) {
131
+ findings.push({
132
+ id: '14-crud-create-error',
133
+ type: 'infra-issue',
134
+ severity: 'critical',
135
+ agentId: this.agentId,
136
+ module: 'data-integrity',
137
+ description: `POST /api/clients failed: ${err instanceof Error ? err.message : String(err)}`,
138
+ });
139
+ return findings;
140
+ }
141
+
142
+ if (createResp.status !== 200 && createResp.status !== 201) {
143
+ findings.push({
144
+ id: '14-crud-create-status',
145
+ type: 'code-bug-logic',
146
+ severity: 'high',
147
+ agentId: this.agentId,
148
+ module: 'data-integrity',
149
+ description: `POST /api/clients returned ${createResp.status} — expected 200 or 201`,
150
+ });
151
+ return findings;
152
+ }
153
+
154
+ const created = createResp.body as ClientRecord;
155
+ createdId = created?.id ?? null;
156
+
157
+ if (!createdId) {
158
+ findings.push({
159
+ id: '14-crud-create-no-id',
160
+ type: 'code-bug-logic',
161
+ severity: 'high',
162
+ agentId: this.agentId,
163
+ module: 'data-integrity',
164
+ description: 'POST /api/clients response did not include an `id` field',
165
+ });
166
+ return findings;
167
+ }
168
+
169
+ // READ — verify all fields match what was sent
170
+ let readResp;
171
+ try {
172
+ readResp = await client.get(`/api/clients/${createdId}`);
173
+ } catch (err) {
174
+ findings.push({
175
+ id: `14-crud-read-error-${createdId}`,
176
+ type: 'infra-issue',
177
+ severity: 'critical',
178
+ agentId: this.agentId,
179
+ module: 'data-integrity',
180
+ description: `GET /api/clients/${createdId} failed: ${err instanceof Error ? err.message : String(err)}`,
181
+ });
182
+ return findings;
183
+ }
184
+
185
+ if (readResp.status !== 200) {
186
+ findings.push({
187
+ id: `14-crud-read-status-${createdId}`,
188
+ type: 'code-bug-logic',
189
+ severity: 'high',
190
+ agentId: this.agentId,
191
+ module: 'data-integrity',
192
+ description: `GET /api/clients/${createdId} returned ${readResp.status} — expected 200 after create`,
193
+ });
194
+ } else {
195
+ const read = readResp.body as ClientRecord;
196
+ // Verify fields match
197
+ const mismatches: string[] = [];
198
+ if (read.name !== createPayload.name) {
199
+ mismatches.push(`name: sent "${createPayload.name}", got "${read.name}"`);
200
+ }
201
+ if (read.email !== createPayload.email) {
202
+ mismatches.push(`email: sent "${createPayload.email}", got "${read.email}"`);
203
+ }
204
+
205
+ if (mismatches.length > 0) {
206
+ findings.push({
207
+ id: `14-crud-field-mismatch-${createdId}`,
208
+ type: 'code-bug-logic',
209
+ severity: 'high',
210
+ agentId: this.agentId,
211
+ module: 'data-integrity',
212
+ description: `CREATE→READ field mismatch for client ${createdId}: ${mismatches.join('; ')}`,
213
+ suggestedFix: 'Verify the API serialises and persists all fields from the request body',
214
+ });
215
+ }
216
+ }
217
+
218
+ // UPDATE
219
+ const updatePayload = { name: 'TF-DataIntegrity-Updated' };
220
+ let updateResp;
221
+ try {
222
+ updateResp = await client.put(`/api/clients/${createdId}`, updatePayload);
223
+ } catch (err) {
224
+ findings.push({
225
+ id: `14-crud-update-error-${createdId}`,
226
+ type: 'infra-issue',
227
+ severity: 'high',
228
+ agentId: this.agentId,
229
+ module: 'data-integrity',
230
+ description: `PUT /api/clients/${createdId} failed: ${err instanceof Error ? err.message : String(err)}`,
231
+ });
232
+ }
233
+
234
+ if (updateResp && (updateResp.status === 200 || updateResp.status === 204)) {
235
+ // Read back and verify update was applied
236
+ const afterUpdateResp = await client.get(`/api/clients/${createdId}`).catch(() => null);
237
+ if (afterUpdateResp && afterUpdateResp.status === 200) {
238
+ const updated = afterUpdateResp.body as ClientRecord;
239
+ if (updated.name !== updatePayload.name) {
240
+ findings.push({
241
+ id: `14-crud-update-mismatch-${createdId}`,
242
+ type: 'code-bug-logic',
243
+ severity: 'high',
244
+ agentId: this.agentId,
245
+ module: 'data-integrity',
246
+ description:
247
+ `UPDATE→READ mismatch for client ${createdId}: name should be "${updatePayload.name}" but got "${updated.name}"`,
248
+ suggestedFix: 'Verify the API persists PUT request body fields correctly',
249
+ });
250
+ }
251
+ }
252
+ }
253
+
254
+ // DELETE
255
+ let deleteResp;
256
+ try {
257
+ deleteResp = await client.delete(`/api/clients/${createdId}`);
258
+ } catch (err) {
259
+ findings.push({
260
+ id: `14-crud-delete-error-${createdId}`,
261
+ type: 'infra-issue',
262
+ severity: 'high',
263
+ agentId: this.agentId,
264
+ module: 'data-integrity',
265
+ description: `DELETE /api/clients/${createdId} failed: ${err instanceof Error ? err.message : String(err)}`,
266
+ });
267
+ return findings;
268
+ }
269
+
270
+ if (deleteResp.status !== 200 && deleteResp.status !== 204) {
271
+ findings.push({
272
+ id: `14-crud-delete-status-${createdId}`,
273
+ type: 'code-bug-logic',
274
+ severity: 'medium',
275
+ agentId: this.agentId,
276
+ module: 'data-integrity',
277
+ description: `DELETE /api/clients/${createdId} returned ${deleteResp.status} — expected 200 or 204`,
278
+ });
279
+ }
280
+
281
+ // Verify 404 after delete
282
+ const afterDeleteResp = await client.get(`/api/clients/${createdId}`).catch(() => null);
283
+ if (afterDeleteResp && afterDeleteResp.status !== 404) {
284
+ findings.push({
285
+ id: `14-crud-delete-verify-${createdId}`,
286
+ type: 'code-bug-logic',
287
+ severity: 'high',
288
+ agentId: this.agentId,
289
+ module: 'data-integrity',
290
+ description: `GET /api/clients/${createdId} returned ${afterDeleteResp.status} after DELETE — expected 404`,
291
+ suggestedFix: 'Ensure DELETE removes the record and subsequent GET returns 404',
292
+ });
293
+ }
294
+
295
+ return findings;
296
+ }
297
+
298
+ private async testDecimalPrecision(client: ApiClient): Promise<Finding[]> {
299
+ const findings: Finding[] = [];
300
+ const precision = this.config.accuracy.decimalPrecision;
301
+ const testAmount = 1000.105;
302
+
303
+ let createdId: string | null = null;
304
+
305
+ try {
306
+ const createPayload: CreateClientPayload = {
307
+ name: 'TF-Decimal-Test',
308
+ email: `tf-decimal-${Date.now()}@test.invalid`,
309
+ amount: testAmount,
310
+ testRecord: true,
311
+ };
312
+
313
+ const createResp = await client.post('/api/clients', createPayload);
314
+ if (createResp.status !== 200 && createResp.status !== 201) {
315
+ // API may not support amount field — skip precision check
316
+ return findings;
317
+ }
318
+
319
+ const created = createResp.body as ClientRecord;
320
+ createdId = created?.id ?? null;
321
+ if (!createdId) return findings;
322
+
323
+ const readResp = await client.get(`/api/clients/${createdId}`);
324
+ if (readResp.status !== 200) return findings;
325
+
326
+ const record = readResp.body as ClientRecord;
327
+ const storedAmount = record?.amount;
328
+
329
+ if (storedAmount !== undefined && storedAmount !== null) {
330
+ // Check drift: compare to expected precision
331
+ const factor = Math.pow(10, precision);
332
+ const expected = Math.round(testAmount * factor) / factor;
333
+ const stored = Math.round(Number(storedAmount) * factor) / factor;
334
+
335
+ if (expected !== stored) {
336
+ findings.push({
337
+ id: `14-decimal-drift-${createdId}`,
338
+ type: 'code-bug-logic',
339
+ severity: 'high',
340
+ agentId: this.agentId,
341
+ module: 'data-integrity',
342
+ description:
343
+ `Decimal precision drift: sent amount ${testAmount}, stored as ${storedAmount}. ` +
344
+ `Expected ${expected} at ${precision} decimal places, got ${stored}`,
345
+ suggestedFix:
346
+ 'Use NUMERIC/DECIMAL SQL types with explicit precision rather than FLOAT. ' +
347
+ 'Validate and round amounts to configured decimal precision on input.',
348
+ });
349
+ }
350
+ }
351
+ } catch {
352
+ // Endpoint may not support amount — skip
353
+ } finally {
354
+ if (createdId) {
355
+ await client.delete(`/api/clients/${createdId}`).catch(() => undefined);
356
+ }
357
+ }
358
+
359
+ return findings;
360
+ }
361
+
362
+ private async testTimezone(client: ApiClient): Promise<Finding[]> {
363
+ const findings: Finding[] = [];
364
+ const expectedTimezone = this.config.accuracy.timezone;
365
+ let createdId: string | null = null;
366
+
367
+ try {
368
+ const createPayload: CreateClientPayload = {
369
+ name: 'TF-Timezone-Test',
370
+ email: `tf-tz-${Date.now()}@test.invalid`,
371
+ testRecord: true,
372
+ };
373
+
374
+ const createResp = await client.post('/api/clients', createPayload);
375
+ if (createResp.status !== 200 && createResp.status !== 201) return findings;
376
+
377
+ const created = createResp.body as ClientRecord;
378
+ createdId = created?.id ?? null;
379
+ if (!createdId) return findings;
380
+
381
+ const readResp = await client.get(`/api/clients/${createdId}`);
382
+ if (readResp.status !== 200) return findings;
383
+
384
+ const record = readResp.body as ClientRecord;
385
+ const createdAt = record?.createdAt;
386
+
387
+ if (createdAt && typeof createdAt === 'string') {
388
+ // ISO 8601 dates should end with Z (UTC) or include timezone offset
389
+ const isUtc = createdAt.endsWith('Z') || createdAt.includes('+00:00');
390
+ const isConfiguredTz = expectedTimezone === 'UTC' ? isUtc : createdAt.includes(expectedTimezone);
391
+
392
+ if (!isConfiguredTz && !isUtc) {
393
+ findings.push({
394
+ id: `14-timezone-mismatch-${createdId}`,
395
+ type: 'code-bug-logic',
396
+ severity: 'medium',
397
+ agentId: this.agentId,
398
+ module: 'data-integrity',
399
+ description:
400
+ `Timezone mismatch: createdAt "${createdAt}" does not reflect configured timezone "${expectedTimezone}"`,
401
+ suggestedFix:
402
+ 'Ensure all datetime fields are stored and returned in the configured timezone. ' +
403
+ 'Store as UTC and convert on display, or consistently use the configured timezone.',
404
+ });
405
+ }
406
+ }
407
+ } catch {
408
+ // Endpoint may not return createdAt — skip
409
+ } finally {
410
+ if (createdId) {
411
+ await client.delete(`/api/clients/${createdId}`).catch(() => undefined);
412
+ }
413
+ }
414
+
415
+ return findings;
416
+ }
417
+ }