@avi770/testteam 1.2.0 → 2.0.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 (300) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/README.md +53 -6
  3. package/agents/24-signup-onboarding-tester.ts +429 -0
  4. package/agents/25-crud-flow-tester.ts +302 -0
  5. package/agents/26-form-validator.ts +297 -0
  6. package/agents/27-search-filter-tester.ts +326 -0
  7. package/agents/28-navigation-routing-tester.ts +425 -0
  8. package/agents/29-responsive-interaction-tester.ts +350 -0
  9. package/agents/30-multi-user-scenario-tester.ts +319 -0
  10. package/agents/31-load-tester.ts +134 -0
  11. package/agents/32-memory-leak-detector.ts +194 -0
  12. package/agents/33-bundle-analyzer.ts +132 -0
  13. package/agents/34-xss-scanner.ts +191 -0
  14. package/agents/35-csrf-tester.ts +82 -0
  15. package/agents/36-auth-fuzzer.ts +194 -0
  16. package/agents/37-dependency-scanner.ts +176 -0
  17. package/agents/38-secrets-scanner.ts +137 -0
  18. package/agents/39-api-contract-tester.ts +199 -0
  19. package/agents/40-rate-limit-tester.ts +94 -0
  20. package/agents/41-api-pagination-tester.ts +97 -0
  21. package/agents/42-graphql-tester.ts +222 -0
  22. package/agents/43-data-consistency-checker.ts +205 -0
  23. package/agents/44-backup-recovery-tester.ts +152 -0
  24. package/agents/45-data-privacy-scanner.ts +125 -0
  25. package/agents/46-seo-auditor.ts +294 -0
  26. package/agents/47-social-preview-tester.ts +232 -0
  27. package/agents/48-lighthouse-auditor.ts +213 -0
  28. package/agents/49-i18n-tester.ts +198 -0
  29. package/agents/50-timezone-tester.ts +173 -0
  30. package/agents/51-error-recovery-tester.ts +155 -0
  31. package/agents/52-offline-mode-tester.ts +180 -0
  32. package/agents/53-graceful-degradation-tester.ts +156 -0
  33. package/agents/54-websocket-tester.ts +151 -0
  34. package/agents/55-realtime-sync-tester.ts +194 -0
  35. package/agents/56-file-upload-tester.ts +194 -0
  36. package/agents/57-export-tester.ts +174 -0
  37. package/agents/58-payment-flow-tester.ts +183 -0
  38. package/agents/59-ssl-tls-auditor.ts +141 -0
  39. package/agents/60-dns-cdn-tester.ts +117 -0
  40. package/agents/61-docker-health-checker.ts +111 -0
  41. package/agents/62-env-config-validator.ts +152 -0
  42. package/agents/63-log-quality-auditor.ts +136 -0
  43. package/agents/64-analytics-tracker-tester.ts +165 -0
  44. package/agents/65-gdpr-compliance-tester.ts +215 -0
  45. package/agents/66-soc2-control-validator.ts +210 -0
  46. package/agents/67-wcag-aaa-tester.ts +241 -0
  47. package/agents/68-dead-code-detector.ts +135 -0
  48. package/agents/69-type-safety-auditor.ts +164 -0
  49. package/agents/70-complexity-analyzer.ts +179 -0
  50. package/agents/__tests__/24-signup-onboarding-tester.test.ts +274 -0
  51. package/agents/__tests__/25-crud-flow-tester.test.ts +322 -0
  52. package/agents/__tests__/26-form-validator.test.ts +345 -0
  53. package/agents/__tests__/27-search-filter-tester.test.ts +311 -0
  54. package/agents/__tests__/28-navigation-routing-tester.test.ts +328 -0
  55. package/agents/__tests__/29-responsive-interaction-tester.test.ts +297 -0
  56. package/agents/__tests__/30-multi-user-scenario-tester.test.ts +328 -0
  57. package/agents/__tests__/31-load-tester.test.ts +189 -0
  58. package/agents/__tests__/32-memory-leak-detector.test.ts +251 -0
  59. package/agents/__tests__/33-bundle-analyzer.test.ts +237 -0
  60. package/agents/__tests__/34-xss-scanner.test.ts +258 -0
  61. package/agents/__tests__/35-csrf-tester.test.ts +200 -0
  62. package/agents/__tests__/36-auth-fuzzer.test.ts +214 -0
  63. package/agents/__tests__/37-dependency-scanner.test.ts +266 -0
  64. package/agents/__tests__/38-secrets-scanner.test.ts +224 -0
  65. package/agents/__tests__/39-api-contract-tester.test.ts +312 -0
  66. package/agents/__tests__/40-rate-limit-tester.test.ts +192 -0
  67. package/agents/__tests__/41-api-pagination-tester.test.ts +198 -0
  68. package/agents/__tests__/42-graphql-tester.test.ts +252 -0
  69. package/agents/__tests__/43-data-consistency-checker.test.ts +232 -0
  70. package/agents/__tests__/44-backup-recovery-tester.test.ts +222 -0
  71. package/agents/__tests__/45-data-privacy-scanner.test.ts +223 -0
  72. package/agents/__tests__/46-seo-auditor.test.ts +261 -0
  73. package/agents/__tests__/47-social-preview-tester.test.ts +245 -0
  74. package/agents/__tests__/48-lighthouse-auditor.test.ts +276 -0
  75. package/agents/__tests__/49-i18n-tester.test.ts +201 -0
  76. package/agents/__tests__/50-timezone-tester.test.ts +172 -0
  77. package/agents/__tests__/51-error-recovery-tester.test.ts +162 -0
  78. package/agents/__tests__/52-offline-mode-tester.test.ts +164 -0
  79. package/agents/__tests__/53-graceful-degradation-tester.test.ts +168 -0
  80. package/agents/__tests__/54-websocket-tester.test.ts +157 -0
  81. package/agents/__tests__/55-realtime-sync-tester.test.ts +181 -0
  82. package/agents/__tests__/56-file-upload-tester.test.ts +172 -0
  83. package/agents/__tests__/57-export-tester.test.ts +169 -0
  84. package/agents/__tests__/58-payment-flow-tester.test.ts +182 -0
  85. package/agents/__tests__/59-ssl-tls-auditor.test.ts +179 -0
  86. package/agents/__tests__/60-dns-cdn-tester.test.ts +176 -0
  87. package/agents/__tests__/61-docker-health-checker.test.ts +150 -0
  88. package/agents/__tests__/62-env-config-validator.test.ts +166 -0
  89. package/agents/__tests__/63-log-quality-auditor.test.ts +175 -0
  90. package/agents/__tests__/64-analytics-tracker-tester.test.ts +158 -0
  91. package/agents/__tests__/65-gdpr-compliance-tester.test.ts +174 -0
  92. package/agents/__tests__/66-soc2-control-validator.test.ts +183 -0
  93. package/agents/__tests__/67-wcag-aaa-tester.test.ts +190 -0
  94. package/agents/__tests__/68-dead-code-detector.test.ts +174 -0
  95. package/agents/__tests__/69-type-safety-auditor.test.ts +173 -0
  96. package/agents/__tests__/70-complexity-analyzer.test.ts +177 -0
  97. package/agents/__tests__/registry.test.ts +13 -13
  98. package/agents/registry.ts +146 -5
  99. package/core/__tests__/integration.test.ts +4 -4
  100. package/core/__tests__/orchestrator.test.ts +17 -16
  101. package/core/license.ts +208 -211
  102. package/core/orchestrator.ts +6 -4
  103. package/dist/agents/24-signup-onboarding-tester.d.ts +35 -0
  104. package/dist/agents/24-signup-onboarding-tester.d.ts.map +1 -0
  105. package/dist/agents/24-signup-onboarding-tester.js +357 -0
  106. package/dist/agents/24-signup-onboarding-tester.js.map +1 -0
  107. package/dist/agents/25-crud-flow-tester.d.ts +11 -0
  108. package/dist/agents/25-crud-flow-tester.d.ts.map +1 -0
  109. package/dist/agents/25-crud-flow-tester.js +253 -0
  110. package/dist/agents/25-crud-flow-tester.js.map +1 -0
  111. package/dist/agents/26-form-validator.d.ts +12 -0
  112. package/dist/agents/26-form-validator.d.ts.map +1 -0
  113. package/dist/agents/26-form-validator.js +257 -0
  114. package/dist/agents/26-form-validator.js.map +1 -0
  115. package/dist/agents/27-search-filter-tester.d.ts +20 -0
  116. package/dist/agents/27-search-filter-tester.d.ts.map +1 -0
  117. package/dist/agents/27-search-filter-tester.js +267 -0
  118. package/dist/agents/27-search-filter-tester.js.map +1 -0
  119. package/dist/agents/28-navigation-routing-tester.d.ts +32 -0
  120. package/dist/agents/28-navigation-routing-tester.d.ts.map +1 -0
  121. package/dist/agents/28-navigation-routing-tester.js +363 -0
  122. package/dist/agents/28-navigation-routing-tester.js.map +1 -0
  123. package/dist/agents/29-responsive-interaction-tester.d.ts +26 -0
  124. package/dist/agents/29-responsive-interaction-tester.d.ts.map +1 -0
  125. package/dist/agents/29-responsive-interaction-tester.js +272 -0
  126. package/dist/agents/29-responsive-interaction-tester.js.map +1 -0
  127. package/dist/agents/30-multi-user-scenario-tester.d.ts +24 -0
  128. package/dist/agents/30-multi-user-scenario-tester.d.ts.map +1 -0
  129. package/dist/agents/30-multi-user-scenario-tester.js +254 -0
  130. package/dist/agents/30-multi-user-scenario-tester.js.map +1 -0
  131. package/dist/agents/31-load-tester.d.ts +12 -0
  132. package/dist/agents/31-load-tester.d.ts.map +1 -0
  133. package/dist/agents/31-load-tester.js +110 -0
  134. package/dist/agents/31-load-tester.js.map +1 -0
  135. package/dist/agents/32-memory-leak-detector.d.ts +12 -0
  136. package/dist/agents/32-memory-leak-detector.d.ts.map +1 -0
  137. package/dist/agents/32-memory-leak-detector.js +167 -0
  138. package/dist/agents/32-memory-leak-detector.js.map +1 -0
  139. package/dist/agents/33-bundle-analyzer.d.ts +10 -0
  140. package/dist/agents/33-bundle-analyzer.d.ts.map +1 -0
  141. package/dist/agents/33-bundle-analyzer.js +111 -0
  142. package/dist/agents/33-bundle-analyzer.js.map +1 -0
  143. package/dist/agents/34-xss-scanner.d.ts +11 -0
  144. package/dist/agents/34-xss-scanner.d.ts.map +1 -0
  145. package/dist/agents/34-xss-scanner.js +164 -0
  146. package/dist/agents/34-xss-scanner.js.map +1 -0
  147. package/dist/agents/35-csrf-tester.d.ts +11 -0
  148. package/dist/agents/35-csrf-tester.d.ts.map +1 -0
  149. package/dist/agents/35-csrf-tester.js +70 -0
  150. package/dist/agents/35-csrf-tester.js.map +1 -0
  151. package/dist/agents/36-auth-fuzzer.d.ts +13 -0
  152. package/dist/agents/36-auth-fuzzer.d.ts.map +1 -0
  153. package/dist/agents/36-auth-fuzzer.js +163 -0
  154. package/dist/agents/36-auth-fuzzer.js.map +1 -0
  155. package/dist/agents/37-dependency-scanner.d.ts +11 -0
  156. package/dist/agents/37-dependency-scanner.d.ts.map +1 -0
  157. package/dist/agents/37-dependency-scanner.js +139 -0
  158. package/dist/agents/37-dependency-scanner.js.map +1 -0
  159. package/dist/agents/38-secrets-scanner.d.ts +11 -0
  160. package/dist/agents/38-secrets-scanner.d.ts.map +1 -0
  161. package/dist/agents/38-secrets-scanner.js +116 -0
  162. package/dist/agents/38-secrets-scanner.js.map +1 -0
  163. package/dist/agents/39-api-contract-tester.d.ts +12 -0
  164. package/dist/agents/39-api-contract-tester.d.ts.map +1 -0
  165. package/dist/agents/39-api-contract-tester.js +142 -0
  166. package/dist/agents/39-api-contract-tester.js.map +1 -0
  167. package/dist/agents/40-rate-limit-tester.d.ts +12 -0
  168. package/dist/agents/40-rate-limit-tester.d.ts.map +1 -0
  169. package/dist/agents/40-rate-limit-tester.js +79 -0
  170. package/dist/agents/40-rate-limit-tester.js.map +1 -0
  171. package/dist/agents/41-api-pagination-tester.d.ts +12 -0
  172. package/dist/agents/41-api-pagination-tester.d.ts.map +1 -0
  173. package/dist/agents/41-api-pagination-tester.js +79 -0
  174. package/dist/agents/41-api-pagination-tester.js.map +1 -0
  175. package/dist/agents/42-graphql-tester.d.ts +13 -0
  176. package/dist/agents/42-graphql-tester.d.ts.map +1 -0
  177. package/dist/agents/42-graphql-tester.js +187 -0
  178. package/dist/agents/42-graphql-tester.js.map +1 -0
  179. package/dist/agents/43-data-consistency-checker.d.ts +11 -0
  180. package/dist/agents/43-data-consistency-checker.d.ts.map +1 -0
  181. package/dist/agents/43-data-consistency-checker.js +176 -0
  182. package/dist/agents/43-data-consistency-checker.js.map +1 -0
  183. package/dist/agents/44-backup-recovery-tester.d.ts +11 -0
  184. package/dist/agents/44-backup-recovery-tester.d.ts.map +1 -0
  185. package/dist/agents/44-backup-recovery-tester.js +128 -0
  186. package/dist/agents/44-backup-recovery-tester.js.map +1 -0
  187. package/dist/agents/45-data-privacy-scanner.d.ts +11 -0
  188. package/dist/agents/45-data-privacy-scanner.d.ts.map +1 -0
  189. package/dist/agents/45-data-privacy-scanner.js +100 -0
  190. package/dist/agents/45-data-privacy-scanner.js.map +1 -0
  191. package/dist/agents/46-seo-auditor.d.ts +12 -0
  192. package/dist/agents/46-seo-auditor.d.ts.map +1 -0
  193. package/dist/agents/46-seo-auditor.js +275 -0
  194. package/dist/agents/46-seo-auditor.js.map +1 -0
  195. package/dist/agents/47-social-preview-tester.d.ts +11 -0
  196. package/dist/agents/47-social-preview-tester.d.ts.map +1 -0
  197. package/dist/agents/47-social-preview-tester.js +219 -0
  198. package/dist/agents/47-social-preview-tester.js.map +1 -0
  199. package/dist/agents/48-lighthouse-auditor.d.ts +11 -0
  200. package/dist/agents/48-lighthouse-auditor.d.ts.map +1 -0
  201. package/dist/agents/48-lighthouse-auditor.js +192 -0
  202. package/dist/agents/48-lighthouse-auditor.js.map +1 -0
  203. package/dist/agents/49-i18n-tester.d.ts +13 -0
  204. package/dist/agents/49-i18n-tester.d.ts.map +1 -0
  205. package/dist/agents/49-i18n-tester.js +172 -0
  206. package/dist/agents/49-i18n-tester.js.map +1 -0
  207. package/dist/agents/50-timezone-tester.d.ts +11 -0
  208. package/dist/agents/50-timezone-tester.d.ts.map +1 -0
  209. package/dist/agents/50-timezone-tester.js +152 -0
  210. package/dist/agents/50-timezone-tester.js.map +1 -0
  211. package/dist/agents/51-error-recovery-tester.d.ts +11 -0
  212. package/dist/agents/51-error-recovery-tester.d.ts.map +1 -0
  213. package/dist/agents/51-error-recovery-tester.js +134 -0
  214. package/dist/agents/51-error-recovery-tester.js.map +1 -0
  215. package/dist/agents/52-offline-mode-tester.d.ts +12 -0
  216. package/dist/agents/52-offline-mode-tester.d.ts.map +1 -0
  217. package/dist/agents/52-offline-mode-tester.js +161 -0
  218. package/dist/agents/52-offline-mode-tester.js.map +1 -0
  219. package/dist/agents/53-graceful-degradation-tester.d.ts +12 -0
  220. package/dist/agents/53-graceful-degradation-tester.d.ts.map +1 -0
  221. package/dist/agents/53-graceful-degradation-tester.js +130 -0
  222. package/dist/agents/53-graceful-degradation-tester.js.map +1 -0
  223. package/dist/agents/54-websocket-tester.d.ts +10 -0
  224. package/dist/agents/54-websocket-tester.d.ts.map +1 -0
  225. package/dist/agents/54-websocket-tester.js +132 -0
  226. package/dist/agents/54-websocket-tester.js.map +1 -0
  227. package/dist/agents/55-realtime-sync-tester.d.ts +11 -0
  228. package/dist/agents/55-realtime-sync-tester.d.ts.map +1 -0
  229. package/dist/agents/55-realtime-sync-tester.js +175 -0
  230. package/dist/agents/55-realtime-sync-tester.js.map +1 -0
  231. package/dist/agents/56-file-upload-tester.d.ts +12 -0
  232. package/dist/agents/56-file-upload-tester.d.ts.map +1 -0
  233. package/dist/agents/56-file-upload-tester.js +166 -0
  234. package/dist/agents/56-file-upload-tester.js.map +1 -0
  235. package/dist/agents/57-export-tester.d.ts +11 -0
  236. package/dist/agents/57-export-tester.d.ts.map +1 -0
  237. package/dist/agents/57-export-tester.js +155 -0
  238. package/dist/agents/57-export-tester.js.map +1 -0
  239. package/dist/agents/58-payment-flow-tester.d.ts +11 -0
  240. package/dist/agents/58-payment-flow-tester.d.ts.map +1 -0
  241. package/dist/agents/58-payment-flow-tester.js +159 -0
  242. package/dist/agents/58-payment-flow-tester.js.map +1 -0
  243. package/dist/agents/59-ssl-tls-auditor.d.ts +10 -0
  244. package/dist/agents/59-ssl-tls-auditor.d.ts.map +1 -0
  245. package/dist/agents/59-ssl-tls-auditor.js +132 -0
  246. package/dist/agents/59-ssl-tls-auditor.js.map +1 -0
  247. package/dist/agents/60-dns-cdn-tester.d.ts +10 -0
  248. package/dist/agents/60-dns-cdn-tester.d.ts.map +1 -0
  249. package/dist/agents/60-dns-cdn-tester.js +105 -0
  250. package/dist/agents/60-dns-cdn-tester.js.map +1 -0
  251. package/dist/agents/61-docker-health-checker.d.ts +10 -0
  252. package/dist/agents/61-docker-health-checker.d.ts.map +1 -0
  253. package/dist/agents/61-docker-health-checker.js +95 -0
  254. package/dist/agents/61-docker-health-checker.js.map +1 -0
  255. package/dist/agents/62-env-config-validator.d.ts +10 -0
  256. package/dist/agents/62-env-config-validator.d.ts.map +1 -0
  257. package/dist/agents/62-env-config-validator.js +132 -0
  258. package/dist/agents/62-env-config-validator.js.map +1 -0
  259. package/dist/agents/63-log-quality-auditor.d.ts +9 -0
  260. package/dist/agents/63-log-quality-auditor.d.ts.map +1 -0
  261. package/dist/agents/63-log-quality-auditor.js +121 -0
  262. package/dist/agents/63-log-quality-auditor.js.map +1 -0
  263. package/dist/agents/64-analytics-tracker-tester.d.ts +10 -0
  264. package/dist/agents/64-analytics-tracker-tester.d.ts.map +1 -0
  265. package/dist/agents/64-analytics-tracker-tester.js +146 -0
  266. package/dist/agents/64-analytics-tracker-tester.js.map +1 -0
  267. package/dist/agents/65-gdpr-compliance-tester.d.ts +13 -0
  268. package/dist/agents/65-gdpr-compliance-tester.d.ts.map +1 -0
  269. package/dist/agents/65-gdpr-compliance-tester.js +186 -0
  270. package/dist/agents/65-gdpr-compliance-tester.js.map +1 -0
  271. package/dist/agents/66-soc2-control-validator.d.ts +14 -0
  272. package/dist/agents/66-soc2-control-validator.d.ts.map +1 -0
  273. package/dist/agents/66-soc2-control-validator.js +178 -0
  274. package/dist/agents/66-soc2-control-validator.js.map +1 -0
  275. package/dist/agents/67-wcag-aaa-tester.d.ts +13 -0
  276. package/dist/agents/67-wcag-aaa-tester.d.ts.map +1 -0
  277. package/dist/agents/67-wcag-aaa-tester.js +207 -0
  278. package/dist/agents/67-wcag-aaa-tester.js.map +1 -0
  279. package/dist/agents/68-dead-code-detector.d.ts +9 -0
  280. package/dist/agents/68-dead-code-detector.d.ts.map +1 -0
  281. package/dist/agents/68-dead-code-detector.js +116 -0
  282. package/dist/agents/68-dead-code-detector.js.map +1 -0
  283. package/dist/agents/69-type-safety-auditor.d.ts +9 -0
  284. package/dist/agents/69-type-safety-auditor.d.ts.map +1 -0
  285. package/dist/agents/69-type-safety-auditor.js +148 -0
  286. package/dist/agents/69-type-safety-auditor.js.map +1 -0
  287. package/dist/agents/70-complexity-analyzer.d.ts +10 -0
  288. package/dist/agents/70-complexity-analyzer.d.ts.map +1 -0
  289. package/dist/agents/70-complexity-analyzer.js +154 -0
  290. package/dist/agents/70-complexity-analyzer.js.map +1 -0
  291. package/dist/agents/registry.d.ts +3 -3
  292. package/dist/agents/registry.d.ts.map +1 -1
  293. package/dist/agents/registry.js +146 -5
  294. package/dist/agents/registry.js.map +1 -1
  295. package/dist/core/license.js +2 -5
  296. package/dist/core/license.js.map +1 -1
  297. package/dist/core/orchestrator.d.ts.map +1 -1
  298. package/dist/core/orchestrator.js +6 -4
  299. package/dist/core/orchestrator.js.map +1 -1
  300. package/package.json +2 -2
