@silasfmartins/testhub 1.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 (296) hide show
  1. package/.github/copilot-instructions.md +520 -0
  2. package/biome.json +37 -0
  3. package/dist/index.d.ts +45 -0
  4. package/dist/index.js +169 -0
  5. package/dist/scripts/consumer-postinstall.d.ts +15 -0
  6. package/dist/scripts/consumer-postinstall.js +785 -0
  7. package/dist/scripts/generate-docs.d.ts +16 -0
  8. package/dist/scripts/generate-docs.js +1363 -0
  9. package/dist/scripts/generate-index.d.ts +2 -0
  10. package/dist/scripts/generate-index.js +314 -0
  11. package/dist/scripts/init-api.d.ts +2 -0
  12. package/dist/scripts/init-api.js +525 -0
  13. package/dist/scripts/init-banco.d.ts +2 -0
  14. package/dist/scripts/init-banco.js +347 -0
  15. package/dist/scripts/init-frontend.d.ts +2 -0
  16. package/dist/scripts/init-frontend.js +627 -0
  17. package/dist/scripts/init-mobile.d.ts +2 -0
  18. package/dist/scripts/init-mobile.js +481 -0
  19. package/dist/scripts/init-scenarios.d.ts +2 -0
  20. package/dist/scripts/init-scenarios.js +846 -0
  21. package/dist/scripts/init-ssh.d.ts +2 -0
  22. package/dist/scripts/init-ssh.js +639 -0
  23. package/dist/scripts/package-versions.d.ts +57 -0
  24. package/dist/scripts/package-versions.js +768 -0
  25. package/dist/scripts/postinstall.d.ts +1 -0
  26. package/dist/scripts/postinstall.js +527 -0
  27. package/dist/scripts/robust-build.d.ts +7 -0
  28. package/dist/scripts/robust-build.js +88 -0
  29. package/dist/scripts/setup-local-packages.d.ts +31 -0
  30. package/dist/scripts/setup-local-packages.js +237 -0
  31. package/dist/scripts/smart-override.d.ts +2 -0
  32. package/dist/scripts/smart-override.js +1360 -0
  33. package/dist/scripts/sync-configs.d.ts +27 -0
  34. package/dist/scripts/sync-configs.js +248 -0
  35. package/dist/scripts/test-biome-parse.d.ts +5 -0
  36. package/dist/scripts/test-biome-parse.js +84 -0
  37. package/dist/scripts/ultracite-setup.d.ts +4 -0
  38. package/dist/scripts/ultracite-setup.js +310 -0
  39. package/dist/scripts/update-all-init-scripts.d.ts +2 -0
  40. package/dist/scripts/update-all-init-scripts.js +52 -0
  41. package/dist/scripts/update-biome-schema.d.ts +15 -0
  42. package/dist/scripts/update-biome-schema.js +124 -0
  43. package/dist/src/AutoCoreFacade.d.ts +145 -0
  44. package/dist/src/AutoCoreFacade.js +217 -0
  45. package/dist/src/api/ApiActions.d.ts +297 -0
  46. package/dist/src/api/ApiActions.js +1905 -0
  47. package/dist/src/api/Certificate.d.ts +60 -0
  48. package/dist/src/api/Certificate.js +79 -0
  49. package/dist/src/api/JsonResponse.d.ts +116 -0
  50. package/dist/src/api/JsonResponse.js +206 -0
  51. package/dist/src/appium/DeviceFarmViewer.d.ts +79 -0
  52. package/dist/src/appium/DeviceFarmViewer.js +1083 -0
  53. package/dist/src/appium/MobileActions.d.ts +347 -0
  54. package/dist/src/appium/MobileActions.js +1632 -0
  55. package/dist/src/appium/MobileConnection.d.ts +160 -0
  56. package/dist/src/appium/MobileConnection.js +772 -0
  57. package/dist/src/config/envLoader.d.ts +123 -0
  58. package/dist/src/config/envLoader.js +361 -0
  59. package/dist/src/config/jest-safe-setup.d.ts +19 -0
  60. package/dist/src/config/jest-safe-setup.js +369 -0
  61. package/dist/src/config/timeouts.d.ts +32 -0
  62. package/dist/src/config/timeouts.js +38 -0
  63. package/dist/src/desktop/DesktopActions.d.ts +46 -0
  64. package/dist/src/desktop/DesktopActions.js +398 -0
  65. package/dist/src/desktop/DesktopConnection.d.ts +32 -0
  66. package/dist/src/desktop/DesktopConnection.js +84 -0
  67. package/dist/src/domain/entities/TestExecution.d.ts +117 -0
  68. package/dist/src/domain/entities/TestExecution.js +150 -0
  69. package/dist/src/domain/entities/TestReport.d.ts +114 -0
  70. package/dist/src/domain/entities/TestReport.js +179 -0
  71. package/dist/src/domain/repositories/ITestRepository.d.ts +196 -0
  72. package/dist/src/domain/repositories/ITestRepository.js +14 -0
  73. package/dist/src/domain/schemas/ValidationSchemas.d.ts +159 -0
  74. package/dist/src/domain/schemas/ValidationSchemas.js +181 -0
  75. package/dist/src/functions/errors/BaseError.d.ts +78 -0
  76. package/dist/src/functions/errors/BaseError.js +245 -0
  77. package/dist/src/functions/errors/ConfigurationError.d.ts +16 -0
  78. package/dist/src/functions/errors/ConfigurationError.js +48 -0
  79. package/dist/src/functions/errors/ErrorCatalog.d.ts +148 -0
  80. package/dist/src/functions/errors/ErrorCatalog.js +157 -0
  81. package/dist/src/functions/errors/GlobalErrorHandler.d.ts +101 -0
  82. package/dist/src/functions/errors/GlobalErrorHandler.js +281 -0
  83. package/dist/src/functions/errors/IntegrationError.d.ts +17 -0
  84. package/dist/src/functions/errors/IntegrationError.js +51 -0
  85. package/dist/src/functions/errors/SecurityError.d.ts +14 -0
  86. package/dist/src/functions/errors/SecurityError.js +42 -0
  87. package/dist/src/functions/errors/SystemError.d.ts +12 -0
  88. package/dist/src/functions/errors/SystemError.js +36 -0
  89. package/dist/src/functions/errors/ValidationError.d.ts +14 -0
  90. package/dist/src/functions/errors/ValidationError.js +61 -0
  91. package/dist/src/functions/errors/index.d.ts +12 -0
  92. package/dist/src/functions/errors/index.js +13 -0
  93. package/dist/src/global-setup.d.ts +1 -0
  94. package/dist/src/global-setup.js +1037 -0
  95. package/dist/src/helpers/BancoActions.d.ts +188 -0
  96. package/dist/src/helpers/BancoActions.js +581 -0
  97. package/dist/src/helpers/EnviromentHelper.d.ts +17 -0
  98. package/dist/src/helpers/EnviromentHelper.js +66 -0
  99. package/dist/src/helpers/ParallelExecutionHelper.d.ts +183 -0
  100. package/dist/src/helpers/ParallelExecutionHelper.js +375 -0
  101. package/dist/src/helpers/SyncSignal.d.ts +15 -0
  102. package/dist/src/helpers/SyncSignal.js +44 -0
  103. package/dist/src/hubdocs/CategoryDetector.d.ts +83 -0
  104. package/dist/src/hubdocs/CategoryDetector.js +401 -0
  105. package/dist/src/hubdocs/DirectStatementInterceptor.d.ts +54 -0
  106. package/dist/src/hubdocs/DirectStatementInterceptor.js +243 -0
  107. package/dist/src/hubdocs/ExecutionTracker.d.ts +107 -0
  108. package/dist/src/hubdocs/ExecutionTracker.js +702 -0
  109. package/dist/src/hubdocs/HubDocs.d.ts +395 -0
  110. package/dist/src/hubdocs/HubDocs.js +3586 -0
  111. package/dist/src/hubdocs/StatementMethodFilter.d.ts +71 -0
  112. package/dist/src/hubdocs/StatementMethodFilter.js +618 -0
  113. package/dist/src/hubdocs/StatementTracker.d.ts +417 -0
  114. package/dist/src/hubdocs/StatementTracker.js +2419 -0
  115. package/dist/src/hubdocs/SwaggerGenerator.d.ts +59 -0
  116. package/dist/src/hubdocs/SwaggerGenerator.js +405 -0
  117. package/dist/src/hubdocs/index.d.ts +9 -0
  118. package/dist/src/hubdocs/index.js +9 -0
  119. package/dist/src/hubdocs/types.d.ts +114 -0
  120. package/dist/src/hubdocs/types.js +5 -0
  121. package/dist/src/infrastructure/DependencyContainer.d.ts +142 -0
  122. package/dist/src/infrastructure/DependencyContainer.js +250 -0
  123. package/dist/src/infrastructure/adapters/AppiumAdapter.d.ts +168 -0
  124. package/dist/src/infrastructure/adapters/AppiumAdapter.js +468 -0
  125. package/dist/src/infrastructure/adapters/OracleAdapter.d.ts +150 -0
  126. package/dist/src/infrastructure/adapters/OracleAdapter.js +388 -0
  127. package/dist/src/infrastructure/adapters/PlaywrightAdapter.d.ts +192 -0
  128. package/dist/src/infrastructure/adapters/PlaywrightAdapter.js +382 -0
  129. package/dist/src/infrastructure/adapters/SSHAdapter.d.ts +141 -0
  130. package/dist/src/infrastructure/adapters/SSHAdapter.js +428 -0
  131. package/dist/src/interfaces.d.ts +501 -0
  132. package/dist/src/interfaces.js +25 -0
  133. package/dist/src/internal/fakes/__fake-actions__.d.ts +17 -0
  134. package/dist/src/internal/fakes/__fake-actions__.js +21 -0
  135. package/dist/src/internal/fakes/__forbidden__.d.ts +10 -0
  136. package/dist/src/internal/fakes/__forbidden__.js +18 -0
  137. package/dist/src/internal/fakes/__honeypot__.d.ts +15 -0
  138. package/dist/src/internal/fakes/__honeypot__.js +24 -0
  139. package/dist/src/octane/OctaneReporter.d.ts +13 -0
  140. package/dist/src/octane/OctaneReporter.js +61 -0
  141. package/dist/src/playwright/CryptoActions.d.ts +20 -0
  142. package/dist/src/playwright/CryptoActions.js +75 -0
  143. package/dist/src/playwright/EnhancedWebActions.d.ts +7 -0
  144. package/dist/src/playwright/EnhancedWebActions.js +65 -0
  145. package/dist/src/playwright/WebActions.d.ts +1599 -0
  146. package/dist/src/playwright/WebActions.js +11788 -0
  147. package/dist/src/playwright/actions/ActionTimeline.d.ts +36 -0
  148. package/dist/src/playwright/actions/ActionTimeline.js +101 -0
  149. package/dist/src/playwright/actions/RecoveryQueue.d.ts +82 -0
  150. package/dist/src/playwright/actions/RecoveryQueue.js +130 -0
  151. package/dist/src/playwright/actions/SelectorCache.d.ts +53 -0
  152. package/dist/src/playwright/actions/SelectorCache.js +96 -0
  153. package/dist/src/playwright/actions/index.d.ts +13 -0
  154. package/dist/src/playwright/actions/index.js +14 -0
  155. package/dist/src/playwright/actions/types.d.ts +147 -0
  156. package/dist/src/playwright/actions/types.js +5 -0
  157. package/dist/src/playwright/fixtures.d.ts +112 -0
  158. package/dist/src/playwright/fixtures.js +718 -0
  159. package/dist/src/playwright/network-logs-reporter.d.ts +7 -0
  160. package/dist/src/playwright/network-logs-reporter.js +66 -0
  161. package/dist/src/playwright/registerRecoveryWrappers.d.ts +1 -0
  162. package/dist/src/playwright/registerRecoveryWrappers.js +54 -0
  163. package/dist/src/security/BuildSecurity.d.ts +12 -0
  164. package/dist/src/security/BuildSecurity.js +138 -0
  165. package/dist/src/security/EulaProtection.d.ts +70 -0
  166. package/dist/src/security/EulaProtection.js +155 -0
  167. package/dist/src/security/HoneypotManager.d.ts +46 -0
  168. package/dist/src/security/HoneypotManager.js +234 -0
  169. package/dist/src/security/KeysManager.d.ts +36 -0
  170. package/dist/src/security/KeysManager.js +158 -0
  171. package/dist/src/security/ProofOfWorkIntegration.d.ts +64 -0
  172. package/dist/src/security/ProofOfWorkIntegration.js +206 -0
  173. package/dist/src/security/SecurityValidation.d.ts +21 -0
  174. package/dist/src/security/SecurityValidation.js +163 -0
  175. package/dist/src/security/SourceMapProtection.d.ts +55 -0
  176. package/dist/src/security/SourceMapProtection.js +220 -0
  177. package/dist/src/security/protector.d.ts +1 -0
  178. package/dist/src/security/protector.js +97 -0
  179. package/dist/src/ssh/SSHActions.d.ts +262 -0
  180. package/dist/src/ssh/SSHActions.js +790 -0
  181. package/dist/src/ssh/SSHClient.d.ts +99 -0
  182. package/dist/src/ssh/SSHClient.js +409 -0
  183. package/dist/src/statements/BaseStatement.d.ts +38 -0
  184. package/dist/src/statements/BaseStatement.js +78 -0
  185. package/dist/src/testContext/AuthStateManager.d.ts +93 -0
  186. package/dist/src/testContext/AuthStateManager.js +256 -0
  187. package/dist/src/testContext/CoverageManager.d.ts +198 -0
  188. package/dist/src/testContext/CoverageManager.js +917 -0
  189. package/dist/src/testContext/TestAnnotations.d.ts +476 -0
  190. package/dist/src/testContext/TestAnnotations.js +2647 -0
  191. package/dist/src/testContext/TestContext.d.ts +138 -0
  192. package/dist/src/testContext/TestContext.js +369 -0
  193. package/dist/src/testContext/UnifiedHtmlGenerator.d.ts +7 -0
  194. package/dist/src/testContext/UnifiedHtmlGenerator.js +264 -0
  195. package/dist/src/testContext/UnifiedReportManager.d.ts +211 -0
  196. package/dist/src/testContext/UnifiedReportManager.js +1206 -0
  197. package/dist/src/testhub/DynamicConfigManager.d.ts +121 -0
  198. package/dist/src/testhub/DynamicConfigManager.js +320 -0
  199. package/dist/src/testhub/SystemsManager.d.ts +119 -0
  200. package/dist/src/testhub/SystemsManager.js +365 -0
  201. package/dist/src/testhub/TestHubClient.d.ts +335 -0
  202. package/dist/src/testhub/TestHubClient.js +1215 -0
  203. package/dist/src/testhub/TestHubReporter.d.ts +62 -0
  204. package/dist/src/testhub/TestHubReporter.js +576 -0
  205. package/dist/src/testhub/TestHubVars.d.ts +116 -0
  206. package/dist/src/testhub/TestHubVars.js +273 -0
  207. package/dist/src/utils/ActionInterceptor.d.ts +59 -0
  208. package/dist/src/utils/ActionInterceptor.js +741 -0
  209. package/dist/src/utils/ArtifactsCompressor.d.ts +43 -0
  210. package/dist/src/utils/ArtifactsCompressor.js +181 -0
  211. package/dist/src/utils/AutoLogsFinal.d.ts +47 -0
  212. package/dist/src/utils/AutoLogsFinal.js +148 -0
  213. package/dist/src/utils/CodeGenSession.d.ts +114 -0
  214. package/dist/src/utils/CodeGenSession.js +264 -0
  215. package/dist/src/utils/ConfigLogger.d.ts +133 -0
  216. package/dist/src/utils/ConfigLogger.js +611 -0
  217. package/dist/src/utils/CustomReporter.d.ts +22 -0
  218. package/dist/src/utils/CustomReporter.js +352 -0
  219. package/dist/src/utils/DataStore.d.ts +171 -0
  220. package/dist/src/utils/DataStore.js +484 -0
  221. package/dist/src/utils/DatabaseInterceptor.d.ts +19 -0
  222. package/dist/src/utils/DatabaseInterceptor.js +295 -0
  223. package/dist/src/utils/DateHelper.d.ts +16 -0
  224. package/dist/src/utils/DateHelper.js +120 -0
  225. package/dist/src/utils/DateValidator.d.ts +4 -0
  226. package/dist/src/utils/DateValidator.js +51 -0
  227. package/dist/src/utils/DocumentGenerator.d.ts +35 -0
  228. package/dist/src/utils/DocumentGenerator.js +129 -0
  229. package/dist/src/utils/EvidenceCapture.d.ts +90 -0
  230. package/dist/src/utils/EvidenceCapture.js +600 -0
  231. package/dist/src/utils/EvidenceReportGenerator.d.ts +70 -0
  232. package/dist/src/utils/EvidenceReportGenerator.js +799 -0
  233. package/dist/src/utils/FrameManagementUtil.d.ts +42 -0
  234. package/dist/src/utils/FrameManagementUtil.js +75 -0
  235. package/dist/src/utils/GlobalStatementsInterceptor.d.ts +1 -0
  236. package/dist/src/utils/GlobalStatementsInterceptor.js +1 -0
  237. package/dist/src/utils/HTMLTemplate.d.ts +1 -0
  238. package/dist/src/utils/HTMLTemplate.js +1034 -0
  239. package/dist/src/utils/InterceptacaoMagica.d.ts +23 -0
  240. package/dist/src/utils/InterceptacaoMagica.js +365 -0
  241. package/dist/src/utils/LogSanitizer.d.ts +35 -0
  242. package/dist/src/utils/LogSanitizer.js +110 -0
  243. package/dist/src/utils/Logger.d.ts +65 -0
  244. package/dist/src/utils/Logger.js +284 -0
  245. package/dist/src/utils/McpLocalClient.d.ts +141 -0
  246. package/dist/src/utils/McpLocalClient.js +871 -0
  247. package/dist/src/utils/PDFEvidenceGenerator.d.ts +20 -0
  248. package/dist/src/utils/PDFEvidenceGenerator.js +156 -0
  249. package/dist/src/utils/SpecFileAnalyzer.d.ts +35 -0
  250. package/dist/src/utils/SpecFileAnalyzer.js +209 -0
  251. package/dist/src/utils/StatementInterceptor.d.ts +18 -0
  252. package/dist/src/utils/StatementInterceptor.js +87 -0
  253. package/dist/src/utils/StatementLogger.d.ts +33 -0
  254. package/dist/src/utils/StatementLogger.js +113 -0
  255. package/dist/src/utils/StatementsInterceptor.d.ts +1 -0
  256. package/dist/src/utils/StatementsInterceptor.js +1 -0
  257. package/dist/src/utils/TeamsFlushHook.d.ts +17 -0
  258. package/dist/src/utils/TeamsFlushHook.js +168 -0
  259. package/dist/src/utils/TerminalLogCapture.d.ts +158 -0
  260. package/dist/src/utils/TerminalLogCapture.js +531 -0
  261. package/dist/src/utils/TestMethodLogger.d.ts +70 -0
  262. package/dist/src/utils/TestMethodLogger.js +95 -0
  263. package/dist/src/utils/UnifiedTeardown.d.ts +4 -0
  264. package/dist/src/utils/UnifiedTeardown.js +400 -0
  265. package/dist/src/utils/XPathCatalog.d.ts +152 -0
  266. package/dist/src/utils/XPathCatalog.js +350 -0
  267. package/dist/src/utils/generators.d.ts +90 -0
  268. package/dist/src/utils/generators.js +167 -0
  269. package/dist/src/utils/testRecovery/ResilientPlaywright.d.ts +152 -0
  270. package/dist/src/utils/testRecovery/ResilientPlaywright.js +715 -0
  271. package/dist/src/utils/testRecovery/TestRecoveryClient.d.ts +801 -0
  272. package/dist/src/utils/testRecovery/TestRecoveryClient.js +1415 -0
  273. package/dist/src/utils/testRecovery/autoFixCode.d.ts +65 -0
  274. package/dist/src/utils/testRecovery/autoFixCode.js +32 -0
  275. package/dist/vitest.config.d.ts +2 -0
  276. package/dist/vitest.config.js +59 -0
  277. package/dist/wdio.conf.d.ts +1 -0
  278. package/dist/wdio.conf.js +420 -0
  279. package/package.json +137 -0
  280. package/protect-loader.mjs +643 -0
  281. package/scripts/consumer-postinstall.ts +975 -0
  282. package/scripts/generate-index.ts +343 -0
  283. package/scripts/init-api.ts +613 -0
  284. package/scripts/init-banco.ts +437 -0
  285. package/scripts/init-frontend.ts +727 -0
  286. package/scripts/init-mobile.ts +558 -0
  287. package/scripts/init-scenarios.ts +925 -0
  288. package/scripts/init-ssh.ts +734 -0
  289. package/scripts/package-versions.ts +978 -0
  290. package/scripts/postinstall.ts +605 -0
  291. package/scripts/smart-override.ts +1675 -0
  292. package/scripts/sync-configs.ts +302 -0
  293. package/scripts/ultracite-setup.ts +370 -0
  294. package/src/types/globals.d.ts +48 -0
  295. package/tsconfig.json +29 -0
  296. package/types/autocore-sync-signal.d.ts +10 -0
