@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,609 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import type { Interface } from 'node:readline';
3
+
4
+ // ── Mock node:readline ────────────────────────────────────────────────────
5
+ const mockRlQuestion = vi.fn();
6
+ const mockRlOnce = vi.fn();
7
+ const mockRlClose = vi.fn();
8
+
9
+ const mockRl: Partial<Interface> = {
10
+ question: mockRlQuestion,
11
+ once: mockRlOnce,
12
+ close: mockRlClose,
13
+ };
14
+
15
+ vi.mock('node:readline', () => ({
16
+ createInterface: vi.fn(() => mockRl),
17
+ }));
18
+
19
+ // ── Mock node:fs ──────────────────────────────────────────────────────────
20
+ vi.mock('node:fs', () => ({
21
+ readFileSync: vi.fn(),
22
+ mkdirSync: vi.fn(),
23
+ writeFileSync: vi.fn(),
24
+ existsSync: vi.fn(),
25
+ }));
26
+
27
+ // ── Mock node:path (pass-through with posix-like join) ────────────────────
28
+ vi.mock('node:path', async () => {
29
+ const actual = await vi.importActual<typeof import('node:path')>('node:path');
30
+ return {
31
+ ...actual,
32
+ basename: actual.basename,
33
+ resolve: actual.resolve,
34
+ join: actual.join,
35
+ };
36
+ });
37
+
38
+ import { runInitWizard } from '../init';
39
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
40
+
41
+ // ── Helpers ───────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Queue a series of answers that will be fed to rl.question() calls in order.
45
+ * Each call to rl.question(prompt, callback) invokes callback with the next answer.
46
+ */
47
+ function queueAnswers(answers: string[]): void {
48
+ let idx = 0;
49
+ mockRlQuestion.mockImplementation((_prompt: string, cb: (answer: string) => void) => {
50
+ const answer = idx < answers.length ? answers[idx] : '';
51
+ idx++;
52
+ cb(answer);
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Standard set of answers for a complete happy-path wizard run:
58
+ * 1. Project name (default)
59
+ * 2. Framework (default)
60
+ * 3. Test runner (default)
61
+ * 4. Alpha URL
62
+ * 5. Beta URL (skip)
63
+ * 6. UAT URL (skip)
64
+ * 7. Auth method (default = form-login)
65
+ * 8. Multi-tenant (default = no)
66
+ * 9. Confirmation (yes)
67
+ */
68
+ function queueHappyPathAnswers(overrides: Partial<Record<number, string>> = {}): void {
69
+ const defaults = [
70
+ '', // 0: project name — use default
71
+ '', // 1: framework — use detected/default
72
+ '', // 2: test runner — use detected/default
73
+ 'http://localhost:3000', // 3: alpha URL
74
+ '', // 4: beta URL — skip
75
+ '', // 5: UAT URL — skip
76
+ '', // 6: auth method — default (form-login)
77
+ '', // 7: multi-tenant — default (no)
78
+ 'y', // 8: confirm
79
+ ];
80
+ for (const [i, v] of Object.entries(overrides)) {
81
+ defaults[Number(i)] = v ?? '';
82
+ }
83
+ queueAnswers(defaults);
84
+ }
85
+
86
+ beforeEach(() => {
87
+ vi.clearAllMocks();
88
+ // Default: no existing config, no package.json
89
+ vi.mocked(existsSync).mockReturnValue(false);
90
+ vi.mocked(readFileSync).mockReturnValue('{}');
91
+ // Suppress console output during tests
92
+ vi.spyOn(console, 'log').mockImplementation(() => {});
93
+ vi.spyOn(console, 'error').mockImplementation(() => {});
94
+ // Stub process.on / removeListener so SIGINT handler doesn't leak
95
+ vi.spyOn(process, 'on').mockImplementation(() => process);
96
+ vi.spyOn(process, 'removeListener').mockImplementation(() => process);
97
+ });
98
+
99
+ afterEach(() => {
100
+ vi.restoreAllMocks();
101
+ });
102
+
103
+ // ═══════════════════════════════════════════════════════════════════════════
104
+ // Tests
105
+ // ═══════════════════════════════════════════════════════════════════════════
106
+
107
+ describe('runInitWizard', () => {
108
+ // ── Happy path ────────────────────────────────────────────────────────
109
+
110
+ it('generates config and scaffolds directories on happy path', async () => {
111
+ queueHappyPathAnswers();
112
+
113
+ const exitCode = await runInitWizard();
114
+
115
+ expect(exitCode).toBe(0);
116
+ // Config file written
117
+ expect(writeFileSync).toHaveBeenCalledWith(
118
+ expect.stringContaining('testteam.config.ts'),
119
+ expect.stringContaining("schemaVersion: 1"),
120
+ 'utf-8',
121
+ );
122
+ // Directories scaffolded
123
+ expect(mkdirSync).toHaveBeenCalled();
124
+ });
125
+
126
+ it('writes config containing the project name', async () => {
127
+ queueHappyPathAnswers({ 0: 'my-cool-project' });
128
+
129
+ await runInitWizard();
130
+
131
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
132
+ (call) => String(call[0]).includes('testteam.config.ts'),
133
+ );
134
+ expect(configCall).toBeDefined();
135
+ expect(String(configCall![1])).toContain("name: 'my-cool-project'");
136
+ });
137
+
138
+ // ── Default values ────────────────────────────────────────────────────
139
+
140
+ it('uses directory name as default project name when no package.json', async () => {
141
+ vi.mocked(existsSync).mockReturnValue(false);
142
+ queueHappyPathAnswers();
143
+
144
+ const exitCode = await runInitWizard();
145
+
146
+ expect(exitCode).toBe(0);
147
+ // Config was written (project name derived from cwd basename)
148
+ expect(writeFileSync).toHaveBeenCalledWith(
149
+ expect.stringContaining('testteam.config.ts'),
150
+ expect.any(String),
151
+ 'utf-8',
152
+ );
153
+ });
154
+
155
+ it('detects framework from package.json dependencies', async () => {
156
+ // First existsSync call = config check (false), then package.json (true), then gitkeeps etc
157
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
158
+ const s = String(p);
159
+ if (s.includes('testteam.config.ts')) return false;
160
+ if (s.includes('package.json')) return true;
161
+ return false;
162
+ });
163
+ vi.mocked(readFileSync).mockReturnValue(
164
+ JSON.stringify({
165
+ name: 'my-next-app',
166
+ dependencies: { next: '14.0.0', react: '18.0.0' },
167
+ devDependencies: { vitest: '1.0.0' },
168
+ }),
169
+ );
170
+
171
+ queueHappyPathAnswers();
172
+
173
+ const exitCode = await runInitWizard();
174
+ expect(exitCode).toBe(0);
175
+ });
176
+
177
+ // ── Existing config guard ─────────────────────────────────────────────
178
+
179
+ it('returns 1 when config exists and user declines overwrite', async () => {
180
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
181
+ return String(p).includes('testteam.config.ts');
182
+ });
183
+
184
+ // Answer "no" to overwrite prompt
185
+ queueAnswers(['n']);
186
+
187
+ const exitCode = await runInitWizard();
188
+
189
+ expect(exitCode).toBe(1);
190
+ // Should NOT write config
191
+ const configWritten = vi.mocked(writeFileSync).mock.calls.some(
192
+ (call) => String(call[0]).includes('testteam.config.ts'),
193
+ );
194
+ expect(configWritten).toBe(false);
195
+ });
196
+
197
+ it('overwrites config when user confirms', async () => {
198
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
199
+ const s = String(p);
200
+ if (s.includes('testteam.config.ts')) return true;
201
+ if (s.includes('package.json')) return false;
202
+ return false;
203
+ });
204
+
205
+ // Answer "y" to overwrite, then normal happy path
206
+ queueAnswers([
207
+ 'y', // overwrite
208
+ '', // project name
209
+ '', // framework
210
+ '', // test runner
211
+ 'http://localhost:3000', // alpha
212
+ '', // beta
213
+ '', // uat
214
+ '', // auth
215
+ '', // multi-tenant
216
+ 'y', // confirm
217
+ ]);
218
+
219
+ const exitCode = await runInitWizard();
220
+ expect(exitCode).toBe(0);
221
+ expect(writeFileSync).toHaveBeenCalledWith(
222
+ expect.stringContaining('testteam.config.ts'),
223
+ expect.any(String),
224
+ 'utf-8',
225
+ );
226
+ });
227
+
228
+ // ── URL validation ────────────────────────────────────────────────────
229
+
230
+ it('rejects invalid alpha URL and retries', async () => {
231
+ // First alpha attempt = invalid, second = valid
232
+ queueAnswers([
233
+ '', // project name
234
+ '', // framework
235
+ '', // test runner
236
+ 'not-a-url', // invalid alpha URL
237
+ 'http://localhost:3000', // valid alpha URL (retry)
238
+ '', // beta
239
+ '', // uat
240
+ '', // auth
241
+ '', // multi-tenant
242
+ 'y', // confirm
243
+ ]);
244
+
245
+ const exitCode = await runInitWizard();
246
+ expect(exitCode).toBe(0);
247
+ expect(console.log).toHaveBeenCalledWith(
248
+ expect.stringContaining('Invalid URL'),
249
+ );
250
+ });
251
+
252
+ it('skips beta URL when invalid', async () => {
253
+ queueAnswers([
254
+ '', // project name
255
+ '', // framework
256
+ '', // test runner
257
+ 'http://localhost:3000', // alpha
258
+ 'not-a-url', // invalid beta — skipped
259
+ '', // uat
260
+ '', // auth
261
+ '', // multi-tenant
262
+ 'y', // confirm
263
+ ]);
264
+
265
+ const exitCode = await runInitWizard();
266
+ expect(exitCode).toBe(0);
267
+ expect(console.log).toHaveBeenCalledWith(
268
+ expect.stringContaining('Invalid URL — skipping beta'),
269
+ );
270
+ });
271
+
272
+ it('skips UAT URL when invalid', async () => {
273
+ queueAnswers([
274
+ '', // project name
275
+ '', // framework
276
+ '', // test runner
277
+ 'http://localhost:3000', // alpha
278
+ '', // beta
279
+ 'bad-uat', // invalid UAT — skipped
280
+ '', // auth
281
+ '', // multi-tenant
282
+ 'y', // confirm
283
+ ]);
284
+
285
+ const exitCode = await runInitWizard();
286
+ expect(exitCode).toBe(0);
287
+ expect(console.log).toHaveBeenCalledWith(
288
+ expect.stringContaining('Invalid URL — skipping UAT'),
289
+ );
290
+ });
291
+
292
+ // ── Beta and UAT URLs included ────────────────────────────────────────
293
+
294
+ it('includes beta and UAT environments in config when provided', async () => {
295
+ queueAnswers([
296
+ 'my-project',
297
+ '', // framework
298
+ '', // test runner
299
+ 'http://localhost:3000', // alpha
300
+ 'https://beta.example.com', // beta
301
+ 'https://uat.example.com', // UAT
302
+ '', // auth
303
+ '', // multi-tenant
304
+ 'y', // confirm
305
+ ]);
306
+
307
+ await runInitWizard();
308
+
309
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
310
+ (call) => String(call[0]).includes('testteam.config.ts'),
311
+ );
312
+ const content = String(configCall![1]);
313
+ expect(content).toContain('beta.example.com');
314
+ expect(content).toContain('uat.example.com');
315
+ });
316
+
317
+ // ── Auth methods ──────────────────────────────────────────────────────
318
+
319
+ it('generates form-login auth block', async () => {
320
+ queueHappyPathAnswers({ 6: 'form-login' });
321
+
322
+ await runInitWizard();
323
+
324
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
325
+ (call) => String(call[0]).includes('testteam.config.ts'),
326
+ );
327
+ const content = String(configCall![1]);
328
+ expect(content).toContain('loginUrl');
329
+ expect(content).toContain('TF_ADMIN_EMAIL');
330
+ });
331
+
332
+ it('generates api-token auth block', async () => {
333
+ queueHappyPathAnswers({ 6: 'api-token' });
334
+
335
+ await runInitWizard();
336
+
337
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
338
+ (call) => String(call[0]).includes('testteam.config.ts'),
339
+ );
340
+ const content = String(configCall![1]);
341
+ expect(content).toContain('TF_API_TOKEN');
342
+ expect(content).not.toContain('loginUrl');
343
+ });
344
+
345
+ it('generates none auth block', async () => {
346
+ queueHappyPathAnswers({ 6: 'none' });
347
+
348
+ await runInitWizard();
349
+
350
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
351
+ (call) => String(call[0]).includes('testteam.config.ts'),
352
+ );
353
+ const content = String(configCall![1]);
354
+ expect(content).toContain('credentials: {}');
355
+ });
356
+
357
+ // ── Multi-tenant ──────────────────────────────────────────────────────
358
+
359
+ it('generates multi-tenant block when enabled', async () => {
360
+ queueHappyPathAnswers({ 7: 'y' });
361
+
362
+ await runInitWizard();
363
+
364
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
365
+ (call) => String(call[0]).includes('testteam.config.ts'),
366
+ );
367
+ const content = String(configCall![1]);
368
+ expect(content).toContain('enabled: true');
369
+ expect(content).toContain("'firm-a'");
370
+ });
371
+
372
+ it('generates single-tenant block when disabled', async () => {
373
+ queueHappyPathAnswers({ 7: 'n' });
374
+
375
+ await runInitWizard();
376
+
377
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
378
+ (call) => String(call[0]).includes('testteam.config.ts'),
379
+ );
380
+ const content = String(configCall![1]);
381
+ expect(content).toContain('enabled: false');
382
+ expect(content).toContain('testFirms: []');
383
+ });
384
+
385
+ // ── Confirmation declined ─────────────────────────────────────────────
386
+
387
+ it('returns 1 when user declines final confirmation', async () => {
388
+ queueAnswers([
389
+ '', // project name
390
+ '', // framework
391
+ '', // test runner
392
+ 'http://localhost:3000', // alpha
393
+ '', // beta
394
+ '', // uat
395
+ '', // auth
396
+ '', // multi-tenant
397
+ 'n', // decline confirmation
398
+ ]);
399
+
400
+ const exitCode = await runInitWizard();
401
+ expect(exitCode).toBe(1);
402
+ });
403
+
404
+ // ── Cancellation / CancelledError ─────────────────────────────────────
405
+
406
+ it('returns 130 when readline closes (CancelledError)', async () => {
407
+ // Simulate rl.close being called during a question
408
+ mockRlQuestion.mockImplementation((_prompt: string, _cb: (answer: string) => void) => {
409
+ // Don't call cb — instead trigger the close listener
410
+ });
411
+ mockRlOnce.mockImplementation((_event: string, handler: () => void) => {
412
+ // Immediately fire close
413
+ handler();
414
+ });
415
+
416
+ const exitCode = await runInitWizard();
417
+ expect(exitCode).toBe(130);
418
+ });
419
+
420
+ // ── Scaffold directories ──────────────────────────────────────────────
421
+
422
+ it('creates baseline directories and .gitkeep files', async () => {
423
+ queueHappyPathAnswers();
424
+
425
+ await runInitWizard();
426
+
427
+ // mkdirSync should be called for each directory
428
+ expect(mkdirSync).toHaveBeenCalledTimes(6);
429
+ expect(mkdirSync).toHaveBeenCalledWith(
430
+ expect.stringContaining('baselines'),
431
+ { recursive: true },
432
+ );
433
+ expect(mkdirSync).toHaveBeenCalledWith(
434
+ expect.stringContaining('reports'),
435
+ { recursive: true },
436
+ );
437
+ });
438
+
439
+ it('does not overwrite existing .gitkeep files', async () => {
440
+ // Config doesn't exist, but gitkeep files do
441
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
442
+ const s = String(p);
443
+ if (s.includes('testteam.config.ts')) return false;
444
+ if (s.includes('package.json')) return false;
445
+ if (s.includes('.gitkeep')) return true;
446
+ if (s.includes('.gitignore')) return true;
447
+ return false;
448
+ });
449
+
450
+ queueHappyPathAnswers();
451
+
452
+ await runInitWizard();
453
+
454
+ // .gitkeep files should NOT be written (they already exist)
455
+ const gitkeepWrites = vi.mocked(writeFileSync).mock.calls.filter(
456
+ (call) => String(call[0]).includes('.gitkeep'),
457
+ );
458
+ expect(gitkeepWrites).toHaveLength(0);
459
+ });
460
+
461
+ // ── Framework detection ───────────────────────────────────────────────
462
+
463
+ it('detects NestJS framework', async () => {
464
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
465
+ const s = String(p);
466
+ if (s.includes('testteam.config.ts')) return false;
467
+ if (s.includes('package.json')) return true;
468
+ return false;
469
+ });
470
+ vi.mocked(readFileSync).mockReturnValue(
471
+ JSON.stringify({
472
+ name: 'nest-api',
473
+ dependencies: { '@nestjs/core': '10.0.0' },
474
+ }),
475
+ );
476
+
477
+ queueHappyPathAnswers();
478
+ await runInitWizard();
479
+ // No assertion needed on framework detection beyond exit code 0
480
+ // The detection is an internal default; we just verify no crash.
481
+ expect(writeFileSync).toHaveBeenCalled();
482
+ });
483
+
484
+ it('detects jest test runner', async () => {
485
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
486
+ const s = String(p);
487
+ if (s.includes('testteam.config.ts')) return false;
488
+ if (s.includes('package.json')) return true;
489
+ return false;
490
+ });
491
+ vi.mocked(readFileSync).mockReturnValue(
492
+ JSON.stringify({
493
+ name: 'jest-app',
494
+ devDependencies: { jest: '29.0.0' },
495
+ }),
496
+ );
497
+
498
+ queueHappyPathAnswers();
499
+ await runInitWizard();
500
+ expect(writeFileSync).toHaveBeenCalled();
501
+ });
502
+
503
+ // ── Handles malformed package.json ────────────────────────────────────
504
+
505
+ it('handles malformed package.json gracefully', async () => {
506
+ vi.mocked(existsSync).mockImplementation((p: unknown) => {
507
+ const s = String(p);
508
+ if (s.includes('testteam.config.ts')) return false;
509
+ if (s.includes('package.json')) return true;
510
+ return false;
511
+ });
512
+ vi.mocked(readFileSync).mockReturnValue('not-valid-json{{{');
513
+
514
+ queueHappyPathAnswers();
515
+
516
+ const exitCode = await runInitWizard();
517
+ expect(exitCode).toBe(0);
518
+ // Should have warned about missing package.json (parsing failed)
519
+ expect(console.log).toHaveBeenCalledWith(
520
+ expect.stringContaining('Warning'),
521
+ );
522
+ });
523
+
524
+ // ── Config file content structure ─────────────────────────────────────
525
+
526
+ it('generated config includes all expected sections', async () => {
527
+ queueHappyPathAnswers();
528
+
529
+ await runInitWizard();
530
+
531
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
532
+ (call) => String(call[0]).includes('testteam.config.ts'),
533
+ );
534
+ const content = String(configCall![1]);
535
+
536
+ expect(content).toContain("import type { TestTeamConfig }");
537
+ expect(content).toContain('schemaVersion: 1');
538
+ expect(content).toContain('environments:');
539
+ expect(content).toContain('modules:');
540
+ expect(content).toContain('portals:');
541
+ expect(content).toContain('breakpoints:');
542
+ expect(content).toContain('accuracy:');
543
+ expect(content).toContain('tenancy:');
544
+ expect(content).toContain('costBudget:');
545
+ expect(content).toContain('agentClassification:');
546
+ expect(content).toContain('thresholds:');
547
+ expect(content).toContain('disabledAgents:');
548
+ expect(content).toContain('export default config');
549
+ });
550
+
551
+ // ── Escaping special characters ───────────────────────────────────────
552
+
553
+ it('escapes single quotes in project name', async () => {
554
+ queueHappyPathAnswers({ 0: "my project's app" });
555
+
556
+ await runInitWizard();
557
+
558
+ const configCall = vi.mocked(writeFileSync).mock.calls.find(
559
+ (call) => String(call[0]).includes('testteam.config.ts'),
560
+ );
561
+ const content = String(configCall![1]);
562
+ expect(content).toContain("my project\\'s app");
563
+ });
564
+
565
+ // ── Next steps output ─────────────────────────────────────────────────
566
+
567
+ it('prints next steps for form-login auth', async () => {
568
+ queueHappyPathAnswers({ 6: 'form-login' });
569
+
570
+ await runInitWizard();
571
+
572
+ expect(console.log).toHaveBeenCalledWith(
573
+ expect.stringContaining('TF_ADMIN_EMAIL'),
574
+ );
575
+ });
576
+
577
+ it('prints next steps for api-token auth', async () => {
578
+ queueHappyPathAnswers({ 6: 'api-token' });
579
+
580
+ await runInitWizard();
581
+
582
+ expect(console.log).toHaveBeenCalledWith(
583
+ expect.stringContaining('TF_API_TOKEN'),
584
+ );
585
+ });
586
+
587
+ // ── Partial match in promptChoice ─────────────────────────────────────
588
+
589
+ it('accepts partial framework input (e.g., "nes" for "nestjs")', async () => {
590
+ queueHappyPathAnswers({ 1: 'nes' });
591
+
592
+ const exitCode = await runInitWizard();
593
+ expect(exitCode).toBe(0);
594
+ });
595
+
596
+ // ── .gitignore creation ───────────────────────────────────────────────
597
+
598
+ it('creates .gitignore in testteam directory', async () => {
599
+ queueHappyPathAnswers();
600
+
601
+ await runInitWizard();
602
+
603
+ const gitignoreCall = vi.mocked(writeFileSync).mock.calls.find(
604
+ (call) => String(call[0]).includes('.gitignore'),
605
+ );
606
+ expect(gitignoreCall).toBeDefined();
607
+ expect(String(gitignoreCall![1])).toContain('reports/*.html');
608
+ });
609
+ });