@@ -0,0 +1,179 @@
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
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
10
+ environments: { alpha: { baseUrl: 'https://example.com', seed: false } },
11
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
12
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
13
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
14
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
15
+ agentClassification: { blocking: [], advisory: [] },
16
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
17
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ const fetchMock = vi.fn();
23
+ vi.stubGlobal('fetch', fetchMock);
24
+
25
+ vi.mock('../../helpers/navigation', () => ({
26
+ login: vi.fn().mockResolvedValue(undefined),
27
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
28
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
29
+ switchPortal: vi.fn().mockResolvedValue(undefined),
30
+ }));
31
+
32
+ const httpsGetMock = vi.fn();
33
+ vi.mock('node:https', () => ({
34
+ get: (...args: unknown[]) => httpsGetMock(...args),
35
+ }));
36
+
37
+ const { SslTlsAuditorAgent } = await import('../59-ssl-tls-auditor');
38
+
39
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
40
+ return new SslTlsAuditorAgent(config, phase, '/tmp/test-run');
41
+ }
42
+
43
+ describe('SslTlsAuditorAgent', () => {
44
+ beforeEach(() => {
45
+ fetchMock.mockReset().mockResolvedValue({
46
+ ok: true,
47
+ status: 200,
48
+ headers: new Map([['strict-transport-security', 'max-age=31536000']]),
49
+ });
50
+ fetchMock.mockResolvedValue({
51
+ ok: true,
52
+ status: 200,
53
+ headers: { get: vi.fn().mockReturnValue('max-age=31536000') },
54
+ });
55
+ httpsGetMock.mockReset();
56
+ httpsGetMock.mockImplementation((_opts: unknown, cb: Function) => {
57
+ const mockRes = {
58
+ socket: {
59
+ getPeerCertificate: () => ({
60
+ valid_to: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toUTCString(),
61
+ }),
62
+ },
63
+ };
64
+ cb(mockRes);
65
+ return { on: vi.fn(), destroy: vi.fn() };
66
+ });
67
+ });
68
+
69
+ it('has agentId 59 and correct name', () => {
70
+ const agent = createAgent();
71
+ expect(agent.agentId).toBe(59);
72
+ expect(agent.agentName).toBe('SSL/TLS Auditor');
73
+ });
74
+
75
+ it('reports missing HSTS header', async () => {
76
+ fetchMock.mockResolvedValue({
77
+ ok: true, status: 200,
78
+ headers: { get: vi.fn().mockReturnValue(null) },
79
+ });
80
+ const agent = createAgent();
81
+ const result = await agent.run();
82
+ const finding = result.findings.find(f => f.id.includes('no-hsts'));
83
+ expect(finding).toBeDefined();
84
+ expect(finding!.severity).toBe('medium');
85
+ });
86
+
87
+ it('reports short HSTS max-age', async () => {
88
+ fetchMock.mockResolvedValue({
89
+ ok: true, status: 200,
90
+ headers: { get: vi.fn().mockReturnValue('max-age=3600') },
91
+ });
92
+ const agent = createAgent();
93
+ const result = await agent.run();
94
+ const finding = result.findings.find(f => f.id.includes('short-hsts'));
95
+ expect(finding).toBeDefined();
96
+ });
97
+
98
+ it('reports no HTTPS when base URL is HTTP', async () => {
99
+ const config = makeConfig({ environments: { alpha: { baseUrl: 'http://example.com', seed: false } } });
100
+ const agent = createAgent(config);
101
+ const result = await agent.run();
102
+ const finding = result.findings.find(f => f.id.includes('no-https'));
103
+ expect(finding).toBeDefined();
104
+ expect(finding!.severity).toBe('high');
105
+ });
106
+
107
+ it('reports cert expiring soon', async () => {
108
+ httpsGetMock.mockImplementation((_opts: unknown, cb: Function) => {
109
+ cb({
110
+ socket: {
111
+ getPeerCertificate: () => ({
112
+ valid_to: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000).toUTCString(),
113
+ }),
114
+ },
115
+ });
116
+ return { on: vi.fn(), destroy: vi.fn() };
117
+ });
118
+ const agent = createAgent();
119
+ const result = await agent.run();
120
+ const finding = result.findings.find(f => f.id.includes('cert-expiring-soon'));
121
+ expect(finding).toBeDefined();
122
+ expect(finding!.severity).toBe('high');
123
+ });
124
+
125
+ it('reports expired cert as critical', async () => {
126
+ httpsGetMock.mockImplementation((_opts: unknown, cb: Function) => {
127
+ cb({
128
+ socket: {
129
+ getPeerCertificate: () => ({
130
+ valid_to: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toUTCString(),
131
+ }),
132
+ },
133
+ });
134
+ return { on: vi.fn(), destroy: vi.fn() };
135
+ });
136
+ const agent = createAgent();
137
+ const result = await agent.run();
138
+ const finding = result.findings.find(f => f.id.includes('cert-expired'));
139
+ expect(finding).toBeDefined();
140
+ expect(finding!.severity).toBe('critical');
141
+ });
142
+
143
+ it('handles fetch failure', async () => {
144
+ fetchMock.mockRejectedValue(new Error('Fetch failed'));
145
+ const agent = createAgent();
146
+ const result = await agent.run();
147
+ const finding = result.findings.find(f => f.id.includes('fetch-failed'));
148
+ expect(finding).toBeDefined();
149
+ });
150
+
151
+ it('handles cert check failure', async () => {
152
+ httpsGetMock.mockImplementation((_opts: unknown, _cb: Function) => {
153
+ return {
154
+ on: (event: string, handler: Function) => {
155
+ if (event === 'error') handler(new Error('Cert error'));
156
+ },
157
+ destroy: vi.fn(),
158
+ };
159
+ });
160
+ const agent = createAgent();
161
+ const result = await agent.run();
162
+ const finding = result.findings.find(f => f.id.includes('cert-check-failed'));
163
+ expect(finding).toBeDefined();
164
+ });
165
+
166
+ it('no finding when cert is valid and HSTS present', async () => {
167
+ const agent = createAgent();
168
+ const result = await agent.run();
169
+ const certIssue = result.findings.find(f => f.id.includes('cert-expired') || f.id.includes('cert-expiring'));
170
+ expect(certIssue).toBeUndefined();
171
+ });
172
+
173
+ it('valid HSTS produces no finding', async () => {
174
+ const agent = createAgent();
175
+ const result = await agent.run();
176
+ const hstsFinding = result.findings.find(f => f.id.includes('no-hsts'));
177
+ expect(hstsFinding).toBeUndefined();
178
+ });
179
+ });
@@ -0,0 +1,176 @@
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
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
9
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
10
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
11
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
12
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
13
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
14
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
15
+ agentClassification: { blocking: [], advisory: [] },
16
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
17
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
18
+ ...overrides,
19
+ };
20
+ }
21
+
22
+ const fetchMock = vi.fn();
23
+ vi.stubGlobal('fetch', fetchMock);
24
+
25
+ vi.mock('../../helpers/navigation', () => ({
26
+ login: vi.fn().mockResolvedValue(undefined),
27
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
28
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
29
+ switchPortal: vi.fn().mockResolvedValue(undefined),
30
+ }));
31
+
32
+ const { DnsCdnTesterAgent } = await import('../60-dns-cdn-tester');
33
+
34
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
35
+ return new DnsCdnTesterAgent(config, phase, '/tmp/test-run');
36
+ }
37
+
38
+ describe('DnsCdnTesterAgent', () => {
39
+ beforeEach(() => {
40
+ fetchMock.mockReset();
41
+ fetchMock.mockResolvedValue({
42
+ ok: true, status: 200,
43
+ headers: { get: vi.fn().mockReturnValue(null) },
44
+ });
45
+ });
46
+
47
+ it('has agentId 60 and correct name', () => {
48
+ const agent = createAgent();
49
+ expect(agent.agentId).toBe(60);
50
+ expect(agent.agentName).toBe('DNS & CDN Tester');
51
+ });
52
+
53
+ it('reports no CDN when no CDN headers present', async () => {
54
+ const agent = createAgent();
55
+ const result = await agent.run();
56
+ const finding = result.findings.find(f => f.id.includes('no-cdn'));
57
+ expect(finding).toBeDefined();
58
+ expect(finding!.severity).toBe('low');
59
+ });
60
+
61
+ it('reports no cache-control header', async () => {
62
+ const agent = createAgent();
63
+ const result = await agent.run();
64
+ const finding = result.findings.find(f => f.id.includes('no-cache-control'));
65
+ expect(finding).toBeDefined();
66
+ });
67
+
68
+ it('detects Cloudflare CDN', async () => {
69
+ fetchMock.mockResolvedValue({
70
+ ok: true, status: 200,
71
+ headers: {
72
+ get: (name: string) => {
73
+ if (name === 'cf-ray') return 'abc123';
74
+ if (name === 'cache-control') return 'public, max-age=3600';
75
+ return null;
76
+ },
77
+ },
78
+ });
79
+ const agent = createAgent();
80
+ const result = await agent.run();
81
+ const cdnFinding = result.findings.find(f => f.id.includes('no-cdn'));
82
+ expect(cdnFinding).toBeUndefined();
83
+ });
84
+
85
+ it('reports slow response time', async () => {
86
+ // Simulate slow response by delaying fetch
87
+ fetchMock.mockImplementation(async () => {
88
+ await new Promise(resolve => setTimeout(resolve, 50));
89
+ return {
90
+ ok: true, status: 200,
91
+ headers: { get: vi.fn().mockReturnValue(null) },
92
+ };
93
+ });
94
+ // Can't easily test timing, but verify structure
95
+ const agent = createAgent();
96
+ const result = await agent.run();
97
+ expect(result.status).toBeDefined();
98
+ });
99
+
100
+ it('handles fetch failure', async () => {
101
+ fetchMock.mockRejectedValue(new Error('Network error'));
102
+ const agent = createAgent();
103
+ const result = await agent.run();
104
+ const finding = result.findings.find(f => f.id.includes('fetch-failed'));
105
+ expect(finding).toBeDefined();
106
+ });
107
+
108
+ it('reports cache-control present', async () => {
109
+ fetchMock.mockResolvedValue({
110
+ ok: true, status: 200,
111
+ headers: {
112
+ get: (name: string) => {
113
+ if (name === 'cache-control') return 'public, max-age=3600';
114
+ return null;
115
+ },
116
+ },
117
+ });
118
+ const agent = createAgent();
119
+ const result = await agent.run();
120
+ const finding = result.findings.find(f => f.id.includes('no-cache-control'));
121
+ expect(finding).toBeUndefined();
122
+ });
123
+
124
+ it('detects no-store cache control', async () => {
125
+ fetchMock.mockResolvedValue({
126
+ ok: true, status: 200,
127
+ headers: {
128
+ get: (name: string) => {
129
+ if (name === 'cache-control') return 'no-store';
130
+ return null;
131
+ },
132
+ },
133
+ });
134
+ const agent = createAgent();
135
+ const result = await agent.run();
136
+ expect(result.status).toBeDefined();
137
+ });
138
+
139
+ it('detects Vercel CDN', async () => {
140
+ fetchMock.mockResolvedValue({
141
+ ok: true, status: 200,
142
+ headers: {
143
+ get: (name: string) => {
144
+ if (name === 'x-vercel-cache') return 'HIT';
145
+ if (name === 'cache-control') return 'public';
146
+ return null;
147
+ },
148
+ },
149
+ });
150
+ const agent = createAgent();
151
+ const result = await agent.run();
152
+ const cdnFinding = result.findings.find(f => f.id.includes('no-cdn'));
153
+ expect(cdnFinding).toBeUndefined();
154
+ });
155
+
156
+ it('runs without error for valid response', async () => {
157
+ fetchMock.mockResolvedValue({
158
+ ok: true, status: 200,
159
+ headers: {
160
+ get: (name: string) => {
161
+ if (name === 'cache-control') return 'max-age=300';
162
+ return null;
163
+ },
164
+ },
165
+ });
166
+ const agent = createAgent();
167
+ const result = await agent.run();
168
+ expect(result.status).toBe('passed');
169
+ });
170
+
171
+ it('resolves environment in preFlight', async () => {
172
+ const agent = createAgent();
173
+ const result = await agent.run();
174
+ expect(result.findings.every(f => !f.description.includes('preFlight'))).toBe(true);
175
+ });
176
+ });
@@ -0,0 +1,150 @@
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
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ projectRoot: '/fake/project',
9
+ auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
10
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
11
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
12
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
13
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
14
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
15
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
16
+ agentClassification: { blocking: [], advisory: [] },
17
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
18
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 });
24
+ vi.stubGlobal('fetch', fetchMock);
25
+
26
+ vi.mock('../../helpers/navigation', () => ({
27
+ login: vi.fn().mockResolvedValue(undefined),
28
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
29
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
30
+ switchPortal: vi.fn().mockResolvedValue(undefined),
31
+ }));
32
+
33
+ const existsSyncMock = vi.fn();
34
+ const readFileSyncMock = vi.fn();
35
+ const statSyncMock = vi.fn();
36
+
37
+ vi.mock('node:fs', () => ({
38
+ existsSync: (...args: unknown[]) => existsSyncMock(...args),
39
+ readFileSync: (...args: unknown[]) => readFileSyncMock(...args),
40
+ statSync: (...args: unknown[]) => statSyncMock(...args),
41
+ }));
42
+
43
+ const { DockerHealthCheckerAgent } = await import('../61-docker-health-checker');
44
+
45
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
46
+ return new DockerHealthCheckerAgent(config, phase, '/tmp/test-run');
47
+ }
48
+
49
+ describe('DockerHealthCheckerAgent', () => {
50
+ beforeEach(() => {
51
+ existsSyncMock.mockReset();
52
+ readFileSyncMock.mockReset();
53
+ statSyncMock.mockReset().mockReturnValue({ size: 500 });
54
+ });
55
+
56
+ it('has agentId 61 and correct name', () => {
57
+ const agent = createAgent();
58
+ expect(agent.agentId).toBe(61);
59
+ expect(agent.agentName).toBe('Docker Health Checker');
60
+ });
61
+
62
+ it('reports no Dockerfile when not found', async () => {
63
+ existsSyncMock.mockReturnValue(false);
64
+ const agent = createAgent();
65
+ const result = await agent.run();
66
+ const finding = result.findings.find(f => f.id.includes('no-dockerfile'));
67
+ expect(finding).toBeDefined();
68
+ });
69
+
70
+ it('reports USER root as high severity', async () => {
71
+ existsSyncMock.mockImplementation((p: string) => p.includes('Dockerfile'));
72
+ readFileSyncMock.mockReturnValue('FROM node:18\nCOPY . .\nUSER root\nCMD ["node", "app.js"]');
73
+ const agent = createAgent();
74
+ const result = await agent.run();
75
+ const finding = result.findings.find(f => f.id.includes('user-root'));
76
+ expect(finding).toBeDefined();
77
+ expect(finding!.severity).toBe('high');
78
+ });
79
+
80
+ it('reports missing HEALTHCHECK', async () => {
81
+ existsSyncMock.mockImplementation((p: string) => p.includes('Dockerfile') || p.includes('.dockerignore'));
82
+ readFileSyncMock.mockReturnValue('FROM node:18\nUSER app\nCMD ["node", "app.js"]');
83
+ const agent = createAgent();
84
+ const result = await agent.run();
85
+ const finding = result.findings.find(f => f.id.includes('no-healthcheck'));
86
+ expect(finding).toBeDefined();
87
+ expect(finding!.severity).toBe('medium');
88
+ });
89
+
90
+ it('reports missing .dockerignore', async () => {
91
+ existsSyncMock.mockImplementation((p: string) => p.includes('Dockerfile') && !p.includes('.dockerignore'));
92
+ readFileSyncMock.mockReturnValue('FROM node:18\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1\nUSER app\nCMD ["node", "app.js"]');
93
+ const agent = createAgent();
94
+ const result = await agent.run();
95
+ const finding = result.findings.find(f => f.id.includes('no-dockerignore'));
96
+ expect(finding).toBeDefined();
97
+ expect(finding!.severity).toBe('low');
98
+ });
99
+
100
+ it('no findings when Dockerfile is well configured', async () => {
101
+ existsSyncMock.mockReturnValue(true);
102
+ readFileSyncMock.mockReturnValue('FROM node:18\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1\nUSER app\nCMD ["node", "app.js"]');
103
+ const agent = createAgent();
104
+ const result = await agent.run();
105
+ expect(result.findings.filter(f => f.severity === 'high' || f.severity === 'critical').length).toBe(0);
106
+ });
107
+
108
+ it('handles read error gracefully', async () => {
109
+ existsSyncMock.mockReturnValue(true);
110
+ readFileSyncMock.mockImplementation(() => { throw new Error('Read error'); });
111
+ const agent = createAgent();
112
+ const result = await agent.run();
113
+ expect(result.status).toBe('failed');
114
+ });
115
+
116
+ it('adds evidence with Dockerfile size', async () => {
117
+ existsSyncMock.mockReturnValue(true);
118
+ readFileSyncMock.mockReturnValue('FROM node:18\nHEALTHCHECK CMD curl\nUSER app');
119
+ statSyncMock.mockReturnValue({ size: 150 });
120
+ const agent = createAgent();
121
+ const result = await agent.run();
122
+ expect(result.evidence.length).toBeGreaterThan(0);
123
+ });
124
+
125
+ it('detects HEALTHCHECK when present', async () => {
126
+ existsSyncMock.mockReturnValue(true);
127
+ readFileSyncMock.mockReturnValue('FROM node:18\nHEALTHCHECK CMD curl -f http://localhost/ || exit 1\nUSER app');
128
+ const agent = createAgent();
129
+ const result = await agent.run();
130
+ const finding = result.findings.find(f => f.id.includes('no-healthcheck'));
131
+ expect(finding).toBeUndefined();
132
+ });
133
+
134
+ it('detects non-root USER as safe', async () => {
135
+ existsSyncMock.mockReturnValue(true);
136
+ readFileSyncMock.mockReturnValue('FROM node:18\nHEALTHCHECK CMD curl\nUSER appuser');
137
+ const agent = createAgent();
138
+ const result = await agent.run();
139
+ const finding = result.findings.find(f => f.id.includes('user-root'));
140
+ expect(finding).toBeUndefined();
141
+ });
142
+
143
+ it('handles empty Dockerfile', async () => {
144
+ existsSyncMock.mockReturnValue(true);
145
+ readFileSyncMock.mockReturnValue('');
146
+ const agent = createAgent();
147
+ const result = await agent.run();
148
+ expect(result.status).toBeDefined();
149
+ });
150
+ });
@@ -0,0 +1,166 @@
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
+ function makeConfig(overrides: Partial<ValidatedConfig> = {}): ValidatedConfig {
6
+ return {
7
+ schemaVersion: 1, name: 'test',
8
+ projectRoot: '/fake/project',
9
+ auth: { loginUrl: '/login', credentials: { admin: { email: 'a@t.com', password: 's' } } },
10
+ modules: [{ id: 'dashboard', route: '/dashboard', sidebarIcon: true }],
11
+ environments: { alpha: { baseUrl: 'http://localhost:3000', seed: false } },
12
+ portals: [], breakpoints: [], integrations: [], workers: [], workflows: [],
13
+ accuracy: { enabled: false, decimalPrecision: 2, currencySymbol: '$', timezone: 'UTC' },
14
+ tenancy: { enabled: false, isolationField: 'firmId', testFirms: [] },
15
+ costBudget: { maxPerRun: 100, maxPerAgent: 10, alertThreshold: 0.8, onExceeded: 'abort' },
16
+ agentClassification: { blocking: [], advisory: [] },
17
+ thresholds: { unitCoverage: { lines: 80, functions: 80, branches: 80, statements: 80 }, lighthouse: { accessibility: 90, performance: 90, bestPractices: 90, seo: 90 }, apiResponseTime: 2000, bundleSizeLimit: 500000, maxConsoleErrors: 0 },
18
+ disabledAgents: [], disabledAgentSet: new Set<number>(),
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200 });
24
+ vi.stubGlobal('fetch', fetchMock);
25
+
26
+ vi.mock('../../helpers/navigation', () => ({
27
+ login: vi.fn().mockResolvedValue(undefined),
28
+ waitForSectionLoad: vi.fn().mockResolvedValue(undefined),
29
+ navigateToSection: vi.fn().mockResolvedValue(undefined),
30
+ switchPortal: vi.fn().mockResolvedValue(undefined),
31
+ }));
32
+
33
+ const existsSyncMock = vi.fn();
34
+ const readFileSyncMock = vi.fn();
35
+
36
+ vi.mock('node:fs', () => ({
37
+ existsSync: (...args: unknown[]) => existsSyncMock(...args),
38
+ readFileSync: (...args: unknown[]) => readFileSyncMock(...args),
39
+ }));
40
+
41
+ const { EnvConfigValidatorAgent } = await import('../62-env-config-validator');
42
+
43
+ function createAgent(config: ValidatedConfig = makeConfig(), phase: Phase = 'beta') {
44
+ return new EnvConfigValidatorAgent(config, phase, '/tmp/test-run');
45
+ }
46
+
47
+ describe('EnvConfigValidatorAgent', () => {
48
+ beforeEach(() => {
49
+ existsSyncMock.mockReset();
50
+ readFileSyncMock.mockReset();
51
+ });
52
+
53
+ it('has agentId 62 and correct name', () => {
54
+ const agent = createAgent();
55
+ expect(agent.agentId).toBe(62);
56
+ expect(agent.agentName).toBe('Env Config Validator');
57
+ });
58
+
59
+ it('reports no env files when none found', async () => {
60
+ existsSyncMock.mockReturnValue(false);
61
+ const agent = createAgent();
62
+ const result = await agent.run();
63
+ const finding = result.findings.find(f => f.id.includes('no-env-files'));
64
+ expect(finding).toBeDefined();
65
+ });
66
+
67
+ it('reports missing env var when defined in example but not env', async () => {
68
+ existsSyncMock.mockReturnValue(true);
69
+ readFileSyncMock.mockImplementation((p: string) => {
70
+ if (p.includes('.env.example')) return 'DB_HOST=localhost\nDB_PORT=5432\nAPI_KEY=secret';
71
+ return 'DB_HOST=localhost';
72
+ });
73
+ const agent = createAgent();
74
+ const result = await agent.run();
75
+ const finding = result.findings.find(f => f.id.includes('missing-env-var'));
76
+ expect(finding).toBeDefined();
77
+ });
78
+
79
+ it('reports invalid URL format', async () => {
80
+ existsSyncMock.mockReturnValue(true);
81
+ readFileSyncMock.mockImplementation((p: string) => {
82
+ if (p.includes('.env.example')) return 'API_URL=url';
83
+ return 'API_URL=not-a-url';
84
+ });
85
+ const agent = createAgent();
86
+ const result = await agent.run();
87
+ const finding = result.findings.find(f => f.id.includes('invalid-url'));
88
+ expect(finding).toBeDefined();
89
+ });
90
+
91
+ it('reports invalid port format', async () => {
92
+ existsSyncMock.mockReturnValue(true);
93
+ readFileSyncMock.mockImplementation((p: string) => {
94
+ if (p.includes('.env.example')) return 'DB_PORT=5432';
95
+ return 'DB_PORT=abc';
96
+ });
97
+ const agent = createAgent();
98
+ const result = await agent.run();
99
+ const finding = result.findings.find(f => f.id.includes('invalid-port'));
100
+ expect(finding).toBeDefined();
101
+ });
102
+
103
+ it('reports placeholder values', async () => {
104
+ existsSyncMock.mockReturnValue(true);
105
+ readFileSyncMock.mockImplementation((p: string) => {
106
+ if (p.includes('.env.example')) return 'SECRET=change';
107
+ return 'SECRET=your-secret-here';
108
+ });
109
+ const agent = createAgent();
110
+ const result = await agent.run();
111
+ const finding = result.findings.find(f => f.id.includes('placeholder'));
112
+ expect(finding).toBeDefined();
113
+ });
114
+
115
+ it('no findings when env matches example', async () => {
116
+ existsSyncMock.mockReturnValue(true);
117
+ readFileSyncMock.mockImplementation((p: string) => {
118
+ if (p.includes('.env.example')) return 'DB_HOST=\nDB_PORT=';
119
+ return 'DB_HOST=localhost\nDB_PORT=5432';
120
+ });
121
+ const agent = createAgent();
122
+ const result = await agent.run();
123
+ const missing = result.findings.find(f => f.id.includes('missing-env-var'));
124
+ expect(missing).toBeUndefined();
125
+ });
126
+
127
+ it('handles only .env existing (no example)', async () => {
128
+ existsSyncMock.mockImplementation((p: string) => !p.includes('.env.example'));
129
+ readFileSyncMock.mockReturnValue('DB_HOST=localhost');
130
+ const agent = createAgent();
131
+ const result = await agent.run();
132
+ expect(result.status).toBeDefined();
133
+ });
134
+
135
+ it('handles only .env.example existing', async () => {
136
+ existsSyncMock.mockImplementation((p: string) => p.includes('.env.example'));
137
+ readFileSyncMock.mockReturnValue('DB_HOST=localhost');
138
+ const agent = createAgent();
139
+ const result = await agent.run();
140
+ expect(result.status).toBeDefined();
141
+ });
142
+
143
+ it('skips comments and empty lines', async () => {
144
+ existsSyncMock.mockReturnValue(true);
145
+ readFileSyncMock.mockImplementation((p: string) => {
146
+ if (p.includes('.env.example')) return '# Comment\n\nDB_HOST=localhost';
147
+ return '# Comment\n\nDB_HOST=localhost';
148
+ });
149
+ const agent = createAgent();
150
+ const result = await agent.run();
151
+ const missing = result.findings.find(f => f.id.includes('missing-env-var'));
152
+ expect(missing).toBeUndefined();
153
+ });
154
+
155
+ it('valid URL passes validation', async () => {
156
+ existsSyncMock.mockReturnValue(true);
157
+ readFileSyncMock.mockImplementation((p: string) => {
158
+ if (p.includes('.env.example')) return 'API_URL=url';
159
+ return 'API_URL=https://api.example.com';
160
+ });
161
+ const agent = createAgent();
162
+ const result = await agent.run();
163
+ const finding = result.findings.find(f => f.id.includes('invalid-url'));
164
+ expect(finding).toBeUndefined();
165
+ });
166
+ });