@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,400 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { ValidatedConfig } from '../../core/config';
3
+ import type { Phase } from '../../core/types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Stub config helpers
7
+ // ---------------------------------------------------------------------------
8
+
9
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
10
+ return {
11
+ schemaVersion: 1,
12
+ name: 'test',
13
+ auth: {
14
+ loginUrl: 'http://localhost/login',
15
+ credentials: {
16
+ admin: { email: 'admin@firma.com', password: 'secret' },
17
+ },
18
+ },
19
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
20
+ environments: {
21
+ beta: { baseUrl: 'http://localhost:3000', seed: false },
22
+ },
23
+ portals: [],
24
+ breakpoints: [],
25
+ integrations: [],
26
+ workers: [],
27
+ workflows: [],
28
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
29
+ tenancy: {
30
+ enabled: true,
31
+ isolationField: 'firmId',
32
+ testFirms: ['firm-a', 'firm-b'],
33
+ },
34
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
35
+ agentClassification: { blocking: [], advisory: [] },
36
+ thresholds: {
37
+ unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 },
38
+ lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 },
39
+ apiResponseTime: 2000,
40
+ bundleSizeLimit: 500000,
41
+ maxConsoleErrors: 0,
42
+ },
43
+ disabledAgents: [],
44
+ disabledAgentSet: new Set<number>(),
45
+ ...overrides,
46
+ };
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Mock ApiClient
51
+ // ---------------------------------------------------------------------------
52
+
53
+ const apiClientGetMock = vi.fn();
54
+ const apiClientPostMock = vi.fn();
55
+ const createAuthenticatedMock = vi.fn();
56
+
57
+ vi.mock('../../helpers/api-client', () => ({
58
+ ApiClient: class MockApiClient {
59
+ static createAuthenticated(...args: unknown[]) {
60
+ return createAuthenticatedMock(...args);
61
+ }
62
+ get(...args: unknown[]) {
63
+ return apiClientGetMock(...args);
64
+ }
65
+ post(...args: unknown[]) {
66
+ return apiClientPostMock(...args);
67
+ }
68
+ },
69
+ }));
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Import after mocks
73
+ // ---------------------------------------------------------------------------
74
+
75
+ const { TenantIsolationAuditorAgent } = await import('../19-tenant-isolation-auditor');
76
+
77
+ function createAgent(
78
+ config: ValidatedConfig = makeConfig(),
79
+ phase: Phase = 'beta',
80
+ ): InstanceType<typeof TenantIsolationAuditorAgent> {
81
+ return new TenantIsolationAuditorAgent(config, phase, '/tmp/test-run');
82
+ }
83
+
84
+ // Default happy-path mock responses (403 for tampering attempts)
85
+ function mockForbiddenResponses(): void {
86
+ createAuthenticatedMock.mockResolvedValue({
87
+ get: apiClientGetMock,
88
+ post: apiClientPostMock,
89
+ });
90
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
91
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Tests
96
+ // ---------------------------------------------------------------------------
97
+
98
+ describe('TenantIsolationAuditorAgent', () => {
99
+ beforeEach(() => {
100
+ vi.resetAllMocks();
101
+ mockForbiddenResponses();
102
+ });
103
+
104
+ it('has agentId 19 and agentName "Tenant Isolation Auditor"', () => {
105
+ const agent = createAgent();
106
+ expect(agent.agentId).toBe(19);
107
+ expect(agent.agentName).toBe('Tenant Isolation Auditor');
108
+ });
109
+
110
+ // -------------------------------------------------------------------------
111
+ // preFlight()
112
+ // -------------------------------------------------------------------------
113
+
114
+ describe('preFlight()', () => {
115
+ it('throws when tenancy is disabled', async () => {
116
+ const config = makeConfig({
117
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: ['firm-a'] },
118
+ });
119
+ const agent = createAgent(config);
120
+ const result = await agent.run();
121
+
122
+ expect(result.status).toBe('failed');
123
+ expect(result.findings[0].description).toContain('tenancy.enabled');
124
+ });
125
+
126
+ it('throws when no test firms are configured', async () => {
127
+ const config = makeConfig({
128
+ tenancy: { enabled: true, isolationField: 'firmId', testFirms: [] },
129
+ });
130
+ const agent = createAgent(config);
131
+ const result = await agent.run();
132
+
133
+ expect(result.status).toBe('failed');
134
+ expect(result.findings[0].description).toContain('testFirms');
135
+ });
136
+
137
+ it('passes when tenancy is enabled and at least one firm is configured', async () => {
138
+ const agent = createAgent();
139
+ const result = await agent.run();
140
+
141
+ expect(result.findings.some((f) => f.description.includes('tenancy.enabled'))).toBe(false);
142
+ });
143
+ });
144
+
145
+ // -------------------------------------------------------------------------
146
+ // execute() — 403 responses (correct behaviour)
147
+ // -------------------------------------------------------------------------
148
+
149
+ describe('execute() — 403 on tampering (correct behaviour)', () => {
150
+ it('creates no finding when all tamper attempts return 403', async () => {
151
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
152
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
153
+
154
+ const agent = createAgent();
155
+ const result = await agent.run();
156
+
157
+ const securityFindings = result.findings.filter((f) => f.type === 'code-bug-security');
158
+ expect(securityFindings).toHaveLength(0);
159
+ });
160
+ });
161
+
162
+ // -------------------------------------------------------------------------
163
+ // execute() — URL tampering data leak
164
+ // -------------------------------------------------------------------------
165
+
166
+ describe('execute() — URL tampering data leak', () => {
167
+ it('creates a critical finding when a URL-tampered GET returns 200 with data', async () => {
168
+ // GET returns data for wrong firm — data leak
169
+ apiClientGetMock.mockResolvedValue({
170
+ status: 200,
171
+ body: [{ id: '1', firmId: 'firm-b', name: 'Firm B Client' }],
172
+ });
173
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
174
+
175
+ const agent = createAgent();
176
+ const result = await agent.run();
177
+
178
+ const urlTamperFinding = result.findings.find(
179
+ (f) => f.id.includes('url-tamper') && f.type === 'code-bug-security',
180
+ );
181
+ expect(urlTamperFinding).toBeDefined();
182
+ expect(urlTamperFinding!.severity).toBe('critical');
183
+ });
184
+
185
+ it('creates no finding when URL-tampered GET returns 200 with empty array', async () => {
186
+ // Empty array — no data leak
187
+ apiClientGetMock.mockResolvedValue({ status: 200, body: [] });
188
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
189
+
190
+ const agent = createAgent();
191
+ const result = await agent.run();
192
+
193
+ const urlTamperFinding = result.findings.find((f) => f.id.includes('url-tamper'));
194
+ expect(urlTamperFinding).toBeUndefined();
195
+ });
196
+ });
197
+
198
+ // -------------------------------------------------------------------------
199
+ // execute() — API parameter tampering data leak
200
+ // -------------------------------------------------------------------------
201
+
202
+ describe('execute() — API parameter tampering', () => {
203
+ it('creates a critical finding when POST with wrong firmId returns 200 with data', async () => {
204
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
205
+ // POST returns data — data leak via parameter tampering
206
+ apiClientPostMock.mockResolvedValue({
207
+ status: 200,
208
+ body: [{ id: '2', firmId: 'firm-b', name: 'Invoice B' }],
209
+ });
210
+
211
+ const agent = createAgent();
212
+ const result = await agent.run();
213
+
214
+ const paramTamperFinding = result.findings.find(
215
+ (f) => f.id.includes('param-tamper') && f.type === 'code-bug-security',
216
+ );
217
+ expect(paramTamperFinding).toBeDefined();
218
+ expect(paramTamperFinding!.severity).toBe('critical');
219
+ });
220
+ });
221
+
222
+ // -------------------------------------------------------------------------
223
+ // execute() — search isolation
224
+ // -------------------------------------------------------------------------
225
+
226
+ describe('execute() — search isolation', () => {
227
+ it('creates a critical finding when a search for firm-b returns results while logged in as firm-a', async () => {
228
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
229
+ // search returns results for firm-b
230
+ apiClientPostMock.mockImplementation(async (path: string) => {
231
+ if (path.includes('search')) {
232
+ return { status: 200, body: [{ id: '3', firmId: 'firm-b', name: 'Leaked Client' }] };
233
+ }
234
+ return { status: 403, body: null };
235
+ });
236
+
237
+ const agent = createAgent();
238
+ const result = await agent.run();
239
+
240
+ const searchFinding = result.findings.find(
241
+ (f) => f.id.includes('search-isolation') && f.type === 'code-bug-security',
242
+ );
243
+ expect(searchFinding).toBeDefined();
244
+ expect(searchFinding!.severity).toBe('critical');
245
+ });
246
+
247
+ it('creates no search isolation finding when search returns 0 results', async () => {
248
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
249
+ apiClientPostMock.mockResolvedValue({ status: 200, body: [] });
250
+
251
+ const agent = createAgent();
252
+ const result = await agent.run();
253
+
254
+ const searchFinding = result.findings.find((f) => f.id.includes('search-isolation'));
255
+ expect(searchFinding).toBeUndefined();
256
+ });
257
+ });
258
+
259
+ // -------------------------------------------------------------------------
260
+ // execute() — auth failure
261
+ // -------------------------------------------------------------------------
262
+
263
+ describe('execute() — auth failure', () => {
264
+ it('creates a medium finding when authentication fails', async () => {
265
+ createAuthenticatedMock.mockRejectedValue(new Error('Invalid credentials'));
266
+
267
+ const agent = createAgent();
268
+ const result = await agent.run();
269
+
270
+ const authFinding = result.findings.find((f) => f.id.includes('auth-failed'));
271
+ expect(authFinding).toBeDefined();
272
+ expect(authFinding!.severity).toBe('medium');
273
+ });
274
+ });
275
+
276
+ // -------------------------------------------------------------------------
277
+ // No environment configured
278
+ // -------------------------------------------------------------------------
279
+
280
+ describe('execute() — no environment', () => {
281
+ it('fails or produces critical finding when no environment is configured', async () => {
282
+ const config = makeConfig({ environments: {} });
283
+ const agent = createAgent(config);
284
+ const result = await agent.run();
285
+
286
+ // resolveEnvironment may provide a fallback or the agent may find no env
287
+ expect(
288
+ result.status === 'failed' ||
289
+ result.findings.some(f => f.severity === 'critical'),
290
+ ).toBe(true);
291
+ });
292
+ });
293
+
294
+ // -------------------------------------------------------------------------
295
+ // No credentials configured
296
+ // -------------------------------------------------------------------------
297
+
298
+ describe('execute() — no credentials', () => {
299
+ it('creates a critical finding when no credentials are configured', async () => {
300
+ const config = makeConfig({
301
+ auth: { loginUrl: 'http://localhost/login', credentials: {} },
302
+ });
303
+ const agent = createAgent(config);
304
+ const result = await agent.run();
305
+
306
+ const finding = result.findings.find(f => f.id.includes('no-credentials'));
307
+ expect(finding).toBeDefined();
308
+ expect(finding!.severity).toBe('critical');
309
+ });
310
+ });
311
+
312
+ // -------------------------------------------------------------------------
313
+ // Single test firm (no firmB) — uses synthetic wrong firm
314
+ // -------------------------------------------------------------------------
315
+
316
+ describe('execute() — single firm config', () => {
317
+ it('uses synthetic wrong firm when only one test firm is configured', async () => {
318
+ const config = makeConfig({
319
+ tenancy: { enabled: true, isolationField: 'firmId', testFirms: ['firm-a'] },
320
+ });
321
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
322
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
323
+
324
+ const agent = createAgent(config);
325
+ const result = await agent.run();
326
+
327
+ // Should complete without findings (all tamper attempts return 403)
328
+ const securityFindings = result.findings.filter(f => f.type === 'code-bug-security');
329
+ expect(securityFindings).toHaveLength(0);
330
+ });
331
+
332
+ it('skips search isolation test when only one firm is configured', async () => {
333
+ const config = makeConfig({
334
+ tenancy: { enabled: true, isolationField: 'firmId', testFirms: ['firm-a'] },
335
+ });
336
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
337
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
338
+
339
+ const agent = createAgent(config);
340
+ const result = await agent.run();
341
+
342
+ const searchFindings = result.findings.filter(f => f.id.includes('search-isolation'));
343
+ expect(searchFindings).toHaveLength(0);
344
+ });
345
+ });
346
+
347
+ // -------------------------------------------------------------------------
348
+ // URL tampering — GET returns 200 with empty object (no data leak)
349
+ // -------------------------------------------------------------------------
350
+
351
+ describe('execute() — URL tampering with 200 but null body', () => {
352
+ it('creates no finding when URL-tampered GET returns 200 with null body', async () => {
353
+ apiClientGetMock.mockResolvedValue({ status: 200, body: null });
354
+ apiClientPostMock.mockResolvedValue({ status: 403, body: null });
355
+
356
+ const agent = createAgent();
357
+ const result = await agent.run();
358
+
359
+ const urlTamperFindings = result.findings.filter(f => f.id.includes('url-tamper'));
360
+ expect(urlTamperFindings).toHaveLength(0);
361
+ });
362
+ });
363
+
364
+ // -------------------------------------------------------------------------
365
+ // Network failure on tamper request — skips
366
+ // -------------------------------------------------------------------------
367
+
368
+ describe('execute() — network errors', () => {
369
+ it('skips tamper checks when API call throws', async () => {
370
+ apiClientGetMock.mockRejectedValue(new Error('Network error'));
371
+ apiClientPostMock.mockRejectedValue(new Error('Network error'));
372
+
373
+ const agent = createAgent();
374
+ const result = await agent.run();
375
+
376
+ const securityFindings = result.findings.filter(f => f.type === 'code-bug-security');
377
+ expect(securityFindings).toHaveLength(0);
378
+ });
379
+ });
380
+
381
+ // -------------------------------------------------------------------------
382
+ // Parameter tampering — POST returns 200 with non-array object data
383
+ // -------------------------------------------------------------------------
384
+
385
+ describe('execute() — parameter tampering with object body', () => {
386
+ it('creates a finding when POST returns 200 with object body (non-null)', async () => {
387
+ apiClientGetMock.mockResolvedValue({ status: 403, body: null });
388
+ apiClientPostMock.mockResolvedValue({
389
+ status: 200,
390
+ body: { id: '1', name: 'leaked data' },
391
+ });
392
+
393
+ const agent = createAgent();
394
+ const result = await agent.run();
395
+
396
+ const paramFindings = result.findings.filter(f => f.id.includes('param-tamper'));
397
+ expect(paramFindings.length).toBeGreaterThan(0);
398
+ });
399
+ });
400
+ });