@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.
- package/.github/copilot-instructions.md +520 -0
- package/biome.json +37 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +169 -0
- package/dist/scripts/consumer-postinstall.d.ts +15 -0
- package/dist/scripts/consumer-postinstall.js +785 -0
- package/dist/scripts/generate-docs.d.ts +16 -0
- package/dist/scripts/generate-docs.js +1363 -0
- package/dist/scripts/generate-index.d.ts +2 -0
- package/dist/scripts/generate-index.js +314 -0
- package/dist/scripts/init-api.d.ts +2 -0
- package/dist/scripts/init-api.js +525 -0
- package/dist/scripts/init-banco.d.ts +2 -0
- package/dist/scripts/init-banco.js +347 -0
- package/dist/scripts/init-frontend.d.ts +2 -0
- package/dist/scripts/init-frontend.js +627 -0
- package/dist/scripts/init-mobile.d.ts +2 -0
- package/dist/scripts/init-mobile.js +481 -0
- package/dist/scripts/init-scenarios.d.ts +2 -0
- package/dist/scripts/init-scenarios.js +846 -0
- package/dist/scripts/init-ssh.d.ts +2 -0
- package/dist/scripts/init-ssh.js +639 -0
- package/dist/scripts/package-versions.d.ts +57 -0
- package/dist/scripts/package-versions.js +768 -0
- package/dist/scripts/postinstall.d.ts +1 -0
- package/dist/scripts/postinstall.js +527 -0
- package/dist/scripts/robust-build.d.ts +7 -0
- package/dist/scripts/robust-build.js +88 -0
- package/dist/scripts/setup-local-packages.d.ts +31 -0
- package/dist/scripts/setup-local-packages.js +237 -0
- package/dist/scripts/smart-override.d.ts +2 -0
- package/dist/scripts/smart-override.js +1360 -0
- package/dist/scripts/sync-configs.d.ts +27 -0
- package/dist/scripts/sync-configs.js +248 -0
- package/dist/scripts/test-biome-parse.d.ts +5 -0
- package/dist/scripts/test-biome-parse.js +84 -0
- package/dist/scripts/ultracite-setup.d.ts +4 -0
- package/dist/scripts/ultracite-setup.js +310 -0
- package/dist/scripts/update-all-init-scripts.d.ts +2 -0
- package/dist/scripts/update-all-init-scripts.js +52 -0
- package/dist/scripts/update-biome-schema.d.ts +15 -0
- package/dist/scripts/update-biome-schema.js +124 -0
- package/dist/src/AutoCoreFacade.d.ts +145 -0
- package/dist/src/AutoCoreFacade.js +217 -0
- package/dist/src/api/ApiActions.d.ts +297 -0
- package/dist/src/api/ApiActions.js +1905 -0
- package/dist/src/api/Certificate.d.ts +60 -0
- package/dist/src/api/Certificate.js +79 -0
- package/dist/src/api/JsonResponse.d.ts +116 -0
- package/dist/src/api/JsonResponse.js +206 -0
- package/dist/src/appium/DeviceFarmViewer.d.ts +79 -0
- package/dist/src/appium/DeviceFarmViewer.js +1083 -0
- package/dist/src/appium/MobileActions.d.ts +347 -0
- package/dist/src/appium/MobileActions.js +1632 -0
- package/dist/src/appium/MobileConnection.d.ts +160 -0
- package/dist/src/appium/MobileConnection.js +772 -0
- package/dist/src/config/envLoader.d.ts +123 -0
- package/dist/src/config/envLoader.js +361 -0
- package/dist/src/config/jest-safe-setup.d.ts +19 -0
- package/dist/src/config/jest-safe-setup.js +369 -0
- package/dist/src/config/timeouts.d.ts +32 -0
- package/dist/src/config/timeouts.js +38 -0
- package/dist/src/desktop/DesktopActions.d.ts +46 -0
- package/dist/src/desktop/DesktopActions.js +398 -0
- package/dist/src/desktop/DesktopConnection.d.ts +32 -0
- package/dist/src/desktop/DesktopConnection.js +84 -0
- package/dist/src/domain/entities/TestExecution.d.ts +117 -0
- package/dist/src/domain/entities/TestExecution.js +150 -0
- package/dist/src/domain/entities/TestReport.d.ts +114 -0
- package/dist/src/domain/entities/TestReport.js +179 -0
- package/dist/src/domain/repositories/ITestRepository.d.ts +196 -0
- package/dist/src/domain/repositories/ITestRepository.js +14 -0
- package/dist/src/domain/schemas/ValidationSchemas.d.ts +159 -0
- package/dist/src/domain/schemas/ValidationSchemas.js +181 -0
- package/dist/src/functions/errors/BaseError.d.ts +78 -0
- package/dist/src/functions/errors/BaseError.js +245 -0
- package/dist/src/functions/errors/ConfigurationError.d.ts +16 -0
- package/dist/src/functions/errors/ConfigurationError.js +48 -0
- package/dist/src/functions/errors/ErrorCatalog.d.ts +148 -0
- package/dist/src/functions/errors/ErrorCatalog.js +157 -0
- package/dist/src/functions/errors/GlobalErrorHandler.d.ts +101 -0
- package/dist/src/functions/errors/GlobalErrorHandler.js +281 -0
- package/dist/src/functions/errors/IntegrationError.d.ts +17 -0
- package/dist/src/functions/errors/IntegrationError.js +51 -0
- package/dist/src/functions/errors/SecurityError.d.ts +14 -0
- package/dist/src/functions/errors/SecurityError.js +42 -0
- package/dist/src/functions/errors/SystemError.d.ts +12 -0
- package/dist/src/functions/errors/SystemError.js +36 -0
- package/dist/src/functions/errors/ValidationError.d.ts +14 -0
- package/dist/src/functions/errors/ValidationError.js +61 -0
- package/dist/src/functions/errors/index.d.ts +12 -0
- package/dist/src/functions/errors/index.js +13 -0
- package/dist/src/global-setup.d.ts +1 -0
- package/dist/src/global-setup.js +1037 -0
- package/dist/src/helpers/BancoActions.d.ts +188 -0
- package/dist/src/helpers/BancoActions.js +581 -0
- package/dist/src/helpers/EnviromentHelper.d.ts +17 -0
- package/dist/src/helpers/EnviromentHelper.js +66 -0
- package/dist/src/helpers/ParallelExecutionHelper.d.ts +183 -0
- package/dist/src/helpers/ParallelExecutionHelper.js +375 -0
- package/dist/src/helpers/SyncSignal.d.ts +15 -0
- package/dist/src/helpers/SyncSignal.js +44 -0
- package/dist/src/hubdocs/CategoryDetector.d.ts +83 -0
- package/dist/src/hubdocs/CategoryDetector.js +401 -0
- package/dist/src/hubdocs/DirectStatementInterceptor.d.ts +54 -0
- package/dist/src/hubdocs/DirectStatementInterceptor.js +243 -0
- package/dist/src/hubdocs/ExecutionTracker.d.ts +107 -0
- package/dist/src/hubdocs/ExecutionTracker.js +702 -0
- package/dist/src/hubdocs/HubDocs.d.ts +395 -0
- package/dist/src/hubdocs/HubDocs.js +3586 -0
- package/dist/src/hubdocs/StatementMethodFilter.d.ts +71 -0
- package/dist/src/hubdocs/StatementMethodFilter.js +618 -0
- package/dist/src/hubdocs/StatementTracker.d.ts +417 -0
- package/dist/src/hubdocs/StatementTracker.js +2419 -0
- package/dist/src/hubdocs/SwaggerGenerator.d.ts +59 -0
- package/dist/src/hubdocs/SwaggerGenerator.js +405 -0
- package/dist/src/hubdocs/index.d.ts +9 -0
- package/dist/src/hubdocs/index.js +9 -0
- package/dist/src/hubdocs/types.d.ts +114 -0
- package/dist/src/hubdocs/types.js +5 -0
- package/dist/src/infrastructure/DependencyContainer.d.ts +142 -0
- package/dist/src/infrastructure/DependencyContainer.js +250 -0
- package/dist/src/infrastructure/adapters/AppiumAdapter.d.ts +168 -0
- package/dist/src/infrastructure/adapters/AppiumAdapter.js +468 -0
- package/dist/src/infrastructure/adapters/OracleAdapter.d.ts +150 -0
- package/dist/src/infrastructure/adapters/OracleAdapter.js +388 -0
- package/dist/src/infrastructure/adapters/PlaywrightAdapter.d.ts +192 -0
- package/dist/src/infrastructure/adapters/PlaywrightAdapter.js +382 -0
- package/dist/src/infrastructure/adapters/SSHAdapter.d.ts +141 -0
- package/dist/src/infrastructure/adapters/SSHAdapter.js +428 -0
- package/dist/src/interfaces.d.ts +501 -0
- package/dist/src/interfaces.js +25 -0
- package/dist/src/internal/fakes/__fake-actions__.d.ts +17 -0
- package/dist/src/internal/fakes/__fake-actions__.js +21 -0
- package/dist/src/internal/fakes/__forbidden__.d.ts +10 -0
- package/dist/src/internal/fakes/__forbidden__.js +18 -0
- package/dist/src/internal/fakes/__honeypot__.d.ts +15 -0
- package/dist/src/internal/fakes/__honeypot__.js +24 -0
- package/dist/src/octane/OctaneReporter.d.ts +13 -0
- package/dist/src/octane/OctaneReporter.js +61 -0
- package/dist/src/playwright/CryptoActions.d.ts +20 -0
- package/dist/src/playwright/CryptoActions.js +75 -0
- package/dist/src/playwright/EnhancedWebActions.d.ts +7 -0
- package/dist/src/playwright/EnhancedWebActions.js +65 -0
- package/dist/src/playwright/WebActions.d.ts +1599 -0
- package/dist/src/playwright/WebActions.js +11788 -0
- package/dist/src/playwright/actions/ActionTimeline.d.ts +36 -0
- package/dist/src/playwright/actions/ActionTimeline.js +101 -0
- package/dist/src/playwright/actions/RecoveryQueue.d.ts +82 -0
- package/dist/src/playwright/actions/RecoveryQueue.js +130 -0
- package/dist/src/playwright/actions/SelectorCache.d.ts +53 -0
- package/dist/src/playwright/actions/SelectorCache.js +96 -0
- package/dist/src/playwright/actions/index.d.ts +13 -0
- package/dist/src/playwright/actions/index.js +14 -0
- package/dist/src/playwright/actions/types.d.ts +147 -0
- package/dist/src/playwright/actions/types.js +5 -0
- package/dist/src/playwright/fixtures.d.ts +112 -0
- package/dist/src/playwright/fixtures.js +718 -0
- package/dist/src/playwright/network-logs-reporter.d.ts +7 -0
- package/dist/src/playwright/network-logs-reporter.js +66 -0
- package/dist/src/playwright/registerRecoveryWrappers.d.ts +1 -0
- package/dist/src/playwright/registerRecoveryWrappers.js +54 -0
- package/dist/src/security/BuildSecurity.d.ts +12 -0
- package/dist/src/security/BuildSecurity.js +138 -0
- package/dist/src/security/EulaProtection.d.ts +70 -0
- package/dist/src/security/EulaProtection.js +155 -0
- package/dist/src/security/HoneypotManager.d.ts +46 -0
- package/dist/src/security/HoneypotManager.js +234 -0
- package/dist/src/security/KeysManager.d.ts +36 -0
- package/dist/src/security/KeysManager.js +158 -0
- package/dist/src/security/ProofOfWorkIntegration.d.ts +64 -0
- package/dist/src/security/ProofOfWorkIntegration.js +206 -0
- package/dist/src/security/SecurityValidation.d.ts +21 -0
- package/dist/src/security/SecurityValidation.js +163 -0
- package/dist/src/security/SourceMapProtection.d.ts +55 -0
- package/dist/src/security/SourceMapProtection.js +220 -0
- package/dist/src/security/protector.d.ts +1 -0
- package/dist/src/security/protector.js +97 -0
- package/dist/src/ssh/SSHActions.d.ts +262 -0
- package/dist/src/ssh/SSHActions.js +790 -0
- package/dist/src/ssh/SSHClient.d.ts +99 -0
- package/dist/src/ssh/SSHClient.js +409 -0
- package/dist/src/statements/BaseStatement.d.ts +38 -0
- package/dist/src/statements/BaseStatement.js +78 -0
- package/dist/src/testContext/AuthStateManager.d.ts +93 -0
- package/dist/src/testContext/AuthStateManager.js +256 -0
- package/dist/src/testContext/CoverageManager.d.ts +198 -0
- package/dist/src/testContext/CoverageManager.js +917 -0
- package/dist/src/testContext/TestAnnotations.d.ts +476 -0
- package/dist/src/testContext/TestAnnotations.js +2647 -0
- package/dist/src/testContext/TestContext.d.ts +138 -0
- package/dist/src/testContext/TestContext.js +369 -0
- package/dist/src/testContext/UnifiedHtmlGenerator.d.ts +7 -0
- package/dist/src/testContext/UnifiedHtmlGenerator.js +264 -0
- package/dist/src/testContext/UnifiedReportManager.d.ts +211 -0
- package/dist/src/testContext/UnifiedReportManager.js +1206 -0
- package/dist/src/testhub/DynamicConfigManager.d.ts +121 -0
- package/dist/src/testhub/DynamicConfigManager.js +320 -0
- package/dist/src/testhub/SystemsManager.d.ts +119 -0
- package/dist/src/testhub/SystemsManager.js +365 -0
- package/dist/src/testhub/TestHubClient.d.ts +335 -0
- package/dist/src/testhub/TestHubClient.js +1215 -0
- package/dist/src/testhub/TestHubReporter.d.ts +62 -0
- package/dist/src/testhub/TestHubReporter.js +576 -0
- package/dist/src/testhub/TestHubVars.d.ts +116 -0
- package/dist/src/testhub/TestHubVars.js +273 -0
- package/dist/src/utils/ActionInterceptor.d.ts +59 -0
- package/dist/src/utils/ActionInterceptor.js +741 -0
- package/dist/src/utils/ArtifactsCompressor.d.ts +43 -0
- package/dist/src/utils/ArtifactsCompressor.js +181 -0
- package/dist/src/utils/AutoLogsFinal.d.ts +47 -0
- package/dist/src/utils/AutoLogsFinal.js +148 -0
- package/dist/src/utils/CodeGenSession.d.ts +114 -0
- package/dist/src/utils/CodeGenSession.js +264 -0
- package/dist/src/utils/ConfigLogger.d.ts +133 -0
- package/dist/src/utils/ConfigLogger.js +611 -0
- package/dist/src/utils/CustomReporter.d.ts +22 -0
- package/dist/src/utils/CustomReporter.js +352 -0
- package/dist/src/utils/DataStore.d.ts +171 -0
- package/dist/src/utils/DataStore.js +484 -0
- package/dist/src/utils/DatabaseInterceptor.d.ts +19 -0
- package/dist/src/utils/DatabaseInterceptor.js +295 -0
- package/dist/src/utils/DateHelper.d.ts +16 -0
- package/dist/src/utils/DateHelper.js +120 -0
- package/dist/src/utils/DateValidator.d.ts +4 -0
- package/dist/src/utils/DateValidator.js +51 -0
- package/dist/src/utils/DocumentGenerator.d.ts +35 -0
- package/dist/src/utils/DocumentGenerator.js +129 -0
- package/dist/src/utils/EvidenceCapture.d.ts +90 -0
- package/dist/src/utils/EvidenceCapture.js +600 -0
- package/dist/src/utils/EvidenceReportGenerator.d.ts +70 -0
- package/dist/src/utils/EvidenceReportGenerator.js +799 -0
- package/dist/src/utils/FrameManagementUtil.d.ts +42 -0
- package/dist/src/utils/FrameManagementUtil.js +75 -0
- package/dist/src/utils/GlobalStatementsInterceptor.d.ts +1 -0
- package/dist/src/utils/GlobalStatementsInterceptor.js +1 -0
- package/dist/src/utils/HTMLTemplate.d.ts +1 -0
- package/dist/src/utils/HTMLTemplate.js +1034 -0
- package/dist/src/utils/InterceptacaoMagica.d.ts +23 -0
- package/dist/src/utils/InterceptacaoMagica.js +365 -0
- package/dist/src/utils/LogSanitizer.d.ts +35 -0
- package/dist/src/utils/LogSanitizer.js +110 -0
- package/dist/src/utils/Logger.d.ts +65 -0
- package/dist/src/utils/Logger.js +284 -0
- package/dist/src/utils/McpLocalClient.d.ts +141 -0
- package/dist/src/utils/McpLocalClient.js +871 -0
- package/dist/src/utils/PDFEvidenceGenerator.d.ts +20 -0
- package/dist/src/utils/PDFEvidenceGenerator.js +156 -0
- package/dist/src/utils/SpecFileAnalyzer.d.ts +35 -0
- package/dist/src/utils/SpecFileAnalyzer.js +209 -0
- package/dist/src/utils/StatementInterceptor.d.ts +18 -0
- package/dist/src/utils/StatementInterceptor.js +87 -0
- package/dist/src/utils/StatementLogger.d.ts +33 -0
- package/dist/src/utils/StatementLogger.js +113 -0
- package/dist/src/utils/StatementsInterceptor.d.ts +1 -0
- package/dist/src/utils/StatementsInterceptor.js +1 -0
- package/dist/src/utils/TeamsFlushHook.d.ts +17 -0
- package/dist/src/utils/TeamsFlushHook.js +168 -0
- package/dist/src/utils/TerminalLogCapture.d.ts +158 -0
- package/dist/src/utils/TerminalLogCapture.js +531 -0
- package/dist/src/utils/TestMethodLogger.d.ts +70 -0
- package/dist/src/utils/TestMethodLogger.js +95 -0
- package/dist/src/utils/UnifiedTeardown.d.ts +4 -0
- package/dist/src/utils/UnifiedTeardown.js +400 -0
- package/dist/src/utils/XPathCatalog.d.ts +152 -0
- package/dist/src/utils/XPathCatalog.js +350 -0
- package/dist/src/utils/generators.d.ts +90 -0
- package/dist/src/utils/generators.js +167 -0
- package/dist/src/utils/testRecovery/ResilientPlaywright.d.ts +152 -0
- package/dist/src/utils/testRecovery/ResilientPlaywright.js +715 -0
- package/dist/src/utils/testRecovery/TestRecoveryClient.d.ts +801 -0
- package/dist/src/utils/testRecovery/TestRecoveryClient.js +1415 -0
- package/dist/src/utils/testRecovery/autoFixCode.d.ts +65 -0
- package/dist/src/utils/testRecovery/autoFixCode.js +32 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +59 -0
- package/dist/wdio.conf.d.ts +1 -0
- package/dist/wdio.conf.js +420 -0
- package/package.json +137 -0
- package/protect-loader.mjs +643 -0
- package/scripts/consumer-postinstall.ts +975 -0
- package/scripts/generate-index.ts +343 -0
- package/scripts/init-api.ts +613 -0
- package/scripts/init-banco.ts +437 -0
- package/scripts/init-frontend.ts +727 -0
- package/scripts/init-mobile.ts +558 -0
- package/scripts/init-scenarios.ts +925 -0
- package/scripts/init-ssh.ts +734 -0
- package/scripts/package-versions.ts +978 -0
- package/scripts/postinstall.ts +605 -0
- package/scripts/smart-override.ts +1675 -0
- package/scripts/sync-configs.ts +302 -0
- package/scripts/ultracite-setup.ts +370 -0
- package/src/types/globals.d.ts +48 -0
- package/tsconfig.json +29 -0
- 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;
|