@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,519 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { Orchestrator } from '../orchestrator';
3
+ import type { ValidatedConfig } from '../config';
4
+ import type { AgentResult } from '../types';
5
+ import type { LicenseTier } from '../license';
6
+ import type { BaseAgent } from '../../agents/base-agent';
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+
11
+ const stubConfig: ValidatedConfig = {
12
+ schemaVersion: 1,
13
+ name: 'test',
14
+ auth: { loginUrl: 'http://localhost' },
15
+ modules: [{ id: 'mod', route: '/mod', sidebarIcon: true }],
16
+ environments: {},
17
+ portals: [],
18
+ breakpoints: [],
19
+ integrations: [],
20
+ workers: [],
21
+ workflows: [],
22
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
23
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
24
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
25
+ agentClassification: { blocking: [1, 2], advisory: [23] },
26
+ thresholds: {
27
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
28
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
29
+ apiResponseTime: 2000,
30
+ bundleSizeLimit: 500000,
31
+ maxConsoleErrors: 0,
32
+ },
33
+ disabledAgents: [],
34
+ disabledAgentSet: new Set<number>(),
35
+ };
36
+
37
+ function createMockAgent(agentId: number, result?: Partial<AgentResult>): BaseAgent {
38
+ const defaultResult: AgentResult = {
39
+ agentId,
40
+ agentName: `agent-${agentId}`,
41
+ phase: 'alpha',
42
+ status: 'passed',
43
+ startedAt: new Date().toISOString(),
44
+ completedAt: new Date().toISOString(),
45
+ durationMs: 10,
46
+ findings: [],
47
+ evidence: [],
48
+ summary: `agent-${agentId}: All checks passed`,
49
+ ...result,
50
+ };
51
+
52
+ return {
53
+ agentId,
54
+ agentName: `agent-${agentId}`,
55
+ run: vi.fn().mockResolvedValue(defaultResult),
56
+ } as unknown as BaseAgent;
57
+ }
58
+
59
+ describe('Orchestrator', () => {
60
+ describe('dryRun', () => {
61
+ it('returns the correct agent IDs in order for alpha', () => {
62
+ const orchestrator = new Orchestrator(stubConfig);
63
+ const plan = orchestrator.dryRun('alpha');
64
+
65
+ expect(plan.map(e => e.agentId)).toEqual([1, 2, 23, 3, 4, 5]);
66
+ });
67
+
68
+ it('returns the correct agent IDs for beta', () => {
69
+ const orchestrator = new Orchestrator(stubConfig);
70
+ const plan = orchestrator.dryRun('beta');
71
+
72
+ expect(plan.map(e => e.agentId)).toEqual([5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22]);
73
+ });
74
+
75
+ it('returns the correct agent IDs for uat', () => {
76
+ const orchestrator = new Orchestrator(stubConfig);
77
+ const plan = orchestrator.dryRun('uat');
78
+
79
+ expect(plan.map(e => e.agentId)).toEqual([5, 6, 12, 13, 14, 15, 18, 19, 20, 21, 22, 17]);
80
+ });
81
+
82
+ it('returns the correct agent IDs for release', () => {
83
+ const orchestrator = new Orchestrator(stubConfig);
84
+ const plan = orchestrator.dryRun('release');
85
+
86
+ expect(plan.map(e => e.agentId)).toEqual([10]);
87
+ });
88
+
89
+ it('marks disabled agents as disabled in the dispatch plan', () => {
90
+ const configWithDisabled: ValidatedConfig = {
91
+ ...stubConfig,
92
+ disabledAgents: [2, 23],
93
+ disabledAgentSet: new Set([2, 23]),
94
+ };
95
+ const orchestrator = new Orchestrator(configWithDisabled);
96
+ const plan = orchestrator.dryRun('alpha');
97
+
98
+ const agent2 = plan.find(e => e.agentId === 2);
99
+ const agent23 = plan.find(e => e.agentId === 23);
100
+ expect(agent2?.status).toBe('disabled');
101
+ expect(agent23?.status).toBe('disabled');
102
+ });
103
+
104
+ it('marks unregistered agents as unregistered', () => {
105
+ const orchestrator = new Orchestrator(stubConfig);
106
+ const plan = orchestrator.dryRun('alpha');
107
+
108
+ // No agents registered, so all should be unregistered
109
+ expect(plan.every(e => e.status === 'unregistered')).toBe(true);
110
+ });
111
+
112
+ it('marks registered agents as scheduled', () => {
113
+ const orchestrator = new Orchestrator(stubConfig);
114
+ orchestrator.registerAgent(1, createMockAgent(1));
115
+
116
+ const plan = orchestrator.dryRun('alpha');
117
+ const agent1 = plan.find(e => e.agentId === 1);
118
+ expect(agent1?.status).toBe('scheduled');
119
+ });
120
+ });
121
+
122
+ describe('runPhase', () => {
123
+ let tmpDir: string;
124
+
125
+ beforeEach(async () => {
126
+ tmpDir = path.join(os.tmpdir(), `tf-orch-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
127
+ await fs.mkdir(tmpDir, { recursive: true });
128
+ });
129
+
130
+ it('calls agent.run() for registered agents and writes results', async () => {
131
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
132
+ const mockAgent = createMockAgent(1);
133
+ orchestrator.registerAgent(1, mockAgent);
134
+
135
+ const result = await orchestrator.runPhase('alpha');
136
+
137
+ expect(mockAgent.run).toHaveBeenCalledOnce();
138
+ expect(result.agentResults[1]).toBeDefined();
139
+ expect(result.agentResults[1].status).toBe('passed');
140
+
141
+ // Verify file was written
142
+ const filePath = path.join(tmpDir, 'agent-results', '1.json');
143
+ const fileContent = await fs.readFile(filePath, 'utf-8');
144
+ const parsed = JSON.parse(fileContent) as AgentResult;
145
+ expect(parsed.agentId).toBe(1);
146
+ });
147
+
148
+ it('skips disabled agents', async () => {
149
+ const configWithDisabled: ValidatedConfig = {
150
+ ...stubConfig,
151
+ disabledAgents: [1],
152
+ disabledAgentSet: new Set([1]),
153
+ };
154
+ const orchestrator = new Orchestrator(configWithDisabled, tmpDir);
155
+ const mockAgent = createMockAgent(1);
156
+ orchestrator.registerAgent(1, mockAgent);
157
+
158
+ const result = await orchestrator.runPhase('alpha');
159
+
160
+ expect(mockAgent.run).not.toHaveBeenCalled();
161
+ expect(result.skippedAgents).toContain(1);
162
+ });
163
+
164
+ it('skips unregistered agents and includes them in skippedAgents', async () => {
165
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
166
+ // Register only agent 1, not 2, 23, 3, 4, 5
167
+ orchestrator.registerAgent(1, createMockAgent(1));
168
+
169
+ const result = await orchestrator.runPhase('alpha');
170
+
171
+ expect(result.skippedAgents).toEqual(expect.arrayContaining([2, 23, 3, 4, 5]));
172
+ expect(result.skippedAgents).not.toContain(1);
173
+ });
174
+
175
+ it('evaluates the phase gate after running agents', async () => {
176
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
177
+ // Blocking agents 1 and 2 both pass
178
+ orchestrator.registerAgent(1, createMockAgent(1));
179
+ orchestrator.registerAgent(2, createMockAgent(2));
180
+
181
+ const result = await orchestrator.runPhase('alpha');
182
+
183
+ expect(result.gate).toBeDefined();
184
+ expect(result.gate.phase).toBe('alpha');
185
+ expect(result.gate.passed).toBe(true);
186
+ });
187
+
188
+ it('gate fails when a blocking agent fails', async () => {
189
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
190
+ orchestrator.registerAgent(1, createMockAgent(1, { status: 'failed' }));
191
+ orchestrator.registerAgent(2, createMockAgent(2));
192
+
193
+ const result = await orchestrator.runPhase('alpha');
194
+
195
+ expect(result.gate.passed).toBe(false);
196
+ expect(result.gate.failedBlockingAgents).toContain(1);
197
+ });
198
+
199
+ it('throws when required health checks fail', async () => {
200
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
201
+ orchestrator.registerHealthChecks([{
202
+ name: 'api-reachable',
203
+ check: async () => false,
204
+ required: true,
205
+ }]);
206
+
207
+ await expect(orchestrator.runPhase('alpha')).rejects.toThrow('Health checks failed');
208
+ });
209
+
210
+ it('proceeds when optional health checks fail', async () => {
211
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
212
+ orchestrator.registerHealthChecks([{
213
+ name: 'optional-check',
214
+ check: async () => false,
215
+ required: false,
216
+ }]);
217
+
218
+ const result = await orchestrator.runPhase('alpha');
219
+ expect(result).toBeDefined();
220
+ });
221
+
222
+ it('tracks cost via CostTracker', async () => {
223
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
224
+ orchestrator.registerAgent(1, createMockAgent(1));
225
+
226
+ const result = await orchestrator.runPhase('alpha');
227
+
228
+ expect(result.costTracker).toBeDefined();
229
+ expect(result.costTracker.getTotalCost()).toBeGreaterThanOrEqual(0);
230
+ });
231
+ });
232
+
233
+ describe('license gating', () => {
234
+ let tmpDir: string;
235
+
236
+ beforeEach(async () => {
237
+ tmpDir = path.join(os.tmpdir(), `tf-orch-license-${Date.now()}-${Math.random().toString(36).slice(2)}`);
238
+ await fs.mkdir(tmpDir, { recursive: true });
239
+ });
240
+
241
+ function createOrchestratorWithTier(tier: LicenseTier): Orchestrator {
242
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
243
+ orchestrator._getLicenseTier = () => tier;
244
+ return orchestrator;
245
+ }
246
+
247
+ /** Register all agents from a phase dispatch list. */
248
+ function registerPhaseAgents(orchestrator: Orchestrator, agentIds: readonly number[]): void {
249
+ for (const id of agentIds) {
250
+ orchestrator.registerAgent(id, createMockAgent(id));
251
+ }
252
+ }
253
+
254
+ // ── Phase gating ─────────────────────────────────────────────────
255
+
256
+ it('free tier blocks beta phase', async () => {
257
+ const orchestrator = createOrchestratorWithTier('free');
258
+ registerPhaseAgents(orchestrator, [5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22]);
259
+
260
+ const result = await orchestrator.runPhase('beta');
261
+
262
+ expect(result.gate.passed).toBe(false);
263
+ expect(result.skippedAgents.length).toBeGreaterThan(0);
264
+ expect(result.agentResults).toEqual({});
265
+ });
266
+
267
+ it('free tier blocks uat phase', async () => {
268
+ const orchestrator = createOrchestratorWithTier('free');
269
+ registerPhaseAgents(orchestrator, [5, 6, 12, 13, 14, 15, 18, 19, 20, 21, 22, 17]);
270
+
271
+ const result = await orchestrator.runPhase('uat');
272
+
273
+ expect(result.gate.passed).toBe(false);
274
+ expect(result.agentResults).toEqual({});
275
+ });
276
+
277
+ it('free tier allows alpha phase', async () => {
278
+ const orchestrator = createOrchestratorWithTier('free');
279
+ orchestrator.registerAgent(1, createMockAgent(1));
280
+ orchestrator.registerAgent(2, createMockAgent(2));
281
+
282
+ const result = await orchestrator.runPhase('alpha');
283
+
284
+ expect(result.agentResults[1]).toBeDefined();
285
+ expect(result.agentResults[1].status).toBe('passed');
286
+ });
287
+
288
+ it('pro tier allows beta phase', async () => {
289
+ const orchestrator = createOrchestratorWithTier('pro');
290
+ // Register only free+pro agents from beta dispatch
291
+ orchestrator.registerAgent(5, createMockAgent(5));
292
+ orchestrator.registerAgent(6, createMockAgent(6));
293
+
294
+ const result = await orchestrator.runPhase('beta');
295
+
296
+ expect(result.agentResults[5]).toBeDefined();
297
+ expect(result.agentResults[5].status).toBe('passed');
298
+ expect(result.agentResults[6]).toBeDefined();
299
+ });
300
+
301
+ it('pro tier allows uat phase', async () => {
302
+ const orchestrator = createOrchestratorWithTier('pro');
303
+ orchestrator.registerAgent(5, createMockAgent(5));
304
+
305
+ const result = await orchestrator.runPhase('uat');
306
+
307
+ expect(result.agentResults[5]).toBeDefined();
308
+ });
309
+
310
+ it('enterprise tier allows all phases', async () => {
311
+ const orchestrator = createOrchestratorWithTier('enterprise');
312
+ orchestrator.registerAgent(10, createMockAgent(10));
313
+
314
+ const result = await orchestrator.runPhase('release');
315
+
316
+ expect(result.agentResults[10]).toBeDefined();
317
+ expect(result.agentResults[10].status).toBe('passed');
318
+ });
319
+
320
+ // ── Agent gating ─────────────────────────────────────────────────
321
+
322
+ it('free tier skips pro agents with info finding', async () => {
323
+ const orchestrator = createOrchestratorWithTier('free');
324
+ // Alpha phase: [1, 2, 23, 3, 4, 5]. Agent 23 is pro tier.
325
+ registerPhaseAgents(orchestrator, [1, 2, 23, 3, 4, 5]);
326
+
327
+ const result = await orchestrator.runPhase('alpha');
328
+
329
+ // Agent 23 (Migration Tester) should be skipped due to license
330
+ expect(result.skippedAgents).toContain(23);
331
+ expect(result.agentResults[23]).toBeDefined();
332
+ expect(result.agentResults[23].status).toBe('skipped');
333
+
334
+ // Should have an info-severity finding
335
+ const findings = result.agentResults[23].findings;
336
+ expect(findings).toHaveLength(1);
337
+ expect(findings[0].severity).toBe('info');
338
+ expect(findings[0].description).toContain('requires Pro tier');
339
+ expect(findings[0].description).toContain('current: free');
340
+ });
341
+
342
+ it('free tier runs free agents normally', async () => {
343
+ const orchestrator = createOrchestratorWithTier('free');
344
+ // Register only free-tier agents from alpha dispatch
345
+ orchestrator.registerAgent(1, createMockAgent(1));
346
+ orchestrator.registerAgent(2, createMockAgent(2));
347
+ orchestrator.registerAgent(3, createMockAgent(3));
348
+ orchestrator.registerAgent(4, createMockAgent(4));
349
+ orchestrator.registerAgent(5, createMockAgent(5));
350
+
351
+ const result = await orchestrator.runPhase('alpha');
352
+
353
+ expect(result.agentResults[1].status).toBe('passed');
354
+ expect(result.agentResults[2].status).toBe('passed');
355
+ expect(result.agentResults[3].status).toBe('passed');
356
+ expect(result.agentResults[4].status).toBe('passed');
357
+ expect(result.agentResults[5].status).toBe('passed');
358
+ });
359
+
360
+ it('pro tier skips enterprise agents with info finding', async () => {
361
+ const orchestrator = createOrchestratorWithTier('pro');
362
+ // Beta phase includes enterprise agents: 14, 16, 18, 19
363
+ registerPhaseAgents(orchestrator, [5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22]);
364
+
365
+ const result = await orchestrator.runPhase('beta');
366
+
367
+ // Enterprise agents 14, 16, 18, 19 should be skipped
368
+ expect(result.skippedAgents).toContain(14);
369
+ expect(result.skippedAgents).toContain(16);
370
+ expect(result.skippedAgents).toContain(18);
371
+ expect(result.skippedAgents).toContain(19);
372
+
373
+ // Verify skipped results have info findings
374
+ expect(result.agentResults[14].status).toBe('skipped');
375
+ expect(result.agentResults[14].findings[0].severity).toBe('info');
376
+ expect(result.agentResults[14].findings[0].description).toContain('Enterprise');
377
+
378
+ // Pro agents should run normally
379
+ expect(result.agentResults[6].status).toBe('passed');
380
+ expect(result.agentResults[7].status).toBe('passed');
381
+ expect(result.agentResults[12].status).toBe('passed');
382
+ });
383
+
384
+ it('pro tier allows all phases but gates enterprise agents', async () => {
385
+ const orchestrator = createOrchestratorWithTier('pro');
386
+ // UAT includes enterprise agents: 14, 18, 19, 20
387
+ registerPhaseAgents(orchestrator, [5, 6, 12, 13, 14, 15, 18, 19, 20, 21, 22, 17]);
388
+
389
+ const result = await orchestrator.runPhase('uat');
390
+
391
+ // Phase itself is allowed
392
+ expect(result.agentResults[5]).toBeDefined();
393
+ expect(result.agentResults[5].status).toBe('passed');
394
+
395
+ // Enterprise agents are gated
396
+ expect(result.agentResults[14].status).toBe('skipped');
397
+ expect(result.agentResults[18].status).toBe('skipped');
398
+ expect(result.agentResults[19].status).toBe('skipped');
399
+ expect(result.agentResults[20].status).toBe('skipped');
400
+ });
401
+
402
+ it('enterprise tier allows all agents', async () => {
403
+ const orchestrator = createOrchestratorWithTier('enterprise');
404
+ // Beta phase: all agents should run
405
+ registerPhaseAgents(orchestrator, [5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22]);
406
+
407
+ const result = await orchestrator.runPhase('beta');
408
+
409
+ // No agents should be license-gated
410
+ const skippedWithInfoFinding = Object.values(result.agentResults).filter(
411
+ r => r.status === 'skipped' && r.findings.some(f => f.severity === 'info'),
412
+ );
413
+ expect(skippedWithInfoFinding).toHaveLength(0);
414
+
415
+ // All agents should have run
416
+ expect(result.agentResults[14].status).toBe('passed');
417
+ expect(result.agentResults[16].status).toBe('passed');
418
+ expect(result.agentResults[18].status).toBe('passed');
419
+ expect(result.agentResults[19].status).toBe('passed');
420
+ });
421
+
422
+ // ── Cost budget enforcement ────────────────────────────────────
423
+
424
+ it('skips remaining agents when cost budget is exceeded with abort policy', async () => {
425
+ const abortConfig: ValidatedConfig = {
426
+ ...stubConfig,
427
+ costBudget: { maxPerRun: 0.0001, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
428
+ };
429
+ const orchestrator = new Orchestrator(abortConfig, tmpDir);
430
+ orchestrator._getLicenseTier = () => 'enterprise';
431
+
432
+ // Register all alpha agents
433
+ for (const id of [1, 2, 23, 3, 4, 5]) {
434
+ orchestrator.registerAgent(id, createMockAgent(id, { durationMs: 100000 }));
435
+ }
436
+
437
+ const result = await orchestrator.runPhase('alpha');
438
+
439
+ // Some agents should have been skipped due to budget
440
+ expect(result.skippedAgents.length).toBeGreaterThan(0);
441
+ });
442
+
443
+ it('continues with warning when cost budget is exceeded with alert-continue policy', async () => {
444
+ const warnConfig: ValidatedConfig = {
445
+ ...stubConfig,
446
+ costBudget: { maxPerRun: 0.0001, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'alert-continue' },
447
+ };
448
+ const orchestrator = new Orchestrator(warnConfig, tmpDir);
449
+ orchestrator._getLicenseTier = () => 'enterprise';
450
+
451
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
452
+
453
+ for (const id of [1, 2, 23, 3, 4, 5]) {
454
+ orchestrator.registerAgent(id, createMockAgent(id, { durationMs: 100000 }));
455
+ }
456
+
457
+ const result = await orchestrator.runPhase('alpha');
458
+
459
+ // All agents should have run despite budget exceeded
460
+ expect(result.agentResults[1]).toBeDefined();
461
+ consoleSpy.mockRestore();
462
+ });
463
+
464
+ it('runs parallel-safe agents concurrently in batches', async () => {
465
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
466
+ orchestrator._getLicenseTier = () => 'enterprise';
467
+
468
+ // Beta phase has parallel-safe agents: 6, 7, 8, 12, 13, etc.
469
+ for (const id of [5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22]) {
470
+ orchestrator.registerAgent(id, createMockAgent(id));
471
+ }
472
+
473
+ const result = await orchestrator.runPhase('beta');
474
+
475
+ // All parallel-safe agents should have results
476
+ expect(result.agentResults[6]).toBeDefined();
477
+ expect(result.agentResults[7]).toBeDefined();
478
+ });
479
+
480
+ it('getRunDir returns the run directory', () => {
481
+ const orchestrator = new Orchestrator(stubConfig, '/my/run/dir');
482
+ expect(orchestrator.getRunDir()).toBe('/my/run/dir');
483
+ });
484
+
485
+ it('fix loop does not dispatch when no findings exist', async () => {
486
+ const healerAgent = createMockAgent(9);
487
+ const orchestrator = new Orchestrator(stubConfig, tmpDir);
488
+ orchestrator._getLicenseTier = () => 'enterprise';
489
+ orchestrator.registerAgent(1, createMockAgent(1)); // no findings
490
+ orchestrator.registerAgent(9, healerAgent);
491
+
492
+ await orchestrator.runPhase('alpha');
493
+
494
+ expect(healerAgent.run).not.toHaveBeenCalled();
495
+ });
496
+
497
+ it('skipped agents produce findings with info severity and license description', async () => {
498
+ const orchestrator = createOrchestratorWithTier('free');
499
+ // Register agent 23 (pro) in alpha phase
500
+ orchestrator.registerAgent(1, createMockAgent(1));
501
+ orchestrator.registerAgent(23, createMockAgent(23));
502
+
503
+ const result = await orchestrator.runPhase('alpha');
504
+
505
+ const gatedResult = result.agentResults[23];
506
+ expect(gatedResult).toBeDefined();
507
+ expect(gatedResult.status).toBe('skipped');
508
+ expect(gatedResult.durationMs).toBe(0);
509
+ expect(gatedResult.findings).toHaveLength(1);
510
+
511
+ const finding = gatedResult.findings[0];
512
+ expect(finding.id).toBe('license-gate-23');
513
+ expect(finding.severity).toBe('info');
514
+ expect(finding.type).toBe('infra-issue');
515
+ expect(finding.module).toBe('license');
516
+ expect(finding.agentId).toBe(23);
517
+ });
518
+ });
519
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { AgentResult } from '../types';
3
+ import { evaluateGate } from '../phase-gate';
4
+
5
+ type PartialAgentResults = Record<number, Pick<AgentResult, 'status'>>;
6
+
7
+ describe('Phase Gate', () => {
8
+ it('passes when all blocking agents pass', () => {
9
+ const result = evaluateGate('alpha', {
10
+ blocking: [1, 2, 4],
11
+ advisory: [12],
12
+ }, {
13
+ 1: { status: 'passed' },
14
+ 2: { status: 'passed' },
15
+ 4: { status: 'passed' },
16
+ 12: { status: 'failed' },
17
+ } as unknown as PartialAgentResults as Record<number, AgentResult>);
18
+ expect(result.passed).toBe(true);
19
+ });
20
+
21
+ it('fails when any blocking agent fails', () => {
22
+ const result = evaluateGate('alpha', {
23
+ blocking: [1, 2],
24
+ advisory: [],
25
+ }, {
26
+ 1: { status: 'passed' },
27
+ 2: { status: 'failed' },
28
+ } as unknown as PartialAgentResults as Record<number, AgentResult>);
29
+ expect(result.passed).toBe(false);
30
+ expect(result.failedBlockingAgents).toEqual([2]);
31
+ });
32
+
33
+ it('skips disabled agents', () => {
34
+ const result = evaluateGate('beta', {
35
+ blocking: [5, 6, 7],
36
+ advisory: [],
37
+ }, {
38
+ 5: { status: 'passed' },
39
+ 6: { status: 'passed' },
40
+ } as unknown as PartialAgentResults as Record<number, AgentResult>, new Set([7]));
41
+ expect(result.passed).toBe(true);
42
+ });
43
+ });