@@ -0,0 +1,1215 @@
1
+ /**
2
+ * TestHubClient: Cliente HTTP para comunicação com o backend TestHub
3
+ * Responsável por buscar sistemas e enviar resultados de execução
4
+ *
5
+ * NOTA: Usa fetch nativo do Node.js (sem axios) para evitar problemas com proxy corporativo
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { Logger } from '../utils/Logger.js';
10
+ /**
11
+ * Detecta se está rodando em ambiente Azure/Linux
12
+ * @returns true se for Azure/Linux, false se for Windows local
13
+ */
14
+ function isAzureEnvironment() {
15
+ // Consider Azure/CI when there are explicit CI pipeline signals.
16
+ // Do NOT classify 'darwin' (macOS) as Azure automatically.
17
+ if (process.env.TF_BUILD === 'True')
18
+ return true;
19
+ if (process.env.AGENT_NAME)
20
+ return true;
21
+ if (process.env.BUILD_BUILDID)
22
+ return true;
23
+ if (process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)
24
+ return true;
25
+ // CI can be an auxiliary hint but not definitive alone
26
+ if (String(process.env.CI).toLowerCase() === 'true')
27
+ return true;
28
+ return false;
29
+ }
30
+ /**
31
+ * URLs do AutoCore Hub
32
+ */
33
+ const HUB_URL_HOSTNAME = 'http://brtlvlty0559pl:3333';
34
+ const HUB_URL_IP = 'http://10.129.170.238:3333';
35
+ /**
36
+ * Obtém a URL correta do AutoCore Hub baseada no ambiente
37
+ * - Windows local: usa hostname interno (brtlvlty0559pl)
38
+ * - Azure/Linux: usa IP direto (10.129.170.238)
39
+ */
40
+ function getAutoCorehubUrl() {
41
+ // Se variável de ambiente definida explicitamente, usar ela
42
+ if (process.env.AUTOCORE_HUB_URL) {
43
+ return process.env.AUTOCORE_HUB_URL;
44
+ }
45
+ // Detectar ambiente e usar URL apropriada
46
+ if (isAzureEnvironment()) {
47
+ return HUB_URL_IP;
48
+ }
49
+ else {
50
+ return HUB_URL_HOSTNAME;
51
+ }
52
+ }
53
+ /**
54
+ * Obtém URL de fallback (IP direto) caso a URL principal falhe
55
+ */
56
+ function getFallbackHubUrl(currentUrl) {
57
+ // Se está usando hostname, fallback é o IP
58
+ if (currentUrl === HUB_URL_HOSTNAME) {
59
+ return HUB_URL_IP;
60
+ }
61
+ // Se está usando IP ou URL customizada, não há fallback
62
+ return null;
63
+ }
64
+ /**
65
+ * Helper para fazer fetch com timeout usando AbortController
66
+ */
67
+ async function fetchWithTimeout(url, options = {}) {
68
+ const { timeout = 60000, ...fetchOptions } = options;
69
+ const controller = new AbortController();
70
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
71
+ try {
72
+ const response = await fetch(url, {
73
+ ...fetchOptions,
74
+ signal: controller.signal,
75
+ });
76
+ return response;
77
+ }
78
+ finally {
79
+ clearTimeout(timeoutId);
80
+ }
81
+ }
82
+ /**
83
+ * Verifica se o erro é retryável (502, 503, 504, conexão, etc.)
84
+ */
85
+ function isRetryableError(error, status) {
86
+ // Erros HTTP retryáveis
87
+ if (status === 502 || status === 503 || status === 504) {
88
+ return true;
89
+ }
90
+ // Erros de conexão
91
+ const errorCode = error?.code || error?.cause?.code;
92
+ if (errorCode === 'ECONNRESET' ||
93
+ errorCode === 'ETIMEDOUT' ||
94
+ errorCode === 'ECONNREFUSED' ||
95
+ errorCode === 'ENOTFOUND' ||
96
+ errorCode === 'UND_ERR_CONNECT_TIMEOUT' ||
97
+ errorCode === 'UND_ERR_SOCKET') {
98
+ return true;
99
+ }
100
+ // AbortError (timeout)
101
+ if (error?.name === 'AbortError') {
102
+ return true;
103
+ }
104
+ return false;
105
+ }
106
+ /**
107
+ * Cliente para comunicação com backend TestHub
108
+ * Usa fetch nativo do Node.js para evitar problemas com proxy corporativo
109
+ */
110
+ export class TestHubClient {
111
+ static instance;
112
+ config;
113
+ systemsCache = null;
114
+ cacheFilePath;
115
+ cachedUserId = null;
116
+ cachedAuthToken = null;
117
+ cachedAuthTokenExpiry = 0;
118
+ hubApiUrl;
119
+ constructor(config) {
120
+ this.config = {
121
+ timeout: 60_000, // 60 segundos
122
+ retries: 5, // 5 retries
123
+ cacheEnabled: true,
124
+ cacheDuration: 10 * 60 * 1000, // 10 minutos
125
+ ...config,
126
+ };
127
+ // AutoCore Hub API URL - detecta ambiente automaticamente
128
+ this.hubApiUrl = getAutoCorehubUrl();
129
+ this.cacheFilePath = path.join(process.cwd(), 'test-results', '.testhub-systems-cache.json');
130
+ }
131
+ /**
132
+ * Obtém instância singleton do cliente
133
+ */
134
+ static getInstance(config) {
135
+ if (!TestHubClient.instance) {
136
+ const baseUrl = config?.baseUrl || process.env.TESTHUB_BASE_URL || getAutoCorehubUrl();
137
+ TestHubClient.instance = new TestHubClient({
138
+ baseUrl,
139
+ ...config,
140
+ });
141
+ }
142
+ return TestHubClient.instance;
143
+ }
144
+ /**
145
+ * Constrói headers padrão para requisições ao AutoCore Hub
146
+ * - Windows local: x-automation-user-matricula (USERNAME)
147
+ * - Linux/Azure: x-automation-user-email (email do pipeline)
148
+ * REGRA: Cada ambiente manda apenas o que tem disponível
149
+ */
150
+ async buildHeaders() {
151
+ const headers = {
152
+ 'Content-Type': 'application/json',
153
+ 'User-Agent': 'AutoCore-Framework',
154
+ 'Connection': 'keep-alive',
155
+ };
156
+ // x-automation-key should be configurable via env; fallback to legacy hardcoded
157
+ const automationKey = process.env.AUTOCORE_AUTOMATION_KEY || process.env.AUTOCORE_KEY || 'd8f3c4a9be7f29d1c6e04fa1a39c2e7bd91f57c8a3de45eb91c4f7a2d0e3b8fa';
158
+ if (automationKey)
159
+ headers['x-automation-key'] = automationKey;
160
+ // Prefer JWT token when available (env or login)
161
+ const envJwt = process.env.AUTOCORE_JWT_TOKEN;
162
+ let token = envJwt || null;
163
+ if (!token && this.cachedAuthToken && this.cachedAuthTokenExpiry > Date.now()) {
164
+ token = this.cachedAuthToken;
165
+ }
166
+ if (!token) {
167
+ // attempt to obtain token but don't throw here — headers should still include automation key
168
+ try {
169
+ token = await this.getAuthToken();
170
+ }
171
+ catch {
172
+ token = null;
173
+ }
174
+ }
175
+ if (token) {
176
+ headers['Authorization'] = `Bearer ${token}`;
177
+ }
178
+ // Identity headers: send email when available, otherwise matricula
179
+ const azureEmail = process.env.BUILD_REQUESTEDFOREMAIL || process.env.AUTOCORE_USER_EMAIL;
180
+ const buildReason = (process.env.BUILD_REASON || '').toString().toLowerCase();
181
+ const isScheduled = buildReason === 'schedule' || buildReason === 'scheduled';
182
+ if (azureEmail) {
183
+ if (isScheduled) {
184
+ // For scheduled runs, include trigger header and prefer technical identity if provided
185
+ headers['x-automation-trigger'] = 'schedule';
186
+ // prefer dedicated identity headers if present
187
+ if (process.env.AUTOCORE_USER_MATRICULA)
188
+ headers['x-automation-user-matricula'] = process.env.AUTOCORE_USER_MATRICULA;
189
+ if (process.env.AUTOCORE_USER_EMAIL)
190
+ headers['x-automation-user-email'] = process.env.AUTOCORE_USER_EMAIL;
191
+ }
192
+ else {
193
+ headers['x-automation-user-email'] = azureEmail;
194
+ }
195
+ }
196
+ else {
197
+ const matricula = process.env.AUTOCORE_USER_MATRICULA || process.env.USERNAME || process.env.USER || 'UNKNOWN';
198
+ headers['x-automation-user-matricula'] = matricula;
199
+ }
200
+ return headers;
201
+ }
202
+ /**
203
+ * Faz uma requisição GET com retry e fallback de URL
204
+ */
205
+ async fetchGet(endpoint, extraHeaders) {
206
+ const maxRetries = this.config.retries || 5;
207
+ let lastError = null;
208
+ // Lista de URLs para tentar (principal + fallback)
209
+ const urlsToTry = [this.hubApiUrl];
210
+ const fallbackUrl = getFallbackHubUrl(this.hubApiUrl);
211
+ if (fallbackUrl) {
212
+ urlsToTry.push(fallbackUrl);
213
+ }
214
+ for (const hubUrl of urlsToTry) {
215
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
216
+ try {
217
+ const url = `${hubUrl}${endpoint}`;
218
+ const defaultHeaders = await this.buildHeaders();
219
+ const response = await fetchWithTimeout(url, {
220
+ method: 'GET',
221
+ headers: {
222
+ ...defaultHeaders,
223
+ ...extraHeaders,
224
+ },
225
+ timeout: this.config.timeout,
226
+ });
227
+ if (!response.ok) {
228
+ const errorText = await response.text().catch(() => '');
229
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
230
+ error.status = response.status;
231
+ error.statusText = response.statusText;
232
+ error.responseText = errorText;
233
+ if (isRetryableError(error, response.status) && attempt < maxRetries) {
234
+ const waitTime = Math.min(1000 * Math.pow(2, attempt), 30000);
235
+ console.warn(`⚠️ Erro ${response.status} - Aguardando ${waitTime}ms antes de tentar novamente...`);
236
+ await new Promise(resolve => setTimeout(resolve, waitTime));
237
+ continue;
238
+ }
239
+ throw error;
240
+ }
241
+ // Atualizar URL do hub para usar a que funcionou
242
+ if (hubUrl !== this.hubApiUrl) {
243
+ console.log(`🔄 Atualizando URL do Hub para: ${hubUrl}`);
244
+ this.hubApiUrl = hubUrl;
245
+ }
246
+ return await response.json();
247
+ }
248
+ catch (error) {
249
+ lastError = error;
250
+ // Enrich missing request metadata for debugging
251
+ if (!error.requestUrl) {
252
+ error.requestUrl = `${hubUrl}${endpoint}`;
253
+ try {
254
+ // build headers on-demand for error context
255
+ // eslint-disable-next-line no-await-in-loop
256
+ error.requestHeaders = await this.buildHeaders();
257
+ }
258
+ catch { }
259
+ }
260
+ if (isRetryableError(error) && attempt < maxRetries) {
261
+ const waitTime = Math.min(1000 * Math.pow(2, attempt), 30000);
262
+ Logger.warning(`⚠️ Erro ${error.code || error.name || 'desconhecido'} - Aguardando ${waitTime}ms antes de tentar novamente...`);
263
+ await new Promise(resolve => setTimeout(resolve, waitTime));
264
+ continue;
265
+ }
266
+ break;
267
+ }
268
+ }
269
+ Logger.warning(`⚠️ Falha ao conectar com ${hubUrl}, tentando próxima URL...`);
270
+ }
271
+ throw lastError;
272
+ }
273
+ /**
274
+ * Faz uma requisição POST com retry
275
+ */
276
+ async fetchPost(endpoint, body, extraHeaders) {
277
+ const maxRetries = this.config.retries || 5;
278
+ let lastError = null;
279
+ // Lista de URLs para tentar (principal + fallback)
280
+ const urlsToTry = [this.hubApiUrl];
281
+ const fallbackUrl = getFallbackHubUrl(this.hubApiUrl);
282
+ if (fallbackUrl) {
283
+ urlsToTry.push(fallbackUrl);
284
+ }
285
+ // Prepara headers para todas as tentativas
286
+ const requestHeaders = {
287
+ ...(await this.buildHeaders()),
288
+ ...extraHeaders,
289
+ };
290
+ const requestBody = JSON.stringify(body);
291
+ for (const hubUrl of urlsToTry) {
292
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
293
+ try {
294
+ const url = `${hubUrl}${endpoint}`;
295
+ const response = await fetchWithTimeout(url, {
296
+ method: 'POST',
297
+ headers: requestHeaders,
298
+ body: requestBody,
299
+ timeout: this.config.timeout,
300
+ });
301
+ if (!response.ok) {
302
+ const errorText = await response.text().catch(() => '');
303
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
304
+ error.status = response.status;
305
+ error.statusText = response.statusText;
306
+ error.responseText = errorText;
307
+ error.requestUrl = url;
308
+ error.requestHeaders = requestHeaders;
309
+ error.requestBody = requestBody;
310
+ if (isRetryableError(error, response.status) && attempt < maxRetries) {
311
+ const waitTime = Math.min(1000 * Math.pow(2, attempt), 30000);
312
+ Logger.warning(`⚠️ Erro ${response.status} - Aguardando ${waitTime}ms antes de tentar novamente...`);
313
+ await new Promise(resolve => setTimeout(resolve, waitTime));
314
+ continue;
315
+ }
316
+ throw error;
317
+ }
318
+ // Atualizar URL do hub para usar a que funcionou
319
+ if (hubUrl !== this.hubApiUrl) {
320
+ Logger.info(`🔄 Atualizando URL do Hub para: ${hubUrl}`);
321
+ this.hubApiUrl = hubUrl;
322
+ }
323
+ return await response.json();
324
+ }
325
+ catch (error) {
326
+ lastError = error;
327
+ // 🔧 Adiciona detalhes da requisição ao erro para debug
328
+ if (!error.requestUrl) {
329
+ error.requestUrl = `${hubUrl}${endpoint}`;
330
+ error.requestHeaders = requestHeaders;
331
+ error.requestBody = requestBody;
332
+ }
333
+ if (isRetryableError(error) && attempt < maxRetries) {
334
+ const waitTime = Math.min(1000 * Math.pow(2, attempt), 30000);
335
+ Logger.warning(`⚠️ Erro ${error.code || error.name || 'desconhecido'} - Aguardando ${waitTime}ms antes de tentar novamente...`);
336
+ await new Promise(resolve => setTimeout(resolve, waitTime));
337
+ continue;
338
+ }
339
+ break;
340
+ }
341
+ }
342
+ Logger.warning(`⚠️ Falha ao conectar com ${hubUrl}, tentando próxima URL...`);
343
+ }
344
+ throw lastError;
345
+ }
346
+ /**
347
+ * 📡 Busca lista de sistemas do backend
348
+ * @returns Lista de sistemas cadastrados
349
+ */
350
+ async getSystems() {
351
+ // Verificar cache primeiro (sempre priorizar cache para evitar 502)
352
+ if (this.config.cacheEnabled && this.isCacheValid()) {
353
+ return this.systemsCache.systems;
354
+ }
355
+ // Tentar carregar do cache de arquivo antes de fazer request
356
+ if (this.loadSystemsFromFileCache()) {
357
+ Logger.info('📦 Usando cache de sistemas (pré-carregado)');
358
+ return this.systemsCache.systems;
359
+ }
360
+ try {
361
+ Logger.info(`📡 Buscando sistemas do AutoCore Hub...`);
362
+ // Garantir token JWT e usar rota oficial em português
363
+ const token = await this.getAuthToken().catch((e) => {
364
+ Logger.warning('⚠️ Não foi possível obter token JWT, prosseguindo com chave de API');
365
+ return null;
366
+ });
367
+ const extraHeaders = {};
368
+ if (token)
369
+ extraHeaders['Authorization'] = `Bearer ${token}`;
370
+ const data = await this.fetchGet('/api/sistemas', extraHeaders);
371
+ // Converter formato do AutoCore Hub para TestHub
372
+ const systems = this.convertHubSystemsToTestHub(data);
373
+ Logger.success(`✅ ${systems.length} sistema(s) carregado(s) do AutoCore Hub`);
374
+ // Salvar em cache
375
+ if (this.config.cacheEnabled) {
376
+ this.saveSystemsToCache(systems);
377
+ }
378
+ return systems;
379
+ }
380
+ catch (error) {
381
+ Logger.error(`❌ Erro ao buscar sistemas do AutoCore Hub: ${error?.message}`);
382
+ Logger.error('🔍 Detalhes do erro:');
383
+ Logger.error(` - Código do erro: ${error?.code || error?.cause?.code || 'N/A'}`);
384
+ Logger.error(` - Status HTTP: ${error?.status || 'N/A'}`);
385
+ // Tentar carregar do cache em caso de erro (fallback final)
386
+ if (this.loadSystemsFromFileCache()) {
387
+ Logger.warning('⚠️ Usando cache de sistemas (AutoCore Hub indisponível)');
388
+ Logger.warning(`📦 ${this.systemsCache.systems.length} sistema(s) carregado(s) do cache`);
389
+ return this.systemsCache.systems;
390
+ }
391
+ Logger.warning('⚠️ Nenhum sistema disponível. Continuando sem integração AutoCore Hub.');
392
+ return [];
393
+ }
394
+ }
395
+ /**
396
+ * 📤 Envia resultado de execução em batch para o backend
397
+ * @param batchPayload Dados completos da execução (batch)
398
+ * @returns IDs das execuções criadas
399
+ */
400
+ async sendBatchExecution(batchPayload) {
401
+ try {
402
+ Logger.info('📤 Iniciando envio do batch para AutoCore Hub...');
403
+ const startTime = Date.now();
404
+ // Garantir que `systemName` esteja presente no payload (backend exige)
405
+ if (!batchPayload || !batchPayload.systemName) {
406
+ const fallbackSystem = process.env.AUTOCORE_SYSTEM_NAME || 'Unknown';
407
+ try {
408
+ Logger.warning(`[TestHubClient] batchPayload.systemName ausente — usando fallback: ${fallbackSystem}`);
409
+ }
410
+ catch { }
411
+ batchPayload = { ...(batchPayload || {}), systemName: fallbackSystem };
412
+ }
413
+ const response = await this.fetchPost('/api/execucoes/batch', batchPayload);
414
+ const duration = Date.now() - startTime;
415
+ Logger.info(`⏱️ Tempo de envio: ${duration}ms`);
416
+ if (response?.success) {
417
+ Logger.success('✅ Resposta do Hub recebida com sucesso!');
418
+ return response.executionIds || [];
419
+ }
420
+ Logger.warning(`⚠️ Resposta inesperada do AutoCore Hub`);
421
+ return [];
422
+ }
423
+ catch (error) {
424
+ Logger.error(`❌ Erro ao enviar batch para AutoCore Hub: ${error.message}`);
425
+ Logger.error('🔍 Detalhes do erro:');
426
+ Logger.error(` - Código do erro: ${error.code || error?.cause?.code || 'N/A'}`);
427
+ Logger.error(` - Status HTTP: ${error.status || 'N/A'}`);
428
+ if (error.responseText) {
429
+ Logger.error(` - Resposta do servidor: ${error.responseText}`);
430
+ }
431
+ // Salvar localmente para retry posterior
432
+ this.saveFailedBatchExecution(batchPayload);
433
+ throw error;
434
+ }
435
+ }
436
+ /**
437
+ * 📦 Envia artifacts (relatórios compactados em Base64) para o backend
438
+ * @param artifactsPayload Dados dos artifacts
439
+ */
440
+ async sendArtifacts(artifactsPayload) {
441
+ try {
442
+ await this.fetchPost('/api/execucoes/artifacts', artifactsPayload);
443
+ }
444
+ catch (error) {
445
+ console.error('❌ Erro ao enviar artifacts para AutoCore Hub:', error.message);
446
+ console.error('🔍 Detalhes do erro:');
447
+ console.error(` - Código do erro: ${error.code || error?.cause?.code || 'N/A'}`);
448
+ console.error(` - Status HTTP: ${error.status || 'N/A'}`);
449
+ console.warn('⚠️ Continuando sem envio de artifacts...');
450
+ }
451
+ }
452
+ /**
453
+ * 🔍 Busca sistema por nome
454
+ * @param systemName Nome do sistema
455
+ * @returns Sistema encontrado ou null
456
+ */
457
+ async getSystemByName(systemName) {
458
+ const systems = await this.getSystems();
459
+ const normalizedSearchName = systemName.toLowerCase().trim();
460
+ const foundSystem = systems.find((s) => s.name.toLowerCase().trim() === normalizedSearchName) || null;
461
+ if (foundSystem) {
462
+ console.log(`✅ Sistema encontrado: ${foundSystem.name} (ID: ${foundSystem.id})`);
463
+ }
464
+ else {
465
+ console.warn(`❌ Sistema "${systemName}" não encontrado`);
466
+ console.warn(`💡 Sistemas disponíveis: ${systems.map((s) => s.name).join(', ')}`);
467
+ }
468
+ return foundSystem;
469
+ }
470
+ /**
471
+ * 🔍 Busca sistemas por tipo de ambiente
472
+ * @param type Tipo de ambiente (API, WEB, SSH, BANCO)
473
+ * @returns Lista de sistemas filtrados
474
+ */
475
+ async getSystemsByType(type) {
476
+ console.log(`🔍 Buscando sistemas por tipo de ambiente: "${type}"`);
477
+ const systems = await this.getSystems();
478
+ console.log(`📊 Total de sistemas disponíveis: ${systems.length}`);
479
+ const filteredSystems = systems.filter((system) => {
480
+ const hasType = system.environments.some((env) => env.type === type);
481
+ console.log(`📋 Sistema "${system.name}": ${hasType ? '✅ tem' : '❌ não tem'} ambiente ${type}`);
482
+ if (hasType) {
483
+ const envs = system.environments.filter((env) => env.type === type);
484
+ console.log(` Ambientes ${type}:`, envs.map((env) => `${env.name} (${env.url})`).join(', '));
485
+ }
486
+ return hasType;
487
+ });
488
+ console.log(`✅ ${filteredSystems.length} sistema(s) encontrado(s) com ambiente ${type}`);
489
+ console.log(`📋 Sistemas filtrados:`, filteredSystems.map((s) => s.name).join(', '));
490
+ return filteredSystems;
491
+ }
492
+ /**
493
+ * 🔍 Busca ambiente específico de um sistema
494
+ * @param systemName Nome do sistema
495
+ * @param environmentName Nome do ambiente (ex: 'preprod', 'esteira1')
496
+ * @returns Ambiente encontrado ou null
497
+ */
498
+ async getEnvironment(systemName, environmentName) {
499
+ console.log(`🔍 Buscando ambiente "${environmentName}" no sistema "${systemName}"`);
500
+ const system = await this.getSystemByName(systemName);
501
+ if (!system) {
502
+ console.error(`❌ Sistema "${systemName}" não encontrado`);
503
+ return null;
504
+ }
505
+ console.log(`📊 Total de ambientes no sistema: ${system.environments.length}`);
506
+ const normalizedEnvName = environmentName.toLowerCase().trim();
507
+ const foundEnvironment = system.environments.find((env) => env.name.toLowerCase().trim() === normalizedEnvName) || null;
508
+ if (foundEnvironment) {
509
+ console.log(`✅ Ambiente encontrado: ${foundEnvironment.name}`);
510
+ }
511
+ else {
512
+ console.warn(`❌ Ambiente "${environmentName}" não encontrado no sistema "${systemName}"`);
513
+ console.warn(`💡 Ambientes disponíveis: ${system.environments.map((env) => env.name).join(', ')}`);
514
+ }
515
+ return foundEnvironment;
516
+ }
517
+ /**
518
+ * 🔑 Busca todas as variáveis de ambiente de um sistema/ambiente específico
519
+ * Inclui automaticamente campos do ambiente como variáveis:
520
+ * - URL, USERNAME, PASSWORD, PORT
521
+ * @param systemName Nome do sistema
522
+ * @param environmentName Nome do ambiente (ex: 'Prod', 'Homolog')
523
+ * @returns Array de variáveis ou array vazio
524
+ */
525
+ async getEnvironmentVariables(systemName, environmentName) {
526
+ console.log(`🔑 Buscando variáveis do ambiente "${environmentName}" no sistema "${systemName}"`);
527
+ const environment = await this.getEnvironment(systemName, environmentName);
528
+ if (!environment) {
529
+ console.warn(`⚠️ Ambiente não encontrado, retornando array vazio`);
530
+ return [];
531
+ }
532
+ // Preferir `frameworkVariables` quando presente (fonte prioritaria)
533
+ const frameworkVars = environment.frameworkVariables || {};
534
+ // Validar requiredFrameworkVariables
535
+ const required = environment.requiredFrameworkVariables || [];
536
+ const missingKeys = required.filter((k) => !(k in frameworkVars));
537
+ if (missingKeys.length > 0) {
538
+ throw new Error(`Missing framework variables for system=${systemName} env=${environmentName} required: ${missingKeys.join(', ')}`);
539
+ }
540
+ // Validar chaves críticas com valor não vazio
541
+ const critical = ['URL', 'PORT', 'USERNAME', 'PASSWORD'];
542
+ const emptyCritical = critical.filter((k) => !String(frameworkVars[k] ?? '').trim());
543
+ if (emptyCritical.length > 0) {
544
+ throw new Error(`Sem permissao ou configuracao incompleta para system=${environmentName} keys: ${emptyCritical.join(', ')}`);
545
+ }
546
+ // Converter para EnvironmentVariable[] preservando chaves do objeto
547
+ const variables = Object.keys(frameworkVars).map((k) => ({
548
+ id: `auto-${k.toLowerCase()}`,
549
+ key: k,
550
+ value: String(frameworkVars[k] ?? ''),
551
+ }));
552
+ // Incluir também campos básicos do ambiente como fallback/metadata (sem expor valores sensíveis extra)
553
+ variables.push({ id: 'auto-env-id', key: 'ENVIRONMENT_ID', value: environment.id || '' });
554
+ if (environment.name)
555
+ variables.push({ id: 'auto-env-name', key: 'ENVIRONMENT_NAME', value: environment.name });
556
+ if (environment.type)
557
+ variables.push({ id: 'auto-env-type', key: 'ENVIRONMENT_TYPE', value: environment.type });
558
+ console.log(`📋 ${variables.length} variável(is) preparada(s) (chaves somente).`);
559
+ return variables;
560
+ }
561
+ /**
562
+ * Obtém token JWT do AutoCore Hub e faz cache com expiry simples.
563
+ */
564
+ async getAuthToken() {
565
+ const now = Date.now();
566
+ if (this.cachedAuthToken && this.cachedAuthTokenExpiry > now)
567
+ return this.cachedAuthToken;
568
+ // 1) Prefer explicit env token
569
+ const envToken = process.env.AUTOCORE_JWT_TOKEN;
570
+ if (envToken) {
571
+ // No expiry info in env token, cache for short period
572
+ this.cachedAuthToken = envToken;
573
+ this.cachedAuthTokenExpiry = Date.now() + 30 * 60 * 1000; // 30 minutes
574
+ return envToken;
575
+ }
576
+ // 2) Attempt login with identifier/email + password if provided
577
+ const identifier = process.env.AUTOCORE_LOGIN_IDENTIFIER || process.env.AUTOCORE_LOGIN_EMAIL;
578
+ const password = process.env.AUTOCORE_LOGIN_PASSWORD;
579
+ if (identifier && password) {
580
+ try {
581
+ const payload = { password };
582
+ // prefer using 'identifier' key when AUTOCORE_LOGIN_IDENTIFIER set
583
+ if (process.env.AUTOCORE_LOGIN_IDENTIFIER)
584
+ payload.identifier = process.env.AUTOCORE_LOGIN_IDENTIFIER;
585
+ else
586
+ payload.email = process.env.AUTOCORE_LOGIN_EMAIL;
587
+ const resp = await this.fetchPost('/auth/login', payload);
588
+ const token = resp?.token || resp?.accessToken || resp?.jwt;
589
+ const expiresIn = resp?.expiresIn || resp?.expires_seconds || 60 * 60;
590
+ if (token) {
591
+ this.cachedAuthToken = token;
592
+ this.cachedAuthTokenExpiry = Date.now() + (Number(expiresIn) * 1000) - 60_000;
593
+ return token;
594
+ }
595
+ }
596
+ catch (e) {
597
+ console.warn('⚠️ Falha ao autenticar via /auth/login com AUTOCORE_LOGIN_*, prosseguindo sem JWT');
598
+ }
599
+ }
600
+ // 3) No token available
601
+ return null;
602
+ }
603
+ /**
604
+ * Retorna um mapa de variáveis final para execução escolhendo credencial com sufixo quando informado.
605
+ * - Sufixo nulo => usa chaves padrão (URL, USERNAME...)
606
+ * - Sufixo 'PARCEIRO' => prefere URL_PARCEIRO etc, mas faz fallback para padrão quando ausente
607
+ */
608
+ async getFrameworkVariablesForExecution(systemName, environmentName, credentialSuffix) {
609
+ const environment = await this.getEnvironment(systemName, environmentName);
610
+ if (!environment)
611
+ throw new Error(`Ambiente ${environmentName} / ${systemName} não encontrado`);
612
+ const frameworkVars = environment.frameworkVariables || {};
613
+ // Validate required keys existence (backend declares which keys must exist)
614
+ const required = environment.requiredFrameworkVariables || [];
615
+ const missingKeys = required.filter((k) => !(k in frameworkVars));
616
+ if (missingKeys.length > 0)
617
+ throw new Error(`Missing framework variables for system=${systemName} env=${environmentName}: ${missingKeys.join(', ')}`);
618
+ // Use credential selection flow to pick suffix + concrete credentials.
619
+ // This keeps selection logic consistent with getCredentialSetForEnvironment.
620
+ const { suffix, credentialSet } = await this.getCredentialSetForEnvironment(systemName, environmentName, credentialSuffix);
621
+ // Start with all framework variables (preserve custom keys like ADB_*)
622
+ const result = {};
623
+ for (const key of Object.keys(frameworkVars)) {
624
+ result[key] = String(frameworkVars[key] ?? '');
625
+ }
626
+ // Overwrite base keys with the selected credential set (so callers can read URL/PORT/USERNAME/PASSWORD)
627
+ result['URL'] = String(credentialSet.url ?? '');
628
+ result['PORT'] = String(credentialSet.port ?? '');
629
+ result['SERVICE'] = String(credentialSet.service ?? '');
630
+ result['USERNAME'] = String(credentialSet.username ?? '');
631
+ result['PASSWORD'] = String(credentialSet.password ?? '');
632
+ // Also ensure suffixed keys exist (map chosen values to suffixed keys when applicable)
633
+ if (suffix) {
634
+ const su = String(suffix).toUpperCase();
635
+ result[`URL_${su}`] = result[`URL_${su}`] ?? result['URL'];
636
+ result[`PORT_${su}`] = result[`PORT_${su}`] ?? result['PORT'];
637
+ result[`SERVICE_${su}`] = result[`SERVICE_${su}`] ?? result['SERVICE'];
638
+ result[`USERNAME_${su}`] = result[`USERNAME_${su}`] ?? result['USERNAME'];
639
+ result[`PASSWORD_${su}`] = result[`PASSWORD_${su}`] ?? result['PASSWORD'];
640
+ }
641
+ // Final critical check
642
+ const critical = ['URL', 'PORT', 'USERNAME', 'PASSWORD'];
643
+ const emptyCritical = critical.filter((k) => !String(result[k] ?? '').trim());
644
+ if (emptyCritical.length > 0) {
645
+ throw new Error(`Sem permissao ou configuracao incompleta para: ${emptyCritical.join(', ')}`);
646
+ }
647
+ return result;
648
+ }
649
+ /**
650
+ * Retorna os sufixos de credenciais disponíveis para um ambiente (inclui null/default)
651
+ */
652
+ async getAvailableCredentialSuffixes(systemName, environmentName) {
653
+ const env = await this.getEnvironment(systemName, environmentName);
654
+ if (!env)
655
+ return [];
656
+ const creds = env.credentials || [];
657
+ const suffixes = [];
658
+ // Sempre incluir default (null) se existir
659
+ if (creds.some((c) => c.isDefault))
660
+ suffixes.push(null);
661
+ // Adicionar todos os sufixos ativos não-nulos
662
+ for (const c of creds) {
663
+ if (c.suffix && !suffixes.includes(c.suffix))
664
+ suffixes.push(String(c.suffix).toUpperCase());
665
+ }
666
+ // Se não houver credencial marcada como default, garantir que pelo menos null exista (fallback)
667
+ if (suffixes.length === 0)
668
+ suffixes.push(null);
669
+ return suffixes;
670
+ }
671
+ /**
672
+ * Constrói um conjunto de credenciais (URL/PORT/SERVICE/USERNAME/PASSWORD) a partir do mapa de framework vars
673
+ */
674
+ getCredentialSetFromVars(vars, suffix) {
675
+ const normalizedSuffix = String(suffix ?? '').trim().toUpperCase();
676
+ const prefix = normalizedSuffix ? `_${normalizedSuffix}` : '';
677
+ return {
678
+ url: String(vars[`URL${prefix}`] ?? ''),
679
+ port: String(vars[`PORT${prefix}`] ?? ''),
680
+ service: String(vars[`SERVICE${prefix}`] ?? ''),
681
+ username: String(vars[`USERNAME${prefix}`] ?? ''),
682
+ password: String(vars[`PASSWORD${prefix}`] ?? ''),
683
+ };
684
+ }
685
+ /**
686
+ * Retorna o conjunto de credenciais efetivo para execução, escolhendo por sufixo ou fallback
687
+ */
688
+ async getCredentialSetForEnvironment(systemName, environmentName, requestedSuffix) {
689
+ const env = await this.getEnvironment(systemName, environmentName);
690
+ if (!env)
691
+ throw new Error(`Ambiente ${environmentName} do sistema ${systemName} não encontrado`);
692
+ const frameworkVars = env.frameworkVariables || {};
693
+ const required = env.requiredFrameworkVariables || [];
694
+ // Validar requiredFrameworkVariables
695
+ const missing = required.filter((k) => !(k in frameworkVars));
696
+ if (missing.length > 0)
697
+ throw new Error(`Missing required framework variables for system=${systemName} env=${environmentName}: ${missing.join(', ')}`);
698
+ // Preferir credenciais ativas com sufixo solicitado
699
+ const creds = env.credentials || [];
700
+ let suffixToUse = null;
701
+ if (requestedSuffix) {
702
+ const found = creds.find((c) => String(c.suffix ?? '').toUpperCase() === String(requestedSuffix).toUpperCase() && c.isActive);
703
+ if (found)
704
+ suffixToUse = String(found.suffix ?? '').toUpperCase();
705
+ }
706
+ // Se não pediu ou não encontrou, usar default credential quando existir
707
+ if (suffixToUse === null) {
708
+ const def = creds.find((c) => c.isDefault && c.isActive);
709
+ if (def)
710
+ suffixToUse = def.suffix ?? null;
711
+ }
712
+ // Se ainda não decidiu, tentar pegar o primeiro ativo com sufixo
713
+ if (suffixToUse === null) {
714
+ const firstSuff = creds.find((c) => c.suffix && c.isActive);
715
+ if (firstSuff)
716
+ suffixToUse = String(firstSuff.suffix).toUpperCase();
717
+ }
718
+ // Build vars and credential set
719
+ const mergedVars = {};
720
+ for (const k of Object.keys(frameworkVars))
721
+ mergedVars[k] = String(frameworkVars[k] ?? '');
722
+ const credentialSet = this.getCredentialSetFromVars(mergedVars, suffixToUse);
723
+ // Final validation: critical keys
724
+ const critical = ['url', 'port', 'username', 'password'];
725
+ const empty = critical.filter((k) => !String(credentialSet[k] ?? '').trim());
726
+ if (empty.length > 0) {
727
+ throw new Error(`Credenciais incompletas para system=${systemName} env=${environmentName} sufixo=${suffixToUse ?? 'DEFAULT'}: ${empty.join(', ')}`);
728
+ }
729
+ return { suffix: suffixToUse, credentialSet };
730
+ }
731
+ /**
732
+ * Resolve um valor possivelmente placeholder usando as frameworkVariables ou credentials do ambiente.
733
+ * - Se `input` for uma chave (ex: "USERNAME", "ADB_VENDA") tenta buscar em frameworkVariables
734
+ * - Se for uma das chaves base (USERNAME/PASSWORD/URL/PORT/SERVICE) e não existir como variável, usa o credentialSet
735
+ * - Caso contrário retorna `input` inalterado
736
+ */
737
+ async resolveFrameworkValue(systemName, environmentName, input, credentialSuffix) {
738
+ if (!input && input !== '')
739
+ return null;
740
+ const raw = String(input ?? '');
741
+ // If empty string, return as-is
742
+ if (raw.trim() === '')
743
+ return raw;
744
+ const env = await this.getEnvironment(systemName, environmentName);
745
+ if (!env)
746
+ return raw;
747
+ const frameworkVars = env.frameworkVariables || {};
748
+ const keyCandidate = raw.toUpperCase().trim();
749
+ // First try exact key in frameworkVariables (case-sensitive keys exist as uppercase in hub)
750
+ if (keyCandidate in frameworkVars && String(frameworkVars[keyCandidate] ?? '').trim() !== '') {
751
+ return String(frameworkVars[keyCandidate]);
752
+ }
753
+ // If a suffix was requested, try suffixed key
754
+ if (credentialSuffix) {
755
+ const su = String(credentialSuffix).toUpperCase();
756
+ const suffKey = `${keyCandidate}_${su}`;
757
+ if (suffKey in frameworkVars && String(frameworkVars[suffKey] ?? '').trim() !== '') {
758
+ return String(frameworkVars[suffKey]);
759
+ }
760
+ }
761
+ // If the input is one of the base credential keys, fallback to credentialSet
762
+ const baseMap = {
763
+ USERNAME: 'username',
764
+ PASSWORD: 'password',
765
+ URL: 'url',
766
+ PORT: 'port',
767
+ SERVICE: 'service',
768
+ };
769
+ if (Object.prototype.hasOwnProperty.call(baseMap, keyCandidate)) {
770
+ try {
771
+ const { credentialSet } = await this.getCredentialSetForEnvironment(systemName, environmentName, credentialSuffix);
772
+ const mapped = credentialSet[baseMap[keyCandidate]];
773
+ if (mapped && String(mapped).trim() !== '')
774
+ return String(mapped);
775
+ }
776
+ catch (e) {
777
+ // ignore and fallback to raw
778
+ }
779
+ }
780
+ // Last attempt: look for case-insensitive match in frameworkVars
781
+ const foundKey = Object.keys(frameworkVars).find((k) => k.toLowerCase() === keyCandidate.toLowerCase());
782
+ if (foundKey && String(frameworkVars[foundKey] ?? '').trim() !== '')
783
+ return String(frameworkVars[foundKey]);
784
+ return raw;
785
+ }
786
+ /**
787
+ * 🔍 Busca uma variável específica por chave
788
+ * @param systemName Nome do sistema
789
+ * @param environmentName Nome do ambiente
790
+ * @param variableKey Chave da variável (ex: 'BASE_URL', 'API_TOKEN')
791
+ * @returns Valor da variável ou null se não encontrada
792
+ */
793
+ async getEnvironmentVariable(systemName, environmentName, variableKey) {
794
+ console.log(`🔍 Buscando variável "${variableKey}" em ${systemName}/${environmentName}`);
795
+ const variables = await this.getEnvironmentVariables(systemName, environmentName);
796
+ const variable = variables.find((v) => v.key.toLowerCase() === variableKey.toLowerCase());
797
+ if (variable) {
798
+ console.log(`✅ Variável encontrada: ${variableKey}`);
799
+ return variable.value;
800
+ }
801
+ console.warn(`⚠️ Variável "${variableKey}" não encontrada`);
802
+ return null;
803
+ }
804
+ /**
805
+ * 🌍 Carrega todas as variáveis de um ambiente como variáveis de ambiente do processo
806
+ * @param systemName Nome do sistema
807
+ * @param environmentName Nome do ambiente
808
+ * @param prefix Prefixo opcional para as variáveis (ex: 'AUTOCORE_')
809
+ * @returns Número de variáveis carregadas
810
+ */
811
+ async loadEnvironmentVariables(systemName, environmentName, prefix = '') {
812
+ console.log(`🌍 Carregando variáveis de ${systemName}/${environmentName} para process.env`);
813
+ const variables = await this.getEnvironmentVariables(systemName, environmentName);
814
+ let loaded = 0;
815
+ for (const variable of variables) {
816
+ const envKey = prefix ? `${prefix}${variable.key}` : variable.key;
817
+ process.env[envKey] = variable.value;
818
+ loaded++;
819
+ console.log(` ✅ ${envKey} carregada`);
820
+ }
821
+ console.log(`📦 ${loaded} variável(is) carregada(s) em process.env`);
822
+ return loaded;
823
+ }
824
+ /**
825
+ * 📝 Retorna todas as variáveis como um objeto chave-valor
826
+ * @param systemName Nome do sistema
827
+ * @param environmentName Nome do ambiente
828
+ * @returns Objeto com chave-valor das variáveis
829
+ */
830
+ async getVariablesAsObject(systemName, environmentName) {
831
+ const variables = await this.getEnvironmentVariables(systemName, environmentName);
832
+ const result = {};
833
+ for (const variable of variables) {
834
+ result[variable.key] = variable.value;
835
+ }
836
+ return result;
837
+ }
838
+ /**
839
+ * Verifica se o cache é válido
840
+ */
841
+ isCacheValid() {
842
+ if (!this.systemsCache) {
843
+ return this.loadSystemsFromFileCache();
844
+ }
845
+ const now = Date.now();
846
+ const cacheAge = now - this.systemsCache.timestamp;
847
+ return cacheAge < (this.config.cacheDuration || 300_000);
848
+ }
849
+ /**
850
+ * Salva sistemas no cache em memória e arquivo
851
+ */
852
+ saveSystemsToCache(systems) {
853
+ this.systemsCache = {
854
+ systems,
855
+ timestamp: Date.now(),
856
+ };
857
+ // Salvar em arquivo para persistência
858
+ try {
859
+ const cacheDir = path.dirname(this.cacheFilePath);
860
+ if (!fs.existsSync(cacheDir)) {
861
+ fs.mkdirSync(cacheDir, { recursive: true });
862
+ }
863
+ const cacheData = JSON.stringify(this.systemsCache, null, 2);
864
+ fs.writeFileSync(this.cacheFilePath, cacheData);
865
+ }
866
+ catch (error) {
867
+ console.warn('⚠️ Erro ao salvar cache de sistemas:', error);
868
+ }
869
+ }
870
+ /**
871
+ * Carrega sistemas do cache em arquivo
872
+ */
873
+ loadSystemsFromFileCache() {
874
+ try {
875
+ if (fs.existsSync(this.cacheFilePath)) {
876
+ const cacheData = fs.readFileSync(this.cacheFilePath, 'utf-8');
877
+ this.systemsCache = JSON.parse(cacheData);
878
+ return this.isCacheValid();
879
+ }
880
+ return false;
881
+ }
882
+ catch (error) {
883
+ console.warn('⚠️ Erro ao carregar cache de sistemas:', error);
884
+ return false;
885
+ }
886
+ }
887
+ /**
888
+ * Salva execução que falhou para retry posterior
889
+ */
890
+ saveFailedBatchExecution(batchPayload) {
891
+ console.log(`💾 Salvando batch falhado para retry posterior...`);
892
+ try {
893
+ const failedDir = path.join(process.cwd(), '.rbqa', '.testhub-failed-batches');
894
+ if (!fs.existsSync(failedDir)) {
895
+ fs.mkdirSync(failedDir, { recursive: true });
896
+ }
897
+ const timestamp = Date.now();
898
+ const fileName = `batch-${timestamp}.json`;
899
+ const filePath = path.join(failedDir, fileName);
900
+ fs.writeFileSync(filePath, JSON.stringify(batchPayload, null, 2));
901
+ console.log(`✅ Batch salvo para retry: ${fileName}`);
902
+ }
903
+ catch (error) {
904
+ console.error('❌ Erro ao salvar batch falhado:', error);
905
+ }
906
+ }
907
+ /**
908
+ * Limpa cache de sistemas
909
+ */
910
+ clearCache() {
911
+ console.log(`🧹 Limpando cache de sistemas...`);
912
+ this.systemsCache = null;
913
+ console.log(`✅ Cache em memória limpo`);
914
+ try {
915
+ if (fs.existsSync(this.cacheFilePath)) {
916
+ fs.unlinkSync(this.cacheFilePath);
917
+ console.log(`✅ Arquivo de cache removido`);
918
+ }
919
+ }
920
+ catch (error) {
921
+ console.warn('⚠️ Erro ao limpar cache:', error);
922
+ }
923
+ }
924
+ /**
925
+ * Testa conectividade com o backend
926
+ */
927
+ async testConnection() {
928
+ console.log(`🔌 Testando conectividade com AutoCore Hub backend...`);
929
+ console.log(`🔗 URL de teste: ${this.hubApiUrl}/systems`);
930
+ try {
931
+ const startTime = Date.now();
932
+ await this.fetchGet('/systems');
933
+ const responseTime = Date.now() - startTime;
934
+ console.log(`✅ Conectividade OK - tempo de resposta: ${responseTime}ms`);
935
+ return true;
936
+ }
937
+ catch (error) {
938
+ console.error(`❌ Falha na conectividade com AutoCore Hub`);
939
+ console.error(`🔍 Detalhes da falha:`);
940
+ console.error(` - Erro: ${error.message}`);
941
+ console.error(` - Código: ${error.code || error?.cause?.code || 'N/A'}`);
942
+ console.error(` - Status: ${error.status || 'N/A'}`);
943
+ return false;
944
+ }
945
+ }
946
+ /**
947
+ * Converte sistemas do AutoCore Hub para formato TestHub
948
+ */
949
+ convertHubSystemsToTestHub(hubSystems) {
950
+ const normalizeSystemType = (raw) => {
951
+ if (!raw && raw !== 0)
952
+ return 'Mixed';
953
+ const s = String(raw).toUpperCase();
954
+ // Map common backend tokens to friendly values used in framework
955
+ switch (s) {
956
+ case 'WEB':
957
+ return 'Frontend';
958
+ case 'API':
959
+ return 'API';
960
+ case 'SSH':
961
+ return 'SSH';
962
+ case 'BANCO':
963
+ case 'DATABASE':
964
+ return 'Banco';
965
+ case 'MOBILE':
966
+ return 'Mobile';
967
+ case 'PROD':
968
+ case 'HML':
969
+ case 'DEV':
970
+ case 'STAGING':
971
+ case 'PRODUCTION':
972
+ return s.toLowerCase();
973
+ default:
974
+ return raw;
975
+ }
976
+ };
977
+ return hubSystems.map((hubSystem) => {
978
+ const systemType = normalizeSystemType(hubSystem.type || hubSystem.category || '');
979
+ return {
980
+ id: hubSystem.id,
981
+ name: hubSystem.name,
982
+ description: hubSystem.description || '',
983
+ url: hubSystem.url || '',
984
+ // Keep the backend-provided type (normalized) instead of forcing 'API'
985
+ type: systemType,
986
+ status: hubSystem.status,
987
+ isActive: hubSystem.isActive,
988
+ // isPrivate removed as per the patch
989
+ sidService: hubSystem.sidService,
990
+ environments: hubSystem.environments?.map((env) => {
991
+ const envType = normalizeSystemType(env.type || env.environmentCategory || env.name || '');
992
+ return {
993
+ id: env.id,
994
+ name: env.name,
995
+ // preserve environment type from backend (ex: PROD, DEV) but normalize common tokens
996
+ type: envType,
997
+ url: env.url,
998
+ port: env.port,
999
+ username: env.username,
1000
+ password: env.password,
1001
+ serviceName: env.serviceName,
1002
+ privateKeyPath: env.privateKeyPath,
1003
+ environmentCategory: env.type || env.environmentCategory || env.name,
1004
+ variables: env.variables?.map((v) => ({
1005
+ id: v.id,
1006
+ key: v.key,
1007
+ value: v.value,
1008
+ })) || [],
1009
+ // Mapear novos campos retornados pelo backend (frameworkVariables / credentials)
1010
+ frameworkVariables: env.frameworkVariables || {},
1011
+ requiredFrameworkVariables: env.requiredFrameworkVariables || [],
1012
+ credentials: env.credentials?.map((c) => ({
1013
+ id: c.id,
1014
+ label: c.label,
1015
+ suffix: c.suffix ?? null,
1016
+ description: c.description ?? null,
1017
+ url: c.url ?? null,
1018
+ port: c.port ?? null,
1019
+ service: c.service ?? null,
1020
+ username: c.username ?? null,
1021
+ password: c.password ?? null,
1022
+ isDefault: c.isDefault ?? false,
1023
+ isActive: c.isActive ?? true,
1024
+ displayOrder: c.displayOrder ?? 0,
1025
+ })) || [],
1026
+ isPrivate: Boolean(env.isPrivate),
1027
+ privateMembers: env.privateMembers || [],
1028
+ };
1029
+ }) || [],
1030
+ };
1031
+ });
1032
+ }
1033
+ /**
1034
+ * Obtém User ID do AutoCore Hub (com autenticação automática)
1035
+ */
1036
+ async getUserId() {
1037
+ if (this.cachedUserId) {
1038
+ console.log('📦 Usando User ID em cache');
1039
+ return this.cachedUserId;
1040
+ }
1041
+ console.log('🔐 Autenticando usuário no AutoCore Hub...');
1042
+ const identifier = await this.getUserIdentifier();
1043
+ try {
1044
+ let user;
1045
+ if (identifier.type === 'email') {
1046
+ console.log(`📧 Buscando usuário por email: ${identifier.value}`);
1047
+ user = await this.fetchGet(`/auth/user-by-email/${encodeURIComponent(identifier.value)}`);
1048
+ }
1049
+ else {
1050
+ console.log(`🎫 Buscando usuário por matrícula: ${identifier.value}`);
1051
+ user = await this.fetchGet(`/auth/user-by-matricula/${identifier.value}`);
1052
+ }
1053
+ this.cachedUserId = user.id;
1054
+ console.log(`✅ Autenticado como: ${user.name} (${user.matricula})`);
1055
+ console.log(`📧 Email: ${user.email}`);
1056
+ console.log(`🏢 Diretoria: ${user.diretoria || 'N/A'}`);
1057
+ console.log(`👥 Equipe: ${user.equipe || 'N/A'}`);
1058
+ console.log(`🆔 User ID: ${user.id}`);
1059
+ return user.id;
1060
+ }
1061
+ catch (error) {
1062
+ if (error.status === 404) {
1063
+ const notFoundMsg = identifier.type === 'email'
1064
+ ? `Usuário com email "${identifier.value}" não encontrado no AutoCore Hub`
1065
+ : `Usuário com matrícula "${identifier.value}" não encontrado no AutoCore Hub`;
1066
+ console.error(`❌ ${notFoundMsg}`);
1067
+ console.error('💡 Dica: Verifique se o usuário está cadastrado no AutoCore Hub');
1068
+ throw new Error(notFoundMsg);
1069
+ }
1070
+ console.error('❌ Erro ao autenticar usuário:', error.message);
1071
+ throw error;
1072
+ }
1073
+ }
1074
+ /**
1075
+ * Detecta ambiente de execução e obtém identificador do usuário
1076
+ */
1077
+ async getUserIdentifier() {
1078
+ console.log('🔍 Detectando ambiente de execução...');
1079
+ // Verificar se estamos em ambiente CI/Azure
1080
+ if (isAzureEnvironment()) {
1081
+ console.log('🔵 Azure Pipeline detectado');
1082
+ // Tentar obter email do ambiente
1083
+ let userEmail = process.env.BUILD_REQUESTEDFOREMAIL || process.env.AUTOCORE_USER_EMAIL;
1084
+ if (!userEmail) {
1085
+ console.log('📡 BUILD_REQUESTEDFOREMAIL não disponível, tentando Azure DevOps API...');
1086
+ const apiEmail = await this.getAzureUserEmailFromAPI();
1087
+ if (apiEmail) {
1088
+ userEmail = apiEmail;
1089
+ }
1090
+ }
1091
+ if (userEmail) {
1092
+ console.log(`👤 Azure User Email: ${userEmail}`);
1093
+ // Verificar se o email contém uma matrícula (padrão A + 7 dígitos)
1094
+ const matriculaMatch = userEmail.match(/A\d{7}/);
1095
+ if (matriculaMatch) {
1096
+ console.log(`🎯 Matrícula extraída do email: ${matriculaMatch[0]}`);
1097
+ return { type: 'matricula', value: matriculaMatch[0] };
1098
+ }
1099
+ return { type: 'email', value: userEmail };
1100
+ }
1101
+ throw new Error('Não foi possível obter informações do usuário no Azure Pipeline. ' +
1102
+ 'Verifique se BUILD_REQUESTEDFOREMAIL está disponível ou configure AZURE_DEVOPS_PAT.');
1103
+ }
1104
+ else {
1105
+ console.log('💻 Execução local detectada');
1106
+ const matricula = process.env.AUTOCORE_USER_MATRICULA ||
1107
+ process.env.USERNAME ||
1108
+ process.env.USER;
1109
+ if (!matricula) {
1110
+ throw new Error('Matrícula não configurada para execução local. ' +
1111
+ 'Configure AUTOCORE_USER_MATRICULA no .env ou defina USERNAME/USER.');
1112
+ }
1113
+ console.log(`👤 Matrícula local: ${matricula}`);
1114
+ return { type: 'matricula', value: matricula };
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Obtém email do usuário via Azure DevOps REST API
1119
+ */
1120
+ async getAzureUserEmailFromAPI() {
1121
+ try {
1122
+ const azureDevOpsPAT = process.env.AZURE_DEVOPS_PAT;
1123
+ if (!azureDevOpsPAT) {
1124
+ console.warn('⚠️ AZURE_DEVOPS_PAT não configurado, não é possível usar Azure DevOps API');
1125
+ return null;
1126
+ }
1127
+ const organization = process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI?.split('/').pop() || '';
1128
+ const project = process.env.SYSTEM_TEAMPROJECT || '';
1129
+ const buildId = process.env.BUILD_BUILDID || '';
1130
+ if (!organization || !project || !buildId) {
1131
+ console.warn('⚠️ Variáveis do Azure Pipeline não disponíveis para API call');
1132
+ return null;
1133
+ }
1134
+ const url = `https://dev.azure.com/${organization}/${project}/_apis/build/builds/${buildId}?api-version=7.2`;
1135
+ console.log(`📡 Chamando Azure DevOps API: ${url}`);
1136
+ const auth = Buffer.from(`:${azureDevOpsPAT}`).toString('base64');
1137
+ const response = await fetchWithTimeout(url, {
1138
+ method: 'GET',
1139
+ headers: {
1140
+ Authorization: `Basic ${auth}`,
1141
+ 'Content-Type': 'application/json',
1142
+ },
1143
+ timeout: 10000,
1144
+ });
1145
+ if (!response.ok) {
1146
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1147
+ }
1148
+ const buildInfo = await response.json();
1149
+ const userEmail = buildInfo.requestedFor.uniqueName;
1150
+ console.log(`✅ Azure DevOps API - Usuário: ${buildInfo.requestedFor.displayName}`);
1151
+ console.log(`📧 Email obtido: ${userEmail}`);
1152
+ console.log(`🏗️ Build: ${buildInfo.buildNumber}`);
1153
+ return userEmail;
1154
+ }
1155
+ catch (error) {
1156
+ console.error('❌ Erro ao chamar Azure DevOps API:', error.message);
1157
+ console.error('💡 Dica: Verifique se AZURE_DEVOPS_PAT está configurado e tem permissões de Build (Read)');
1158
+ return null;
1159
+ }
1160
+ }
1161
+ /**
1162
+ * Envia execução de cenário para o AutoCore Hub
1163
+ */
1164
+ async sendScenarioExecution(payload) {
1165
+ console.log(`📤 Enviando execução de cenário: ${payload.cenarioNome}`);
1166
+ console.log(`🎯 Sistema: ${payload.sistemaNome || 'N/A'}`);
1167
+ console.log(`🌍 Ambiente: ${payload.ambiente}`);
1168
+ console.log(`📊 Status: ${payload.status || 'executando'}`);
1169
+ const userId = await this.getUserId();
1170
+ try {
1171
+ const response = await this.fetchPost('/execucoes-cenarios', payload, {
1172
+ 'X-User-Id': userId,
1173
+ });
1174
+ console.log(`✅ Execução criada no AutoCore Hub - ID: ${response.id}`);
1175
+ return response.id;
1176
+ }
1177
+ catch (error) {
1178
+ console.error('❌ Erro ao enviar execução para o AutoCore Hub:', error.message);
1179
+ if (error.status === 400) {
1180
+ console.error('🔍 Erro de validação - verifique o payload:');
1181
+ console.error('📄 Payload enviado:', JSON.stringify(payload, null, 2));
1182
+ if (error.responseText) {
1183
+ console.error('📋 Resposta do servidor:', error.responseText);
1184
+ }
1185
+ }
1186
+ if (error.status === 401) {
1187
+ console.error('🔐 Erro de autenticação - verifique se o User ID é válido');
1188
+ console.error('🆔 User ID usado:', userId);
1189
+ }
1190
+ // Salvar execução localmente para retry
1191
+ this.saveFailedScenarioExecution(payload);
1192
+ throw error;
1193
+ }
1194
+ }
1195
+ /**
1196
+ * Salva execução de cenário falhada para retry posterior
1197
+ */
1198
+ saveFailedScenarioExecution(payload) {
1199
+ try {
1200
+ const failedDir = path.join(process.cwd(), 'test-results', '.hub-failed-scenarios');
1201
+ if (!fs.existsSync(failedDir)) {
1202
+ fs.mkdirSync(failedDir, { recursive: true });
1203
+ }
1204
+ const timestamp = Date.now();
1205
+ const fileName = `scenario-${timestamp}.json`;
1206
+ const filePath = path.join(failedDir, fileName);
1207
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
1208
+ console.log(`💾 Execução de cenário salva para retry: ${fileName}`);
1209
+ }
1210
+ catch (error) {
1211
+ console.error('❌ Erro ao salvar execução de cenário falhada:', error);
1212
+ }
1213
+ }
1214
+ }
1215
+ export default TestHubClient;