@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,159 @@
1
+ import { BaseAgent } from './base-agent';
2
+ import { login, waitForSectionLoad } from '../helpers/navigation';
3
+ import { captureScreenshot } from '../helpers/screenshot';
4
+ import { fillForm } from '../helpers/form-filler';
5
+ import { resolveEnvironment } from '../helpers/env-resolver';
6
+ export class WorkflowCompletionTesterAgent extends BaseAgent {
7
+ agentId = 20;
8
+ agentName = 'Workflow Completion Tester';
9
+ baseUrl = '';
10
+ async preFlight() {
11
+ if (!this.config.workflows || this.config.workflows.length === 0) {
12
+ throw new Error('WorkflowCompletionTesterAgent requires at least one workflow in config.workflows');
13
+ }
14
+ const { env } = await resolveEnvironment(this.config, this.phase);
15
+ this.baseUrl = env.baseUrl;
16
+ }
17
+ async execute() {
18
+ const findings = [];
19
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
20
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
21
+ const credentials = this.config.auth.credentials?.['admin'] ??
22
+ Object.values(this.config.auth.credentials ?? {})[0];
23
+ if (!credentials) {
24
+ findings.push({
25
+ id: `${this.agentId}-no-credentials`,
26
+ type: 'infra-issue',
27
+ severity: 'critical',
28
+ agentId: this.agentId,
29
+ module: 'workflow-completion-tester',
30
+ description: 'No credentials configured — cannot log in to run workflow tests',
31
+ });
32
+ return findings;
33
+ }
34
+ const { chromium } = await import('playwright');
35
+ let browser = null;
36
+ let page = null;
37
+ try {
38
+ browser = await chromium.launch({ headless: true });
39
+ page = await browser.newPage();
40
+ try {
41
+ await login(page, credentials, loginUrl);
42
+ }
43
+ catch (loginError) {
44
+ findings.push({
45
+ id: `${this.agentId}-login-failed`,
46
+ type: 'infra-issue',
47
+ severity: 'medium',
48
+ agentId: this.agentId,
49
+ module: 'workflow-completion-tester',
50
+ description: `Login failed — skipping workflow tests: ${loginError instanceof Error ? loginError.message.split('\n')[0] : String(loginError)}`,
51
+ });
52
+ return findings;
53
+ }
54
+ for (const workflow of this.config.workflows) {
55
+ const workflowFindings = await this.runWorkflow(page, workflow.name, workflow.steps);
56
+ findings.push(...workflowFindings);
57
+ }
58
+ }
59
+ catch (playwrightError) {
60
+ findings.push({
61
+ id: `${this.agentId}-playwright-failure`,
62
+ type: 'infra-issue',
63
+ severity: 'medium',
64
+ agentId: this.agentId,
65
+ module: 'workflow-completion-tester',
66
+ description: `Workflow Playwright execution failed: ${playwrightError instanceof Error ? playwrightError.message.split('\n')[0] : String(playwrightError)}`,
67
+ });
68
+ }
69
+ finally {
70
+ if (page)
71
+ await page.close().catch(() => undefined);
72
+ if (browser)
73
+ await browser.close().catch(() => undefined);
74
+ }
75
+ return findings;
76
+ }
77
+ async runWorkflow(page, workflowName, steps) {
78
+ const findings = [];
79
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
80
+ const step = steps[stepIndex];
81
+ try {
82
+ await this.executeStep(page, step);
83
+ }
84
+ catch (err) {
85
+ const description = err instanceof Error ? err.message : String(err);
86
+ const screenshotPath = await captureScreenshot(page, `workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}-failure`, this.runDir).catch(() => '');
87
+ if (screenshotPath) {
88
+ this.addEvidence({
89
+ type: 'screenshot',
90
+ path: screenshotPath,
91
+ description: `[Workflow "${workflowName}"] Failure screenshot at step ${stepIndex} (${step.action}: ${step.target})`,
92
+ });
93
+ }
94
+ findings.push({
95
+ id: `${this.agentId}-workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}`,
96
+ type: 'test-bug',
97
+ severity: 'medium',
98
+ agentId: this.agentId,
99
+ module: `workflow:${workflowName}`,
100
+ description: `Workflow "${workflowName}" failed at step ${stepIndex} (${step.action}: ${step.target}): ${description}`,
101
+ screenshot: screenshotPath || undefined,
102
+ });
103
+ // Stop processing this workflow on failure
104
+ break;
105
+ }
106
+ // Screenshot evidence at each step
107
+ const screenshotPath = await captureScreenshot(page, `workflow-${workflowName.replace(/\s+/g, '-')}-step${stepIndex}`, this.runDir).catch(() => '');
108
+ if (screenshotPath) {
109
+ this.addEvidence({
110
+ type: 'screenshot',
111
+ path: screenshotPath,
112
+ description: `[Workflow "${workflowName}"] Step ${stepIndex} completed (${step.action}: ${step.target})`,
113
+ });
114
+ }
115
+ }
116
+ return findings;
117
+ }
118
+ async executeStep(page, step) {
119
+ switch (step.action) {
120
+ case 'navigate': {
121
+ const url = step.target.startsWith('http') ? step.target : `${this.baseUrl}${step.target}`;
122
+ await page.goto(url);
123
+ await waitForSectionLoad(page);
124
+ break;
125
+ }
126
+ case 'click': {
127
+ await page.click(step.target);
128
+ if (step.verify) {
129
+ await page.waitForSelector(step.verify, { timeout: step.timeout ?? 5_000 });
130
+ }
131
+ break;
132
+ }
133
+ case 'fill-form': {
134
+ await fillForm(page, step.target, step.data ? { data: step.data } : undefined);
135
+ break;
136
+ }
137
+ case 'wait': {
138
+ await page.waitForSelector(step.target, { timeout: step.timeout ?? 5_000 });
139
+ break;
140
+ }
141
+ case 'verify': {
142
+ const element = await page.waitForSelector(step.target, { timeout: step.timeout ?? 5_000 });
143
+ if (step.contains !== undefined) {
144
+ const text = await element.textContent();
145
+ if (!text?.includes(step.contains)) {
146
+ throw new Error(`Element "${step.target}" does not contain expected text "${step.contains}" — found: "${text ?? ''}"`);
147
+ }
148
+ }
149
+ break;
150
+ }
151
+ default: {
152
+ // TypeScript exhaustiveness guard
153
+ const _exhaustive = step.action;
154
+ throw new Error(`Unknown workflow step action: ${String(_exhaustive)}`);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ //# sourceMappingURL=20-workflow-completion-tester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20-workflow-completion-tester.js","sourceRoot":"","sources":["../../agents/20-workflow-completion-tester.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Finding } from '../core/types';
2
+ import { BaseAgent } from './base-agent';
3
+ export declare class StateSessionTesterAgent extends BaseAgent {
4
+ readonly agentId = 21;
5
+ readonly agentName = "State & Session Tester";
6
+ private baseUrl;
7
+ protected preFlight(): Promise<void>;
8
+ protected execute(): Promise<Finding[]>;
9
+ }
10
+ //# sourceMappingURL=21-state-session-tester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"21-state-session-tester.d.ts","sourceRoot":"","sources":["../../agents/21-state-session-tester.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,qBAAa,uBAAwB,SAAQ,SAAS;IACpD,QAAQ,CAAC,OAAO,MAAM;IACtB,QAAQ,CAAC,SAAS,4BAA4B;IAC9C,OAAO,CAAC,OAAO,CAAM;cAEL,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;cAK1B,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAsP9C"}
@@ -0,0 +1,233 @@
1
+ import { BaseAgent } from './base-agent';
2
+ import { resolveEnvironment } from '../helpers/env-resolver';
3
+ export class StateSessionTesterAgent extends BaseAgent {
4
+ agentId = 21;
5
+ agentName = 'State & Session Tester';
6
+ baseUrl = '';
7
+ async preFlight() {
8
+ const { env } = await resolveEnvironment(this.config, this.phase);
9
+ this.baseUrl = env.baseUrl;
10
+ }
11
+ async execute() {
12
+ const findings = [];
13
+ const rawLoginUrl = this.config.auth.loginUrl ?? '/login';
14
+ const loginUrl = rawLoginUrl.startsWith('http') ? rawLoginUrl : `${this.baseUrl}${rawLoginUrl}`;
15
+ const credentials = this.config.auth.credentials?.['admin'] ??
16
+ Object.values(this.config.auth.credentials ?? {})[0];
17
+ if (!credentials) {
18
+ findings.push({
19
+ id: `${this.agentId}-no-credentials`,
20
+ type: 'infra-issue',
21
+ severity: 'critical',
22
+ agentId: this.agentId,
23
+ module: 'state-session',
24
+ description: 'No credentials configured — cannot run state/session tests',
25
+ });
26
+ return findings;
27
+ }
28
+ if (this.config.modules.length === 0) {
29
+ findings.push({
30
+ id: `${this.agentId}-no-modules`,
31
+ type: 'infra-issue',
32
+ severity: 'medium',
33
+ agentId: this.agentId,
34
+ module: 'state-session',
35
+ description: 'No modules configured — skipping state/session tests',
36
+ });
37
+ return findings;
38
+ }
39
+ const { chromium } = await import('playwright');
40
+ // -----------------------------------------------------------------------
41
+ // Scenario 1: Deep link — open module URL directly in a fresh context
42
+ // -----------------------------------------------------------------------
43
+ {
44
+ let browser = null;
45
+ let context = null;
46
+ let page = null;
47
+ try {
48
+ browser = await chromium.launch({ headless: true });
49
+ context = await browser.newContext();
50
+ page = await context.newPage();
51
+ const targetModule = this.config.modules[0];
52
+ const deepLinkUrl = `${this.baseUrl}${targetModule.route}`;
53
+ // Navigate directly to a deep link without prior login
54
+ await page.goto(deepLinkUrl, { waitUntil: 'networkidle', timeout: 20_000 });
55
+ // After potential login redirect and re-authentication, check we eventually load the module
56
+ const finalUrl = page.url();
57
+ const isOnLogin = finalUrl.includes('/login') ||
58
+ finalUrl.includes('/auth') ||
59
+ finalUrl.includes('/signin');
60
+ if (!isOnLogin) {
61
+ // We either loaded the module or landed somewhere unexpected without login
62
+ const pageContent = await page.content();
63
+ const moduleLoaded = pageContent.includes(targetModule.id) ||
64
+ pageContent.includes(targetModule.route.replace('/', ''));
65
+ if (!moduleLoaded) {
66
+ findings.push({
67
+ id: `${this.agentId}-deep-link-${targetModule.id}`,
68
+ type: 'code-bug-logic',
69
+ severity: 'medium',
70
+ agentId: this.agentId,
71
+ module: targetModule.id,
72
+ description: `Deep link scenario: navigating directly to "${deepLinkUrl}" did not load the expected module ` +
73
+ `or redirect to login. Final URL: ${finalUrl}`,
74
+ suggestedFix: 'Ensure unauthenticated deep links redirect to login then back to the original route.',
75
+ });
76
+ }
77
+ }
78
+ // Redirect to login on direct deep link is the correct behaviour — no finding
79
+ }
80
+ catch (error) {
81
+ findings.push({
82
+ id: `${this.agentId}-deep-link-error`,
83
+ type: 'infra-issue',
84
+ severity: 'medium',
85
+ agentId: this.agentId,
86
+ module: this.config.modules[0]?.id ?? 'unknown',
87
+ description: `Deep link scenario failed: ${error instanceof Error ? error.message : String(error)}`,
88
+ });
89
+ }
90
+ finally {
91
+ if (page)
92
+ await page.close().catch(() => undefined);
93
+ if (context)
94
+ await context.close().catch(() => undefined);
95
+ if (browser)
96
+ await browser.close().catch(() => undefined);
97
+ }
98
+ }
99
+ // -----------------------------------------------------------------------
100
+ // Scenario 2: Refresh persistence — navigate to a module, reload, verify
101
+ // -----------------------------------------------------------------------
102
+ {
103
+ let browser = null;
104
+ let context = null;
105
+ let page = null;
106
+ try {
107
+ browser = await chromium.launch({ headless: true });
108
+ context = await browser.newContext();
109
+ page = await context.newPage();
110
+ // Login first
111
+ await page.goto(loginUrl, { waitUntil: 'networkidle', timeout: 20_000 });
112
+ await page
113
+ .fill('input[type="email"], input[name="email"]', credentials.email)
114
+ .catch(() => undefined);
115
+ await page
116
+ .fill('input[type="password"], input[name="password"]', credentials.password)
117
+ .catch(() => undefined);
118
+ await page.click('button[type="submit"]').catch(() => undefined);
119
+ await page
120
+ .waitForNavigation({ waitUntil: 'networkidle', timeout: 15_000 })
121
+ .catch(() => undefined);
122
+ const targetModule = this.config.modules[0];
123
+ const moduleUrl = `${this.baseUrl}${targetModule.route}`;
124
+ await page.goto(moduleUrl, { waitUntil: 'networkidle', timeout: 15_000 });
125
+ const urlBeforeReload = page.url();
126
+ // Reload the page
127
+ await page.reload({ waitUntil: 'networkidle', timeout: 15_000 });
128
+ const urlAfterReload = page.url();
129
+ const reloadedToLogin = urlAfterReload.includes('/login') ||
130
+ urlAfterReload.includes('/auth') ||
131
+ urlAfterReload.includes('/signin');
132
+ if (reloadedToLogin) {
133
+ findings.push({
134
+ id: `${this.agentId}-refresh-${targetModule.id}`,
135
+ type: 'code-bug-logic',
136
+ severity: 'medium',
137
+ agentId: this.agentId,
138
+ module: targetModule.id,
139
+ description: `Refresh persistence: page reload on "${urlBeforeReload}" redirected to login (${urlAfterReload}). ` +
140
+ 'Session was not persisted across a page refresh.',
141
+ suggestedFix: 'Store the authentication token in a persistent storage (localStorage or httpOnly cookie) and restore session on reload.',
142
+ });
143
+ }
144
+ }
145
+ catch (error) {
146
+ findings.push({
147
+ id: `${this.agentId}-refresh-error`,
148
+ type: 'infra-issue',
149
+ severity: 'medium',
150
+ agentId: this.agentId,
151
+ module: this.config.modules[0]?.id ?? 'unknown',
152
+ description: `Refresh persistence scenario failed: ${error instanceof Error ? error.message : String(error)}`,
153
+ });
154
+ }
155
+ finally {
156
+ if (page)
157
+ await page.close().catch(() => undefined);
158
+ if (context)
159
+ await context.close().catch(() => undefined);
160
+ if (browser)
161
+ await browser.close().catch(() => undefined);
162
+ }
163
+ }
164
+ // -----------------------------------------------------------------------
165
+ // Scenario 3: Back/forward navigation — navigate 3 modules, go back 2
166
+ // -----------------------------------------------------------------------
167
+ if (this.config.modules.length >= 3) {
168
+ let browser = null;
169
+ let context = null;
170
+ let page = null;
171
+ try {
172
+ browser = await chromium.launch({ headless: true });
173
+ context = await browser.newContext();
174
+ page = await context.newPage();
175
+ // Login first
176
+ await page.goto(loginUrl, { waitUntil: 'networkidle', timeout: 20_000 });
177
+ await page
178
+ .fill('input[type="email"], input[name="email"]', credentials.email)
179
+ .catch(() => undefined);
180
+ await page
181
+ .fill('input[type="password"], input[name="password"]', credentials.password)
182
+ .catch(() => undefined);
183
+ await page.click('button[type="submit"]').catch(() => undefined);
184
+ await page
185
+ .waitForNavigation({ waitUntil: 'networkidle', timeout: 15_000 })
186
+ .catch(() => undefined);
187
+ const [mod1, mod2, mod3] = this.config.modules.slice(0, 3);
188
+ await page.goto(`${this.baseUrl}${mod1.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
189
+ await page.goto(`${this.baseUrl}${mod2.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
190
+ await page.goto(`${this.baseUrl}${mod3.route}`, { waitUntil: 'networkidle', timeout: 15_000 });
191
+ // Go back 2 pages — should land on mod1
192
+ await page.goBack({ waitUntil: 'networkidle', timeout: 10_000 });
193
+ await page.goBack({ waitUntil: 'networkidle', timeout: 10_000 });
194
+ const currentUrl = page.url();
195
+ const expectedRoute = mod1.route;
196
+ const landedOnCorrectModule = currentUrl.includes(expectedRoute) ||
197
+ currentUrl.endsWith(expectedRoute.replace(/^\//, ''));
198
+ if (!landedOnCorrectModule) {
199
+ findings.push({
200
+ id: `${this.agentId}-back-forward-${mod1.id}`,
201
+ type: 'code-bug-logic',
202
+ severity: 'low',
203
+ agentId: this.agentId,
204
+ module: mod1.id,
205
+ description: `Back/forward navigation: after navigating to modules [${mod1.id}, ${mod2.id}, ${mod3.id}] ` +
206
+ `and going back 2 times, expected to be on "${mod1.route}" but landed on "${currentUrl}".`,
207
+ suggestedFix: 'Ensure router history is correctly maintained across module transitions.',
208
+ });
209
+ }
210
+ }
211
+ catch (error) {
212
+ findings.push({
213
+ id: `${this.agentId}-back-forward-error`,
214
+ type: 'infra-issue',
215
+ severity: 'low',
216
+ agentId: this.agentId,
217
+ module: 'navigation',
218
+ description: `Back/forward scenario failed: ${error instanceof Error ? error.message : String(error)}`,
219
+ });
220
+ }
221
+ finally {
222
+ if (page)
223
+ await page.close().catch(() => undefined);
224
+ if (context)
225
+ await context.close().catch(() => undefined);
226
+ if (browser)
227
+ await browser.close().catch(() => undefined);
228
+ }
229
+ }
230
+ return findings;
231
+ }
232
+ }
233
+ //# sourceMappingURL=21-state-session-tester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"21-state-session-tester.js","sourceRoot":"","sources":["../../agents/21-state-session-tester.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Finding } from '../core/types';
2
+ import { BaseAgent } from './base-agent';
3
+ export declare class EmailNotificationVerifierAgent extends BaseAgent {
4
+ readonly agentId = 22;
5
+ readonly agentName = "Email & Notification Verifier";
6
+ /** Set during preFlight so execute() knows whether Mailhog is available. */
7
+ private mailhogAvailable;
8
+ protected preFlight(): Promise<void>;
9
+ protected execute(): Promise<Finding[]>;
10
+ }
11
+ //# sourceMappingURL=22-email-notification-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"22-email-notification-verifier.d.ts","sourceRoot":"","sources":["../../agents/22-email-notification-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AA6CzC,qBAAa,8BAA+B,SAAQ,SAAS;IAC3D,QAAQ,CAAC,OAAO,MAAM;IACtB,QAAQ,CAAC,SAAS,mCAAmC;IAErD,4EAA4E;IAC5E,OAAO,CAAC,gBAAgB,CAAS;cAEjB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;cAK1B,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAyL9C"}
@@ -0,0 +1,199 @@
1
+ import { BaseAgent } from './base-agent';
2
+ import { ApiClient } from '../helpers/api-client';
3
+ const MAILHOG_API_URL = 'http://localhost:8025/api/v2/messages?limit=10';
4
+ const MAILHOG_HEALTH_URL = 'http://localhost:8025';
5
+ async function isMailhogReachable() {
6
+ try {
7
+ const response = await fetch(MAILHOG_HEALTH_URL, {
8
+ method: 'HEAD',
9
+ signal: AbortSignal.timeout(5_000),
10
+ });
11
+ return response.ok || response.status < 500;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ async function fetchMailhogMessages() {
18
+ const response = await fetch(MAILHOG_API_URL, {
19
+ signal: AbortSignal.timeout(10_000),
20
+ });
21
+ if (!response.ok) {
22
+ throw new Error(`Mailhog API returned ${response.status}`);
23
+ }
24
+ const data = (await response.json());
25
+ return data.items ?? [];
26
+ }
27
+ export class EmailNotificationVerifierAgent extends BaseAgent {
28
+ agentId = 22;
29
+ agentName = 'Email & Notification Verifier';
30
+ /** Set during preFlight so execute() knows whether Mailhog is available. */
31
+ mailhogAvailable = false;
32
+ async preFlight() {
33
+ this.mailhogAvailable = await isMailhogReachable();
34
+ // Advisory only — Mailhog is optional in non-local environments
35
+ }
36
+ async execute() {
37
+ const findings = [];
38
+ const env = this.config.environments['beta'] ??
39
+ this.config.environments['alpha'] ??
40
+ Object.values(this.config.environments)[0];
41
+ if (!env) {
42
+ findings.push({
43
+ id: `${this.agentId}-no-env`,
44
+ type: 'infra-issue',
45
+ severity: 'critical',
46
+ agentId: this.agentId,
47
+ module: 'email',
48
+ description: 'No environment configured — cannot run email/notification checks',
49
+ });
50
+ return findings;
51
+ }
52
+ // -----------------------------------------------------------------------
53
+ // Advisory finding when Mailhog is unavailable
54
+ // -----------------------------------------------------------------------
55
+ if (!this.mailhogAvailable) {
56
+ findings.push({
57
+ id: `${this.agentId}-mailhog-unavailable`,
58
+ type: 'infra-issue',
59
+ severity: 'low',
60
+ agentId: this.agentId,
61
+ module: 'email',
62
+ description: 'Advisory: Mailhog is not reachable at localhost:8025. ' +
63
+ 'Email delivery verification will be skipped. ' +
64
+ 'Start Mailhog locally to enable full email testing.',
65
+ suggestedFix: 'Run `docker run -p 1025:1025 -p 8025:8025 mailhog/mailhog` before the test suite.',
66
+ });
67
+ return findings;
68
+ }
69
+ // -----------------------------------------------------------------------
70
+ // Step 1: Trigger a password reset to generate an email
71
+ // -----------------------------------------------------------------------
72
+ const testEmail = this.config.auth.credentials?.['admin']?.email ??
73
+ Object.values(this.config.auth.credentials ?? {})[0]?.email ??
74
+ 'test@example.com';
75
+ const apiClient = new ApiClient(env.baseUrl);
76
+ let passwordResetTriggered = false;
77
+ try {
78
+ const response = await apiClient.post('/api/auth/forgot-password', { email: testEmail });
79
+ if (response.status === 200 || response.status === 202 || response.status === 204) {
80
+ passwordResetTriggered = true;
81
+ }
82
+ else {
83
+ findings.push({
84
+ id: `${this.agentId}-password-reset-failed`,
85
+ type: 'infra-issue',
86
+ severity: 'medium',
87
+ agentId: this.agentId,
88
+ module: 'email',
89
+ description: `Password reset endpoint POST /api/auth/forgot-password returned ${response.status}. ` +
90
+ `Expected 200/202/204.`,
91
+ suggestedFix: 'Verify the forgot-password endpoint is implemented and returns a 2xx status.',
92
+ });
93
+ }
94
+ }
95
+ catch (error) {
96
+ findings.push({
97
+ id: `${this.agentId}-password-reset-error`,
98
+ type: 'infra-issue',
99
+ severity: 'medium',
100
+ agentId: this.agentId,
101
+ module: 'email',
102
+ description: `Password reset trigger failed: ${error instanceof Error ? error.message : String(error)}`,
103
+ });
104
+ }
105
+ if (!passwordResetTriggered) {
106
+ return findings;
107
+ }
108
+ // -----------------------------------------------------------------------
109
+ // Step 2: Poll Mailhog for the delivered email (up to 5 seconds)
110
+ // -----------------------------------------------------------------------
111
+ let messages = [];
112
+ const backoffDelays = [200, 400, 800, 1600, 3200];
113
+ for (const delay of backoffDelays) {
114
+ try {
115
+ messages = await fetchMailhogMessages();
116
+ if (messages.length > 0)
117
+ break;
118
+ }
119
+ catch {
120
+ // Retry on transient errors
121
+ }
122
+ await new Promise((resolve) => setTimeout(resolve, delay));
123
+ }
124
+ if (messages.length === 0) {
125
+ findings.push({
126
+ id: `${this.agentId}-email-not-delivered`,
127
+ type: 'code-bug-logic',
128
+ severity: 'high',
129
+ agentId: this.agentId,
130
+ module: 'email',
131
+ description: `Email delivery failure: no messages received in Mailhog within 5 seconds of ` +
132
+ `triggering password reset for "${testEmail}".`,
133
+ suggestedFix: 'Verify the mailer service is configured correctly and Mailhog SMTP port (1025) is reachable.',
134
+ });
135
+ return findings;
136
+ }
137
+ // -----------------------------------------------------------------------
138
+ // Step 3: Inspect email bodies for raw template variables like {{variable}}
139
+ // -----------------------------------------------------------------------
140
+ const templateVarPattern = /\{\{[^}]+\}\}/g;
141
+ for (const message of messages) {
142
+ const body = message.Content.Body;
143
+ const rawVars = body.match(templateVarPattern);
144
+ if (rawVars && rawVars.length > 0) {
145
+ findings.push({
146
+ id: `${this.agentId}-template-vars-${message.ID.slice(0, 8)}`,
147
+ type: 'code-bug-logic',
148
+ severity: 'high',
149
+ agentId: this.agentId,
150
+ module: 'email',
151
+ description: `Email template variable(s) not substituted: found ${rawVars.join(', ')} in email body (message ID: ${message.ID}).`,
152
+ suggestedFix: 'Check the email template renderer and ensure all variables are provided at send time.',
153
+ });
154
+ }
155
+ }
156
+ // -----------------------------------------------------------------------
157
+ // Step 4: Check that all links in the email body resolve (HTTP HEAD → 200)
158
+ // -----------------------------------------------------------------------
159
+ const urlPattern = /https?:\/\/[^\s"'<>)]+/g;
160
+ for (const message of messages) {
161
+ const body = message.Content.Body;
162
+ const urls = body.match(urlPattern) ?? [];
163
+ const uniqueUrls = [...new Set(urls)];
164
+ for (const url of uniqueUrls) {
165
+ try {
166
+ const response = await fetch(url, {
167
+ method: 'HEAD',
168
+ signal: AbortSignal.timeout(10_000),
169
+ redirect: 'follow',
170
+ });
171
+ if (response.status >= 400) {
172
+ findings.push({
173
+ id: `${this.agentId}-broken-link-${url.slice(0, 40).replace(/[^a-zA-Z0-9]/g, '-')}`,
174
+ type: 'code-bug-logic',
175
+ severity: 'medium',
176
+ agentId: this.agentId,
177
+ module: 'email',
178
+ description: `Broken link in email body: HEAD ${url} returned ${response.status}.`,
179
+ suggestedFix: `Update the email template to use a valid URL for: ${url}`,
180
+ });
181
+ }
182
+ }
183
+ catch (error) {
184
+ findings.push({
185
+ id: `${this.agentId}-link-error-${url.slice(0, 40).replace(/[^a-zA-Z0-9]/g, '-')}`,
186
+ type: 'infra-issue',
187
+ severity: 'low',
188
+ agentId: this.agentId,
189
+ module: 'email',
190
+ description: `Could not verify link in email body: ${url}. ` +
191
+ `Error: ${error instanceof Error ? error.message : String(error)}`,
192
+ });
193
+ }
194
+ }
195
+ }
196
+ return findings;
197
+ }
198
+ }
199
+ //# sourceMappingURL=22-email-notification-verifier.js.map