@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,387 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { join } from 'path';
3
+ import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
4
+
5
+ // ── Shared mock state ──────────────────────────────────────────────────────
6
+
7
+ const mockDryRun = vi.fn();
8
+ const mockRunPhase = vi.fn();
9
+ const mockRunInitWizard = vi.fn();
10
+
11
+ // ── Module mocks ───────────────────────────────────────────────────────────
12
+
13
+ vi.mock('../init', () => ({
14
+ runInitWizard: (...args: unknown[]) => mockRunInitWizard(...args),
15
+ }));
16
+
17
+ vi.mock('../config', () => ({
18
+ validateConfig: vi.fn((c: unknown) => ({
19
+ ...(c as Record<string, unknown>),
20
+ disabledAgentSet: new Set(),
21
+ })),
22
+ }));
23
+
24
+ vi.mock('../orchestrator', () => {
25
+ return {
26
+ Orchestrator: class MockOrchestrator {
27
+ dryRun(...args: unknown[]) { return mockDryRun(...args); }
28
+ runPhase(...args: unknown[]) { return mockRunPhase(...args); }
29
+ },
30
+ };
31
+ });
32
+
33
+ vi.mock('../../agents/registry', () => ({
34
+ registerAllAgents: vi.fn(),
35
+ }));
36
+
37
+ vi.mock('../ci-output', () => ({
38
+ determineExitCode: vi.fn().mockReturnValue(0),
39
+ writeCISummary: vi.fn(),
40
+ detectCIEnvironment: vi.fn().mockReturnValue('none'),
41
+ EXIT_CODE: { SUCCESS: 0, BLOCKING_FAILURE: 1, ADVISORY_WARNINGS: 2 },
42
+ }));
43
+
44
+ import { parseArgs, main } from '../cli';
45
+ import { determineExitCode, detectCIEnvironment, writeCISummary } from '../ci-output';
46
+
47
+ // ── Create a temp config file that dynamic import() can actually load ──────
48
+
49
+ const TEMP_DIR = join(__dirname, '__temp_cli_test__');
50
+ let TEMP_CONFIG = '';
51
+ let configCounter = 0;
52
+
53
+ function ensureTempConfig(): void {
54
+ if (!existsSync(TEMP_DIR)) {
55
+ mkdirSync(TEMP_DIR, { recursive: true });
56
+ }
57
+ // Use a unique filename per test to bust Node's ESM import cache
58
+ configCounter++;
59
+ TEMP_CONFIG = join(TEMP_DIR, `test-${configCounter}-${Date.now()}.config.mjs`);
60
+ writeFileSync(
61
+ TEMP_CONFIG,
62
+ `export default {
63
+ schemaVersion: 1,
64
+ name: 'test',
65
+ modules: [{ id: 'home', route: '/', sidebarIcon: true }],
66
+ environments: { alpha: { baseUrl: 'http://localhost:3000' } },
67
+ auth: { credentials: {}, roles: [] },
68
+ };`,
69
+ 'utf-8',
70
+ );
71
+ }
72
+
73
+ function cleanupTempConfig(): void {
74
+ try {
75
+ rmSync(TEMP_DIR, { recursive: true, force: true });
76
+ } catch {
77
+ // ignore cleanup errors
78
+ }
79
+ }
80
+
81
+ // Default mock implementations — re-applied each test since clearAllMocks resets them
82
+ function setupDefaultMocks(): void {
83
+ mockRunInitWizard.mockResolvedValue(0);
84
+ mockDryRun.mockReturnValue([
85
+ { agentId: 1, status: 'scheduled' },
86
+ ]);
87
+ mockRunPhase.mockResolvedValue({
88
+ skippedAgents: [],
89
+ agentResults: { 1: { agentId: 1, status: 'passed' } },
90
+ gate: { passed: true, failedBlockingAgents: [] },
91
+ });
92
+ // These are defined via vi.mock factory but clearAllMocks resets them
93
+ vi.mocked(determineExitCode).mockReturnValue(0);
94
+ vi.mocked(detectCIEnvironment).mockReturnValue('none');
95
+ }
96
+
97
+ beforeEach(() => {
98
+ vi.clearAllMocks();
99
+ setupDefaultMocks();
100
+ vi.spyOn(console, 'log').mockImplementation(() => {});
101
+ vi.spyOn(console, 'error').mockImplementation(() => {});
102
+ // Prevent the module-level auto-execution from calling process.exit
103
+ vi.spyOn(process, 'exit').mockImplementation((() => {}) as never);
104
+ ensureTempConfig();
105
+ });
106
+
107
+ afterEach(() => {
108
+ vi.restoreAllMocks();
109
+ cleanupTempConfig();
110
+ });
111
+
112
+ // Helper to build argv with our temp config
113
+ function argv(...args: string[]): string[] {
114
+ return ['node', 'cli.ts', ...args, '--config', TEMP_CONFIG];
115
+ }
116
+
117
+ // ═══════════════════════════════════════════════════════════════════════════
118
+ // parseArgs
119
+ // ═══════════════════════════════════════════════════════════════════════════
120
+
121
+ describe('parseArgs', () => {
122
+ it('parses --phase alpha', () => {
123
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'alpha']);
124
+ expect(result.phase).toBe('alpha');
125
+ expect(result.dryRun).toBe(false);
126
+ expect(result.agent).toBeNull();
127
+ });
128
+
129
+ it('parses --phase beta', () => {
130
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'beta']);
131
+ expect(result.phase).toBe('beta');
132
+ });
133
+
134
+ it('parses --phase uat', () => {
135
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'uat']);
136
+ expect(result.phase).toBe('uat');
137
+ });
138
+
139
+ it('parses --phase release', () => {
140
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'release']);
141
+ expect(result.phase).toBe('release');
142
+ });
143
+
144
+ it('parses --phase all', () => {
145
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'all']);
146
+ expect(result.phase).toBe('all');
147
+ });
148
+
149
+ it('parses --dry-run flag', () => {
150
+ const result = parseArgs(['node', 'cli.ts', '--dry-run']);
151
+ expect(result.dryRun).toBe(true);
152
+ });
153
+
154
+ it('parses --agent flag with valid ID', () => {
155
+ const result = parseArgs(['node', 'cli.ts', '--agent', '5']);
156
+ expect(result.agent).toBe(5);
157
+ });
158
+
159
+ it('parses --config flag', () => {
160
+ const result = parseArgs(['node', 'cli.ts', '--config', 'custom.config.ts']);
161
+ expect(result.configPath).toBe('custom.config.ts');
162
+ });
163
+
164
+ it('parses combined flags', () => {
165
+ const result = parseArgs([
166
+ 'node', 'cli.ts',
167
+ '--phase', 'alpha',
168
+ '--dry-run',
169
+ '--agent', '3',
170
+ '--config', 'my.config.ts',
171
+ ]);
172
+ expect(result.phase).toBe('alpha');
173
+ expect(result.dryRun).toBe(true);
174
+ expect(result.agent).toBe(3);
175
+ expect(result.configPath).toBe('my.config.ts');
176
+ });
177
+
178
+ it('defaults configPath to testteam.config.ts', () => {
179
+ const result = parseArgs(['node', 'cli.ts', '--phase', 'alpha']);
180
+ expect(result.configPath).toBe('testteam.config.ts');
181
+ });
182
+
183
+ it('defaults phase to null', () => {
184
+ const result = parseArgs(['node', 'cli.ts', '--dry-run']);
185
+ expect(result.phase).toBeNull();
186
+ });
187
+
188
+ // ── Error cases ─────────────────────────────────────────────────────
189
+
190
+ it('throws on invalid phase', () => {
191
+ expect(() => parseArgs(['node', 'cli.ts', '--phase', 'invalid']))
192
+ .toThrow('Invalid phase: invalid');
193
+ });
194
+
195
+ it('throws on missing phase value', () => {
196
+ expect(() => parseArgs(['node', 'cli.ts', '--phase']))
197
+ .toThrow('Invalid phase');
198
+ });
199
+
200
+ it('throws on invalid agent ID (non-number)', () => {
201
+ expect(() => parseArgs(['node', 'cli.ts', '--agent', 'abc']))
202
+ .toThrow('Invalid agent ID: abc');
203
+ });
204
+
205
+ it('throws on invalid agent ID (zero)', () => {
206
+ expect(() => parseArgs(['node', 'cli.ts', '--agent', '0']))
207
+ .toThrow('Invalid agent ID: 0');
208
+ });
209
+
210
+ it('throws on invalid agent ID (negative)', () => {
211
+ expect(() => parseArgs(['node', 'cli.ts', '--agent', '-1']))
212
+ .toThrow('Invalid agent ID: -1');
213
+ });
214
+
215
+ it('throws on missing agent value', () => {
216
+ expect(() => parseArgs(['node', 'cli.ts', '--agent']))
217
+ .toThrow('Invalid agent ID');
218
+ });
219
+
220
+ it('throws on missing config path', () => {
221
+ expect(() => parseArgs(['node', 'cli.ts', '--config']))
222
+ .toThrow('--config requires a path');
223
+ });
224
+
225
+ it('throws on unknown argument', () => {
226
+ expect(() => parseArgs(['node', 'cli.ts', '--unknown']))
227
+ .toThrow('Unknown argument: --unknown');
228
+ });
229
+ });
230
+
231
+ // ═══════════════════════════════════════════════════════════════════════════
232
+ // main
233
+ // ═══════════════════════════════════════════════════════════════════════════
234
+
235
+ describe('main', () => {
236
+ // ── Init command ──────────────────────────────────────────────────────
237
+
238
+ it('routes to init wizard when "init" is in args', async () => {
239
+ const code = await main(['node', 'cli.ts', 'init']);
240
+ expect(mockRunInitWizard).toHaveBeenCalled();
241
+ expect(code).toBe(0);
242
+ });
243
+
244
+ it('routes to init wizard when "--init" is in args', async () => {
245
+ const code = await main(['node', 'cli.ts', '--init']);
246
+ expect(mockRunInitWizard).toHaveBeenCalled();
247
+ expect(code).toBe(0);
248
+ });
249
+
250
+ // ── No arguments ──────────────────────────────────────────────────────
251
+
252
+ it('prints usage and returns 1 when no arguments', async () => {
253
+ const code = await main(['node', 'cli.ts']);
254
+ expect(code).toBe(1);
255
+ });
256
+
257
+ // ── Missing phase ─────────────────────────────────────────────────────
258
+
259
+ it('returns 1 when phase is missing and not dry-run', async () => {
260
+ const code = await main(['node', 'cli.ts', '--agent', '1', '--config', TEMP_CONFIG]);
261
+ expect(code).toBe(1);
262
+ expect(console.error).toHaveBeenCalledWith(
263
+ expect.stringContaining('--phase is required'),
264
+ );
265
+ });
266
+
267
+ // ── Invalid args ──────────────────────────────────────────────────────
268
+
269
+ it('returns 1 on invalid args', async () => {
270
+ const code = await main(['node', 'cli.ts', '--phase', 'bogus']);
271
+ expect(code).toBe(1);
272
+ expect(console.error).toHaveBeenCalledWith(
273
+ expect.stringContaining('Invalid phase'),
274
+ );
275
+ });
276
+
277
+ // ── Dry-run mode ──────────────────────────────────────────────────────
278
+
279
+ it('dry-run without phase runs all phases and returns 0', async () => {
280
+ const code = await main(['node', 'cli.ts', '--dry-run', '--config', TEMP_CONFIG]);
281
+ expect(code).toBe(0);
282
+ expect(mockDryRun).toHaveBeenCalledTimes(4);
283
+ });
284
+
285
+ it('dry-run with specific phase runs only that phase', async () => {
286
+ const code = await main(argv('--dry-run', '--phase', 'alpha'));
287
+ expect(code).toBe(0);
288
+ expect(mockDryRun).toHaveBeenCalledTimes(1);
289
+ expect(mockDryRun).toHaveBeenCalledWith('alpha');
290
+ });
291
+
292
+ it('dry-run with --phase all runs all phases', async () => {
293
+ const code = await main(argv('--dry-run', '--phase', 'all'));
294
+ expect(code).toBe(0);
295
+ expect(mockDryRun).toHaveBeenCalledTimes(4);
296
+ });
297
+
298
+ // ── Run mode ──────────────────────────────────────────────────────────
299
+
300
+ it('runs single phase and returns 0 on success', async () => {
301
+ const code = await main(argv('--phase', 'alpha'));
302
+ expect(mockRunPhase).toHaveBeenCalledWith('alpha');
303
+ expect(code).toBe(0);
304
+ });
305
+
306
+ it('runs all phases when --phase all', async () => {
307
+ const code = await main(argv('--phase', 'all'));
308
+ expect(code).toBe(0);
309
+ expect(mockRunPhase).toHaveBeenCalledTimes(4);
310
+ });
311
+
312
+ it('logs skipped agents when present', async () => {
313
+ mockRunPhase.mockResolvedValueOnce({
314
+ skippedAgents: [16, 17],
315
+ agentResults: {},
316
+ gate: { passed: true, failedBlockingAgents: [] },
317
+ });
318
+
319
+ await main(argv('--phase', 'alpha'));
320
+
321
+ expect(console.log).toHaveBeenCalledWith(
322
+ expect.stringContaining('Skipped agents: 16, 17'),
323
+ );
324
+ });
325
+
326
+ it('logs gate FAILED with blocking agents', async () => {
327
+ mockRunPhase.mockResolvedValueOnce({
328
+ skippedAgents: [],
329
+ agentResults: { 1: { agentId: 1, status: 'failed' } },
330
+ gate: { passed: false, failedBlockingAgents: [1, 3] },
331
+ });
332
+
333
+ await main(argv('--phase', 'alpha'));
334
+
335
+ expect(console.log).toHaveBeenCalledWith(
336
+ expect.stringContaining('Gate: FAILED'),
337
+ );
338
+ });
339
+
340
+ // ── Exit codes ────────────────────────────────────────────────────────
341
+
342
+ it('returns exit code 1 when phase throws error', async () => {
343
+ mockRunPhase.mockRejectedValueOnce(new Error('boom'));
344
+
345
+ const code = await main(argv('--phase', 'alpha'));
346
+
347
+ expect(code).toBe(1);
348
+ expect(console.error).toHaveBeenCalledWith(
349
+ expect.stringContaining('Phase alpha error: boom'),
350
+ );
351
+ });
352
+
353
+ it('returns exit code 2 for advisory warnings', async () => {
354
+ vi.mocked(determineExitCode).mockReturnValueOnce(2);
355
+
356
+ const code = await main(argv('--phase', 'alpha'));
357
+ expect(code).toBe(2);
358
+ });
359
+
360
+ // ── CI summary ────────────────────────────────────────────────────────
361
+
362
+ it('writes CI summary when running in CI environment', async () => {
363
+ vi.mocked(detectCIEnvironment).mockReturnValueOnce('github');
364
+
365
+ await main(argv('--phase', 'alpha'));
366
+
367
+ expect(writeCISummary).toHaveBeenCalled();
368
+ });
369
+
370
+ it('does not write CI summary when not in CI', async () => {
371
+ vi.mocked(detectCIEnvironment).mockReturnValueOnce('none');
372
+
373
+ await main(argv('--phase', 'alpha'));
374
+
375
+ expect(writeCISummary).not.toHaveBeenCalled();
376
+ });
377
+
378
+ // ── Config loading failure ────────────────────────────────────────────
379
+
380
+ it('returns 1 when config file fails to load', async () => {
381
+ const code = await main(['node', 'cli.ts', '--phase', 'alpha', '--config', 'nonexistent-config-xyz.ts']);
382
+ expect(code).toBe(1);
383
+ expect(console.error).toHaveBeenCalledWith(
384
+ expect.stringContaining('Failed to load config'),
385
+ );
386
+ });
387
+ });
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { TestTeamConfig } from '../config';
3
+ import { validateConfig } from '../config';
4
+
5
+ describe('TestTeamConfig', () => {
6
+ it('rejects config missing schemaVersion', () => {
7
+ const invalid = { name: 'test' } as unknown as TestTeamConfig;
8
+ expect(() => validateConfig(invalid)).toThrow('schemaVersion is required');
9
+ });
10
+
11
+ it('rejects config with empty modules array', () => {
12
+ const invalid = { schemaVersion: 1, name: 'test', modules: [] } as unknown as TestTeamConfig;
13
+ expect(() => validateConfig(invalid)).toThrow('at least one module');
14
+ });
15
+
16
+ it('accepts credentials with non-empty passwords', () => {
17
+ const valid = {
18
+ schemaVersion: 1,
19
+ name: 'test',
20
+ auth: { credentials: { alpha: { email: 'a@b.com', password: 'literal' } } },
21
+ } as unknown as TestTeamConfig;
22
+ expect(() => validateConfig(valid)).not.toThrow();
23
+ });
24
+
25
+ it('throws when ALL credentials have empty passwords', () => {
26
+ const invalid = {
27
+ schemaVersion: 1,
28
+ name: 'test',
29
+ auth: { credentials: { alpha: { email: 'a@b.com', password: '' }, beta: { email: 'b@b.com', password: '' } } },
30
+ modules: [{ id: 'home', route: '/', sidebarIcon: true }],
31
+ } as unknown as TestTeamConfig;
32
+ expect(() => validateConfig(invalid)).toThrow('All credentials have empty passwords');
33
+ });
34
+
35
+ it('accepts valid complete config', () => {
36
+ const valid = createValidConfig();
37
+ expect(() => validateConfig(valid)).not.toThrow();
38
+ });
39
+
40
+ it('returns disabled agents as a Set for O(1) lookup', () => {
41
+ const config = createValidConfig({ disabledAgents: [16, 17] });
42
+ const validated = validateConfig(config);
43
+ expect(validated.disabledAgentSet.has(16)).toBe(true);
44
+ expect(validated.disabledAgentSet.has(1)).toBe(false);
45
+ });
46
+ });
47
+
48
+ function createValidConfig(overrides = {}) {
49
+ return {
50
+ schemaVersion: 1,
51
+ name: 'test-project',
52
+ auth: {
53
+ loginUrl: '/login',
54
+ credentials: { alpha: { email: 'test@test.com', password: 'pass' } },
55
+ roles: [{ name: 'admin', email: 'admin@test.com' }],
56
+ },
57
+ modules: [{ id: 'home', route: '/', sidebarIcon: true }],
58
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: true } },
59
+ portals: [{ name: 'main', queryParam: null }],
60
+ breakpoints: [1920],
61
+ integrations: [],
62
+ workers: [],
63
+ workflows: [],
64
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: 'USD', timezone: 'UTC' },
65
+ tenancy: { enabled: false, isolationField: 'tenantId', testFirms: [] },
66
+ costBudget: { maxPerRun: 50, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' as const },
67
+ agentClassification: { blocking: [1], advisory: [] },
68
+ thresholds: {
69
+ unitCoverage: { lines: 80, functions: 80, branches: 70, statements: 80 },
70
+ lighthouse: { accessibility: 90, performance: 85, bestPractices: 85, seo: 80 },
71
+ apiResponseTime: 500,
72
+ bundleSizeLimit: 500,
73
+ maxConsoleErrors: 0,
74
+ },
75
+ disabledAgents: [],
76
+ ...overrides,
77
+ };
78
+ }
@@ -0,0 +1,158 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { CostTracker } from '../cost-tracker';
3
+ import type { CostBudget } from '../cost-tracker';
4
+
5
+ const defaultBudget: CostBudget = {
6
+ maxPerRun: 50,
7
+ maxPerAgent: 10,
8
+ alertThreshold: 0.8,
9
+ onExceeded: 'abort',
10
+ };
11
+
12
+ describe('CostTracker', () => {
13
+ let tracker: CostTracker;
14
+
15
+ beforeEach(() => {
16
+ tracker = new CostTracker(defaultBudget);
17
+ });
18
+
19
+ describe('constructor', () => {
20
+ it('accepts a CostBudget config', () => {
21
+ expect(tracker).toBeDefined();
22
+ });
23
+
24
+ it('starts with zero total cost', () => {
25
+ expect(tracker.getTotalCost()).toBe(0);
26
+ });
27
+ });
28
+
29
+ describe('trackCost(agentId, amount)', () => {
30
+ it('accumulates cost for a single agent', () => {
31
+ tracker.trackCost('agent-1', 3);
32
+ tracker.trackCost('agent-1', 2);
33
+ expect(tracker.getAgentCost('agent-1')).toBe(5);
34
+ });
35
+
36
+ it('accumulates total cost across multiple agents', () => {
37
+ tracker.trackCost('agent-1', 5);
38
+ tracker.trackCost('agent-2', 7);
39
+ expect(tracker.getTotalCost()).toBe(12);
40
+ });
41
+
42
+ it('tracks costs per agent independently', () => {
43
+ tracker.trackCost('agent-1', 4);
44
+ tracker.trackCost('agent-2', 6);
45
+ expect(tracker.getAgentCost('agent-1')).toBe(4);
46
+ expect(tracker.getAgentCost('agent-2')).toBe(6);
47
+ });
48
+ });
49
+
50
+ describe('getTotalCost()', () => {
51
+ it('returns 0 with no costs tracked', () => {
52
+ expect(tracker.getTotalCost()).toBe(0);
53
+ });
54
+
55
+ it('returns accumulated total after multiple tracks', () => {
56
+ tracker.trackCost('agent-1', 10);
57
+ tracker.trackCost('agent-2', 15);
58
+ tracker.trackCost('agent-1', 5);
59
+ expect(tracker.getTotalCost()).toBe(30);
60
+ });
61
+ });
62
+
63
+ describe('getAgentCost(agentId)', () => {
64
+ it('returns 0 for unknown agent', () => {
65
+ expect(tracker.getAgentCost('unknown-agent')).toBe(0);
66
+ });
67
+
68
+ it('returns correct cost for tracked agent', () => {
69
+ tracker.trackCost('agent-42', 7.5);
70
+ expect(tracker.getAgentCost('agent-42')).toBe(7.5);
71
+ });
72
+ });
73
+
74
+ describe('checkBudget()', () => {
75
+ it('returns status ok when under alert threshold', () => {
76
+ // maxPerRun=50, alertThreshold=0.8 → alert at 40
77
+ tracker.trackCost('agent-1', 30); // 30/50 = 0.6 → ok
78
+ const result = tracker.checkBudget();
79
+ expect(result.status).toBe('ok');
80
+ expect(result.totalCost).toBe(30);
81
+ expect(result.budgetRemaining).toBe(20);
82
+ });
83
+
84
+ it('returns status alert when above threshold but under max', () => {
85
+ // 80% of 50 = 40; spending 42 → alert
86
+ tracker.trackCost('agent-1', 42);
87
+ const result = tracker.checkBudget();
88
+ expect(result.status).toBe('alert');
89
+ expect(result.totalCost).toBe(42);
90
+ expect(result.budgetRemaining).toBe(8);
91
+ });
92
+
93
+ it('returns status exceeded when above max', () => {
94
+ tracker.trackCost('agent-1', 55); // 55 > 50
95
+ const result = tracker.checkBudget();
96
+ expect(result.status).toBe('exceeded');
97
+ expect(result.totalCost).toBe(55);
98
+ expect(result.budgetRemaining).toBe(-5);
99
+ });
100
+
101
+ it('returns status ok when exactly at zero cost', () => {
102
+ const result = tracker.checkBudget();
103
+ expect(result.status).toBe('ok');
104
+ expect(result.budgetRemaining).toBe(50);
105
+ });
106
+
107
+ it('returns status alert when exactly at alert threshold', () => {
108
+ // exactly 80% of 50 = 40
109
+ tracker.trackCost('agent-1', 40);
110
+ const result = tracker.checkBudget();
111
+ expect(result.status).toBe('alert');
112
+ });
113
+
114
+ it('returns status exceeded when exactly at max', () => {
115
+ tracker.trackCost('agent-1', 50);
116
+ const result = tracker.checkBudget();
117
+ expect(result.status).toBe('exceeded');
118
+ });
119
+ });
120
+
121
+ describe('isAgentOverBudget(agentId)', () => {
122
+ it('returns false when agent has not been tracked', () => {
123
+ expect(tracker.isAgentOverBudget('agent-1')).toBe(false);
124
+ });
125
+
126
+ it('returns false when agent cost is under maxPerAgent', () => {
127
+ tracker.trackCost('agent-1', 5); // 5 < 10
128
+ expect(tracker.isAgentOverBudget('agent-1')).toBe(false);
129
+ });
130
+
131
+ it('returns true when agent cost exceeds maxPerAgent', () => {
132
+ tracker.trackCost('agent-1', 11); // 11 > 10
133
+ expect(tracker.isAgentOverBudget('agent-1')).toBe(true);
134
+ });
135
+
136
+ it('returns true when agent cost is exactly at maxPerAgent', () => {
137
+ tracker.trackCost('agent-1', 10); // exactly 10
138
+ expect(tracker.isAgentOverBudget('agent-1')).toBe(true);
139
+ });
140
+
141
+ it('does not affect other agents budget check', () => {
142
+ tracker.trackCost('agent-1', 15); // over budget
143
+ expect(tracker.isAgentOverBudget('agent-2')).toBe(false);
144
+ });
145
+ });
146
+
147
+ describe('onExceeded config variants', () => {
148
+ it('accepts alert-continue as onExceeded policy', () => {
149
+ const t = new CostTracker({ ...defaultBudget, onExceeded: 'alert-continue' });
150
+ expect(t).toBeDefined();
151
+ });
152
+
153
+ it('accepts skip-remaining as onExceeded policy', () => {
154
+ const t = new CostTracker({ ...defaultBudget, onExceeded: 'skip-remaining' });
155
+ expect(t).toBeDefined();
156
+ });
157
+ });
158
+ });