@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,1905 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { request as playwrightRequest, } from '@playwright/test';
|
|
3
|
+
import { IntegrationError, ValidationError, } from '../functions/errors/index.js';
|
|
4
|
+
import { TestContext } from '../testContext/TestContext.js';
|
|
5
|
+
import { UnifiedReportManager } from '../testContext/UnifiedReportManager.js';
|
|
6
|
+
import { Logger } from '../utils/Logger.js';
|
|
7
|
+
import { createCertificate } from './Certificate.js';
|
|
8
|
+
import { JsonResponse } from './JsonResponse.js';
|
|
9
|
+
// 🤖 IMPORTAÇÃO CRÍTICA: Força inicialização do StatementTracker
|
|
10
|
+
import '../hubdocs/StatementTracker.js';
|
|
11
|
+
import { ExecutionTracker } from '../hubdocs/ExecutionTracker.js';
|
|
12
|
+
import { StatementTracker } from '../hubdocs/StatementTracker.js';
|
|
13
|
+
import DynamicConfigManager from '../testhub/DynamicConfigManager.js';
|
|
14
|
+
export { createCertificate };
|
|
15
|
+
/**
|
|
16
|
+
* ✅ APIACTIONS DEFINITIVA: Interface Nova Avançada
|
|
17
|
+
*
|
|
18
|
+
* Interface Unificada:
|
|
19
|
+
* - get(request, descrição, ignoreSSL) -> ApiResponse
|
|
20
|
+
* - post(request, descrição, ignoreSSL) -> ApiResponse
|
|
21
|
+
*
|
|
22
|
+
* Melhorias Incluídas:
|
|
23
|
+
* - Tratamento de erros especializado
|
|
24
|
+
* - Logs estruturados e mascaramento de dados sensíveis
|
|
25
|
+
* - Métricas de performance automáticas
|
|
26
|
+
* - Compatibilidade com TestContext automática
|
|
27
|
+
* - Bypass automático do sistema de proteção
|
|
28
|
+
*/
|
|
29
|
+
export class ApiActions {
|
|
30
|
+
static successCount = 0;
|
|
31
|
+
static failureCount = 0;
|
|
32
|
+
static inicioExecucao;
|
|
33
|
+
static fimExecucao;
|
|
34
|
+
static logsPerTest = {};
|
|
35
|
+
static duracoes = [];
|
|
36
|
+
static foiUsado = false;
|
|
37
|
+
static requestCount = 0;
|
|
38
|
+
static contextCache = new Map();
|
|
39
|
+
static dadosGerais = {
|
|
40
|
+
inicio: null,
|
|
41
|
+
fim: null,
|
|
42
|
+
totalRequisicoes: 0,
|
|
43
|
+
totalSuccesso: 0,
|
|
44
|
+
totalFalhas: 0,
|
|
45
|
+
};
|
|
46
|
+
static marcarUsoAutomatico() {
|
|
47
|
+
if (!ApiActions.foiUsado) {
|
|
48
|
+
ApiActions.foiUsado = true;
|
|
49
|
+
ApiActions.registrarInicioExecucao();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* ✅ Limpa o cache de contextos, descartando recursos corretamente
|
|
54
|
+
* Deve ser chamado quando um novo CT inicia ou quando o sistema muda
|
|
55
|
+
*/
|
|
56
|
+
static async clearContextCache() {
|
|
57
|
+
const disposePromises = [];
|
|
58
|
+
for (const ctx of ApiActions.contextCache.values()) {
|
|
59
|
+
disposePromises.push(ctx.dispose().catch(() => { }));
|
|
60
|
+
}
|
|
61
|
+
await Promise.all(disposePromises);
|
|
62
|
+
ApiActions.contextCache.clear();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* ✅ Garante que TestContext.apiContext existe
|
|
66
|
+
*/
|
|
67
|
+
static async ensureApiContext() {
|
|
68
|
+
try {
|
|
69
|
+
// Tentar usar TestContext.apiContext se disponível
|
|
70
|
+
return await TestContext.getOrCreateApiContext();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Fallback: criar contexto direto
|
|
74
|
+
return await playwrightRequest.newContext({
|
|
75
|
+
ignoreHTTPSErrors: true,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
static createApiResponse(statusCode, responseHeaders, responseBody, duration) {
|
|
80
|
+
// ✅ VALIDAÇÃO CRÍTICA: Garantir que os parâmetros são válidos
|
|
81
|
+
const safeStatusCode = typeof statusCode === 'number' ? statusCode : 500;
|
|
82
|
+
const safeHeaders = responseHeaders || {};
|
|
83
|
+
const safeBody = typeof responseBody === 'string'
|
|
84
|
+
? responseBody
|
|
85
|
+
: JSON.stringify({ error: 'Invalid response body' });
|
|
86
|
+
const safeDuration = typeof duration === 'number' ? duration : 0;
|
|
87
|
+
const jsonResponse = new JsonResponse(ApiActions.tryParseJson(safeBody));
|
|
88
|
+
jsonResponse.setStatus(safeStatusCode);
|
|
89
|
+
const apiResponse = {
|
|
90
|
+
status: () => safeStatusCode,
|
|
91
|
+
getStatus: () => safeStatusCode,
|
|
92
|
+
headers: () => safeHeaders,
|
|
93
|
+
text: () => safeBody,
|
|
94
|
+
getString: (key) => jsonResponse.getString(key),
|
|
95
|
+
get: (key) => jsonResponse.get(key),
|
|
96
|
+
getArray: (key) => jsonResponse.getArray(key),
|
|
97
|
+
getObject: (key) => jsonResponse.getObject(key),
|
|
98
|
+
getFromArray: (index) => jsonResponse.getFromArray(index),
|
|
99
|
+
getStringFromArray: (index, field) => jsonResponse.getStringFromArray(index, field),
|
|
100
|
+
getPath: (...path) => jsonResponse.getPath(...path),
|
|
101
|
+
raw: () => jsonResponse.raw(),
|
|
102
|
+
duration: safeDuration,
|
|
103
|
+
};
|
|
104
|
+
// ✅ VALIDAÇÃO FINAL: Verificar se o objeto criado é válido
|
|
105
|
+
if (!apiResponse || typeof apiResponse.getStatus !== 'function') {
|
|
106
|
+
Logger.error('❌ ApiResponse criado é inválido!');
|
|
107
|
+
throw new Error('Falha crítica ao criar ApiResponse válido');
|
|
108
|
+
}
|
|
109
|
+
return apiResponse;
|
|
110
|
+
}
|
|
111
|
+
static foiUtilizado() {
|
|
112
|
+
return ApiActions.foiUsado || ApiActions.requestCount > 0;
|
|
113
|
+
}
|
|
114
|
+
static registrarInicioExecucao() {
|
|
115
|
+
if (!ApiActions.inicioExecucao) {
|
|
116
|
+
ApiActions.inicioExecucao = new Date();
|
|
117
|
+
ApiActions.dadosGerais.inicio = ApiActions.inicioExecucao;
|
|
118
|
+
console.log(`⏱️ Início da execução API registrado: ${ApiActions.inicioExecucao.toLocaleTimeString()}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
static registrarFimExecucao() {
|
|
122
|
+
ApiActions.fimExecucao = new Date();
|
|
123
|
+
ApiActions.dadosGerais.totalRequisicoes = ApiActions.requestCount;
|
|
124
|
+
ApiActions.dadosGerais.totalSuccesso = ApiActions.successCount;
|
|
125
|
+
ApiActions.dadosGerais.totalFalhas = ApiActions.failureCount;
|
|
126
|
+
ApiActions.dadosGerais.fim = ApiActions.fimExecucao;
|
|
127
|
+
if (!ApiActions.dadosGerais.inicio) {
|
|
128
|
+
ApiActions.dadosGerais.inicio = ApiActions.inicioExecucao;
|
|
129
|
+
}
|
|
130
|
+
if (ApiActions.inicioExecucao) {
|
|
131
|
+
const duracao = (ApiActions.fimExecucao.getTime() -
|
|
132
|
+
ApiActions.inicioExecucao.getTime()) /
|
|
133
|
+
1000;
|
|
134
|
+
Logger.info(`⏱️ Execução de APIs concluída em ${duracao.toFixed(2)}s`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
static logEvent(message) {
|
|
138
|
+
if (message.includes('[API]') || message.includes('Log registrado')) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
ApiActions.marcarUsoAutomatico();
|
|
142
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
143
|
+
const testId = testInfo?.title || 'global';
|
|
144
|
+
if (!ApiActions.logsPerTest[testId])
|
|
145
|
+
ApiActions.logsPerTest[testId] = [];
|
|
146
|
+
ApiActions.logsPerTest[testId].push(message);
|
|
147
|
+
if (testInfo?.title) {
|
|
148
|
+
try {
|
|
149
|
+
UnifiedReportManager.adicionarLog(testInfo.title, message);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Silenciosamente ignorar
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* ✅ MÉTODO GET: Interface Nova Avançada
|
|
158
|
+
*
|
|
159
|
+
* @param request Objeto contendo URL, opções e certificado opcional da requisição
|
|
160
|
+
* @param descricao Descrição da ação para logs e relatórios
|
|
161
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL (mutuamente exclusivo com certificate)
|
|
162
|
+
* @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
|
|
163
|
+
*
|
|
164
|
+
* ⚠️ IMPORTANTE: Não use ignoreSSL=true junto com certificate no request.
|
|
165
|
+
* Use apenas um dos dois: ignoreSSL OU certificate.
|
|
166
|
+
*/
|
|
167
|
+
static async get(request, descricao, ignoreSSL = false) {
|
|
168
|
+
ApiActions.marcarUsoAutomatico();
|
|
169
|
+
return ApiActions._request('get', request, descricao, ignoreSSL);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* ✅ MÉTODO POST: Interface Nova Avançada
|
|
173
|
+
*
|
|
174
|
+
* @param request Objeto contendo URL, opções e certificado opcional da requisição
|
|
175
|
+
* @param descricao Descrição da ação para logs e relatórios
|
|
176
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL (mutuamente exclusivo com certificate)
|
|
177
|
+
* @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
|
|
178
|
+
*
|
|
179
|
+
* ⚠️ IMPORTANTE: Não use ignoreSSL=true junto com certificate no request.
|
|
180
|
+
* Use apenas um dos dois: ignoreSSL OU certificate.
|
|
181
|
+
*/
|
|
182
|
+
static async post(request, descricao, ignoreSSL = false) {
|
|
183
|
+
ApiActions.marcarUsoAutomatico();
|
|
184
|
+
return ApiActions._request('post', request, descricao, ignoreSSL);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* ✅ MÉTODO PUT: Interface Nova Avançada
|
|
188
|
+
*
|
|
189
|
+
* @param request Objeto contendo URL e opções da requisição
|
|
190
|
+
* @param descricao Descrição da ação para logs e relatórios
|
|
191
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL
|
|
192
|
+
* @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
|
|
193
|
+
*/
|
|
194
|
+
static async put(request, descricao, ignoreSSL = false) {
|
|
195
|
+
ApiActions.marcarUsoAutomatico();
|
|
196
|
+
return ApiActions._request('put', request, descricao, ignoreSSL);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* ✅ MÉTODO PATCH: Interface Nova Avançada
|
|
200
|
+
*
|
|
201
|
+
* @param request Objeto contendo URL e opções da requisição
|
|
202
|
+
* @param descricao Descrição da ação para logs e relatórios
|
|
203
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL
|
|
204
|
+
* @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
|
|
205
|
+
*/
|
|
206
|
+
static async patch(request, descricao, ignoreSSL = false) {
|
|
207
|
+
ApiActions.marcarUsoAutomatico();
|
|
208
|
+
return ApiActions._request('patch', request, descricao, ignoreSSL);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* ✅ MÉTODO DELETE: Interface Nova Avançada
|
|
212
|
+
*
|
|
213
|
+
* @param request Objeto contendo URL e opções da requisição
|
|
214
|
+
* @param descricao Descrição da ação para logs e relatórios
|
|
215
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL
|
|
216
|
+
* @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
|
|
217
|
+
*/
|
|
218
|
+
static async delete(request, descricao, ignoreSSL = false) {
|
|
219
|
+
ApiActions.marcarUsoAutomatico();
|
|
220
|
+
return ApiActions._request('delete', request, descricao, ignoreSSL);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* ✅ COMPATIBILIDADE: Método especial para registrar duração
|
|
224
|
+
* @param nome Nome do teste
|
|
225
|
+
* @param duracao Duração em milissegundos
|
|
226
|
+
*/
|
|
227
|
+
static registrarDuracaoDoTeste(nome, duracao) {
|
|
228
|
+
ApiActions.duracoes.push({ nome, duracao });
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Cria um objeto FormData para requisições multipart/form-data.
|
|
232
|
+
* @param fields Campos simples do formulário (chave/valor).
|
|
233
|
+
* @param files Arquivos para upload, onde o valor pode ser um Buffer ou caminho do arquivo.
|
|
234
|
+
* @returns Instância de FormData pronta para uso em requisições.
|
|
235
|
+
*/
|
|
236
|
+
static createFormData(fields, files) {
|
|
237
|
+
// ESM compat: use createRequire to import CommonJS module synchronously
|
|
238
|
+
const require = createRequire(import.meta.url);
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
|
|
240
|
+
const FormData = require('form-data');
|
|
241
|
+
const form = new FormData();
|
|
242
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
243
|
+
form.append(key, value);
|
|
244
|
+
}
|
|
245
|
+
if (files) {
|
|
246
|
+
for (const [key, value] of Object.entries(files)) {
|
|
247
|
+
// biome-ignore lint/suspicious/noExplicitAny: FormData.append accepts Buffer|string but typings are from dynamic require
|
|
248
|
+
form.append(key, value);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return form;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Cria um contexto de requisição Playwright com certificado de cliente.
|
|
255
|
+
* @param options Opções para configuração do contexto, incluindo caminhos de certificado, headers, timeout, etc.
|
|
256
|
+
* @returns Contexto de requisição configurado para autenticação mútua ou SSL customizado.
|
|
257
|
+
*/
|
|
258
|
+
static async prepareContextWithCert(options) {
|
|
259
|
+
const clientCertificates = [];
|
|
260
|
+
if (options.certPath && options.keyPath) {
|
|
261
|
+
clientCertificates.push({
|
|
262
|
+
origin: options.origin,
|
|
263
|
+
certPath: options.certPath,
|
|
264
|
+
keyPath: options.keyPath,
|
|
265
|
+
passphrase: options.passphrase,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else if (options.pfxPath) {
|
|
269
|
+
clientCertificates.push({
|
|
270
|
+
origin: options.origin,
|
|
271
|
+
pfxPath: options.pfxPath,
|
|
272
|
+
passphrase: options.passphrase,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return playwrightRequest.newContext({
|
|
276
|
+
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? true,
|
|
277
|
+
clientCertificates,
|
|
278
|
+
extraHTTPHeaders: options.extraHTTPHeaders,
|
|
279
|
+
timeout: options.timeout,
|
|
280
|
+
userAgent: options.userAgent,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Centraliza execução de requisições HTTP, logs e métricas.
|
|
285
|
+
* @param method Método HTTP a ser executado (get, post, put, patch, delete).
|
|
286
|
+
* @param request Objeto contendo a URL e opções da requisição.
|
|
287
|
+
* @param descricao Descrição da requisição para logs e relatórios.
|
|
288
|
+
* @param ignoreSSL Se true, ignora erros de certificado SSL.
|
|
289
|
+
* @returns Resposta padronizada da API.
|
|
290
|
+
*/
|
|
291
|
+
static async _request(method, request, descricao, ignoreSSL) {
|
|
292
|
+
// ✅ Validação de URL obrigatória (boundary check)
|
|
293
|
+
if (!request.url || typeof request.url !== 'string') {
|
|
294
|
+
throw new ValidationError('AC-API-001', 'URL da requisição é obrigatória e deve ser uma string', 'A URL da requisição não foi informada ou não é uma string válida.');
|
|
295
|
+
}
|
|
296
|
+
const trimmedUrl = request.url.trim();
|
|
297
|
+
if (!/^(https?:\/\/.+|\/)/i.test(trimmedUrl)) {
|
|
298
|
+
throw new ValidationError('AC-API-001', `URL inválida: "${trimmedUrl}". A URL deve começar com http://, https:// ou / (relativa com baseURL do sistema)`, `A URL informada ("${trimmedUrl}") não é válida. Use http://, https:// ou caminho relativo iniciando com /.`);
|
|
299
|
+
}
|
|
300
|
+
ApiActions.marcarUsoAutomatico();
|
|
301
|
+
// 🆕 Registrar statement no StatementTracker
|
|
302
|
+
let statementTimestamp = null;
|
|
303
|
+
try {
|
|
304
|
+
statementTimestamp = StatementTracker.startStatement('ApiActions', `${method.toUpperCase()}: ${descricao}`);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.warn('Não foi possível registrar statement no StatementTracker:', error);
|
|
308
|
+
}
|
|
309
|
+
// 🚀 LÓGICA INTELIGENTE: Certificados sempre usam SSL bypass
|
|
310
|
+
const effectiveIgnoreSSL = ignoreSSL || !!request.certificate;
|
|
311
|
+
// 📡 LOG COM PROTEÇÃO ANTI-DUPLICAÇÃO (via StatementTracker)
|
|
312
|
+
StatementTracker.logRequest(method, descricao);
|
|
313
|
+
ApiActions.logEvent(`🔄 Executando ${method.toUpperCase()}: ${descricao}`);
|
|
314
|
+
const startTime = Date.now();
|
|
315
|
+
// 📚 CRÍTICO: Interceptar para documentação automática e StatementTracker
|
|
316
|
+
let docId = null;
|
|
317
|
+
try {
|
|
318
|
+
// 🔄 ExecutionTracker - Interceptação CRÍTICA para persistência (CORRIGIDO ESM)
|
|
319
|
+
try {
|
|
320
|
+
if (ExecutionTracker) {
|
|
321
|
+
const apiCallData = {
|
|
322
|
+
id: `api_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
323
|
+
method: method.toUpperCase(),
|
|
324
|
+
url: request.url,
|
|
325
|
+
headers: request.options?.headers || {},
|
|
326
|
+
payload: request.options?.data || {},
|
|
327
|
+
timestamp: new Date().toISOString(),
|
|
328
|
+
testName: descricao || 'current-test',
|
|
329
|
+
environment: process.env.NODE_ENV || 'test',
|
|
330
|
+
success: true, // Will be updated later
|
|
331
|
+
statusCode: 0, // Will be updated later
|
|
332
|
+
duration: 0, // Will be updated later
|
|
333
|
+
response: null, // Will be updated later
|
|
334
|
+
error: null,
|
|
335
|
+
};
|
|
336
|
+
ExecutionTracker.trackAPI(apiCallData);
|
|
337
|
+
docId = apiCallData.id;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (trackerError) {
|
|
341
|
+
console.log(`📚 ExecutionTracker import falhou: ${String(trackerError)}`);
|
|
342
|
+
}
|
|
343
|
+
// ✅ REMOVIDO: AutoDocs interceptação duplicada - usando apenas ExecutionTracker
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
// Log de debug apenas se debug ativo
|
|
347
|
+
docId = null;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
// ✅ CORRIGIDO: Obter baseURL ANTES de criar o contexto
|
|
351
|
+
let baseURL;
|
|
352
|
+
const isAbsoluteUrl = request.url.startsWith('http://') || request.url.startsWith('https://');
|
|
353
|
+
// ✅ SEMPRE tentar obter baseURL do AutoCore Hub ANTES de criar contexto
|
|
354
|
+
if (!isAbsoluteUrl) {
|
|
355
|
+
try {
|
|
356
|
+
const configManager = DynamicConfigManager.getInstance();
|
|
357
|
+
const maxAttempts = 10;
|
|
358
|
+
const waitIntervalMs = 60;
|
|
359
|
+
let hasSystemConfigured = null;
|
|
360
|
+
let apiConfig = configManager.getApiConfig();
|
|
361
|
+
const waitForSystemConfig = async () => {
|
|
362
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
363
|
+
try {
|
|
364
|
+
hasSystemConfigured = StatementTracker.getCurrentCTSystem();
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
hasSystemConfigured = null;
|
|
368
|
+
}
|
|
369
|
+
apiConfig = configManager.getApiConfig();
|
|
370
|
+
if (apiConfig?.baseUrl && hasSystemConfigured) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, waitIntervalMs * (attempt + 1)));
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
// ✅ Esperar sistema ser configurado quando ainda não houver base ou CT estiver desconhecido
|
|
377
|
+
if (!apiConfig?.baseUrl) {
|
|
378
|
+
console.log('⏳ [AutoCore Hub] Aguardando configuração do sistema (baseUrl indisponível)...');
|
|
379
|
+
await waitForSystemConfig();
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
try {
|
|
383
|
+
hasSystemConfigured = StatementTracker.getCurrentCTSystem();
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
hasSystemConfigured = null;
|
|
387
|
+
}
|
|
388
|
+
if (!hasSystemConfigured) {
|
|
389
|
+
await waitForSystemConfig();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (apiConfig?.baseUrl) {
|
|
393
|
+
// ✅ CORRIGIDO: Remover barra final se existir (Playwright adiciona automaticamente)
|
|
394
|
+
baseURL = apiConfig.baseUrl.replace(/\/$/, '');
|
|
395
|
+
const resolvedSystem = hasSystemConfigured || 'desconhecido';
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log('⚠️ [AutoCore Hub] Nenhuma configuração de sistema encontrada - usando URL absoluta ou chamada sem setSystem()');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.log(`⚠️ [AutoCore Hub] Erro ao obter baseURL: ${error}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const context = await ApiActions.prepareContext(effectiveIgnoreSSL, request.certificate, request.url, baseURL);
|
|
406
|
+
// Validar que URL relativa tenha baseURL resolvido
|
|
407
|
+
if (!isAbsoluteUrl && !baseURL) {
|
|
408
|
+
throw new ValidationError('AC-API-002', `URL relativa "${request.url}" requer baseURL. Verifique se o sistema foi configurado via setSystem() e possui URL cadastrada.`, 'Use TestAnnotations.setSystem() antes de chamar ApiActions ou forneça uma URL absoluta (http/https).');
|
|
409
|
+
}
|
|
410
|
+
// ✅ LOGS DETALHADOS PARA DEBUG
|
|
411
|
+
console.log('\n-------------------REQUEST-------------------\n');
|
|
412
|
+
// 🎯 Mostrar URL completa no log (baseURL + endpoint apenas se não for absoluta)
|
|
413
|
+
const fullUrl = baseURL && !isAbsoluteUrl
|
|
414
|
+
? `${baseURL}${request.url}`
|
|
415
|
+
: request.url;
|
|
416
|
+
Logger.info(`ENDPOINT: ${fullUrl}`);
|
|
417
|
+
Logger.info(`METHOD: ${method.toUpperCase()}`);
|
|
418
|
+
Logger.info(`IGNORE_SSL: ${ignoreSSL}`);
|
|
419
|
+
Logger.info(`CONTEXT: ${context ? 'OK' : 'NULL'}`);
|
|
420
|
+
Logger.info(`CERTIFICATE: ${request.certificate ? 'YES' : 'NO'}`);
|
|
421
|
+
if (request.options?.headers) {
|
|
422
|
+
const maskedHeaders = Object.entries(request.options.headers)
|
|
423
|
+
.map(([key, value]) => `${key} : ${ApiActions.shouldMaskHeader(key) ? '***' : value}`)
|
|
424
|
+
.join('][');
|
|
425
|
+
Logger.info(`HEADERS: [${maskedHeaders}]`);
|
|
426
|
+
}
|
|
427
|
+
if (request.options?.data !== undefined) {
|
|
428
|
+
const bodyString = typeof request.options.data === 'string'
|
|
429
|
+
? request.options.data
|
|
430
|
+
: JSON.stringify(request.options.data);
|
|
431
|
+
const formattedData = ApiActions.formatAndMaskBody(bodyString);
|
|
432
|
+
Logger.info(`BODY:\n${formattedData}`);
|
|
433
|
+
}
|
|
434
|
+
if (request.options?.form !== undefined) {
|
|
435
|
+
Logger.info(`FORM (x-www-form-urlencoded): ${JSON.stringify(request.options.form)}`);
|
|
436
|
+
}
|
|
437
|
+
// ✅ DIAGNÓSTICO: Validar URL (apenas para URLs absolutas)
|
|
438
|
+
if (request.url.startsWith('http://') ||
|
|
439
|
+
request.url.startsWith('https://')) {
|
|
440
|
+
try {
|
|
441
|
+
new URL(request.url); // Validação - lança erro se inválida
|
|
442
|
+
}
|
|
443
|
+
catch (urlError) {
|
|
444
|
+
console.error(`❌ URL inválida: ${request.url}`);
|
|
445
|
+
throw new Error(`URL inválida: ${request.url}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else if (baseURL) {
|
|
449
|
+
// URL relativa com baseURL - validar a combinação
|
|
450
|
+
try {
|
|
451
|
+
// ✅ CORRIGIDO: Playwright concatena baseURL + endpoint (mantém / inicial do endpoint)
|
|
452
|
+
const fullUrlForValidation = `${baseURL}${request.url}`;
|
|
453
|
+
new URL(fullUrlForValidation); // Validação - lança erro se inválida
|
|
454
|
+
}
|
|
455
|
+
catch (urlError) {
|
|
456
|
+
const fullUrl = `${baseURL}${request.url}`;
|
|
457
|
+
console.error(`❌ URL inválida após combinar com baseURL: ${fullUrl}`);
|
|
458
|
+
throw new Error(`URL inválida: ${fullUrl}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// 🎯 Adicionar params (query parameters) à URL se existirem
|
|
462
|
+
let finalUrl = request.url;
|
|
463
|
+
if (request.options?.params) {
|
|
464
|
+
const queryParams = new URLSearchParams();
|
|
465
|
+
for (const [key, value] of Object.entries(request.options.params)) {
|
|
466
|
+
queryParams.append(key, String(value));
|
|
467
|
+
}
|
|
468
|
+
const separator = finalUrl.includes('?') ? '&' : '?';
|
|
469
|
+
finalUrl = `${finalUrl}${separator}${queryParams.toString()}`;
|
|
470
|
+
console.log(`🎯 [AutoCore Hub] Query params adicionados: ${queryParams.toString()}`);
|
|
471
|
+
}
|
|
472
|
+
// ✅ NOVO: Processar form (x-www-form-urlencoded) se existir
|
|
473
|
+
const { form: _formField, ...finalOptions } = { ...request.options };
|
|
474
|
+
if (request.options?.form) {
|
|
475
|
+
const formData = new URLSearchParams();
|
|
476
|
+
for (const [key, value] of Object.entries(request.options.form)) {
|
|
477
|
+
formData.append(key, String(value));
|
|
478
|
+
}
|
|
479
|
+
finalOptions.data = formData.toString();
|
|
480
|
+
finalOptions.headers = {
|
|
481
|
+
...finalOptions.headers,
|
|
482
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
483
|
+
};
|
|
484
|
+
Logger.info(`📝 [AutoCore Hub] Form convertido para x-www-form-urlencoded: ${formData.toString()}`);
|
|
485
|
+
}
|
|
486
|
+
// Executa requisição
|
|
487
|
+
const response = await context[method](finalUrl, finalOptions);
|
|
488
|
+
// ✅ VALIDAÇÃO CRÍTICA: Verificar se response é válido
|
|
489
|
+
if (!response) {
|
|
490
|
+
Logger.error(`❌ Response é null/undefined para ${method.toUpperCase()} ${finalUrl}`);
|
|
491
|
+
throw new Error(`Response inválido recebido para ${method.toUpperCase()} ${finalUrl}`);
|
|
492
|
+
}
|
|
493
|
+
// ✅ LOG APÓS RECEBER RESPONSE
|
|
494
|
+
Logger.success(`Response recebida - Status: ${response?.status() || 'UNDEFINED'}`);
|
|
495
|
+
Logger.success(`Response type: ${typeof response}`);
|
|
496
|
+
const endTime = Date.now();
|
|
497
|
+
const duration = endTime - startTime;
|
|
498
|
+
// ✅ VALIDAÇÃO CRÍTICA: Verificar se métodos existem
|
|
499
|
+
let responseBody;
|
|
500
|
+
let statusCode;
|
|
501
|
+
try {
|
|
502
|
+
statusCode = response.status();
|
|
503
|
+
if (typeof statusCode !== 'number') {
|
|
504
|
+
Logger.error(`❌ Status code inválido: ${statusCode}`);
|
|
505
|
+
statusCode = 500;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
Logger.error(`❌ Erro ao obter status code: ${error}`);
|
|
510
|
+
statusCode = 500;
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
responseBody = await response.text();
|
|
514
|
+
if (typeof responseBody !== 'string') {
|
|
515
|
+
Logger.error(`❌ Response body inválido: ${typeof responseBody}`);
|
|
516
|
+
responseBody = JSON.stringify({ error: 'Invalid response body' });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
Logger.error(`❌ Erro ao obter response body: ${error}`);
|
|
521
|
+
responseBody = JSON.stringify({
|
|
522
|
+
error: 'Failed to read response body',
|
|
523
|
+
details: error instanceof Error ? error.message : String(error),
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// 📚 CRÍTICO: Atualizar documentação automática com resposta
|
|
527
|
+
try {
|
|
528
|
+
// 🔄 ExecutionTracker - Sistema único de atualização (evita duplicação)
|
|
529
|
+
if (docId) {
|
|
530
|
+
if (ExecutionTracker) {
|
|
531
|
+
let responseData = null;
|
|
532
|
+
try {
|
|
533
|
+
responseData = responseBody
|
|
534
|
+
? JSON.parse(responseBody)
|
|
535
|
+
: responseBody;
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
responseData = responseBody; // Se não for JSON válido, usar como string
|
|
539
|
+
}
|
|
540
|
+
ExecutionTracker.updateAPIResult(docId, responseData, statusCode);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// ✅ REMOVIDO: AutoDocs update duplicado - usando apenas ExecutionTracker
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
// Log de debug apenas se debug ativo
|
|
547
|
+
if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
|
|
548
|
+
console.log(`📚 AutoDocs update falhou: ${String(error)}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// Log da resposta
|
|
552
|
+
console.log('\n-------------------RESPONSE-------------------\n');
|
|
553
|
+
Logger.info(`STATUS CODE: ${statusCode}`);
|
|
554
|
+
const responseHeaders = response.headers();
|
|
555
|
+
const maskedResponseHeaders = Object.entries(responseHeaders)
|
|
556
|
+
.map(([key, value]) => `${key} : ${ApiActions.shouldMaskHeader(key) ? '***' : value}`)
|
|
557
|
+
.join('][');
|
|
558
|
+
Logger.info(`HEADERS: [${maskedResponseHeaders}]`);
|
|
559
|
+
const formattedBody = ApiActions.formatAndMaskBody(responseBody);
|
|
560
|
+
Logger.info(`BODY:\n${formattedBody}`);
|
|
561
|
+
ApiActions.requestCount++;
|
|
562
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
563
|
+
ApiActions.successCount++;
|
|
564
|
+
ApiActions.logTestResult(descricao, true, duration);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
ApiActions.failureCount++;
|
|
568
|
+
ApiActions.logTestResult(descricao, false, duration);
|
|
569
|
+
}
|
|
570
|
+
const apiResponse = ApiActions.createApiResponse(statusCode, responseHeaders, responseBody, duration);
|
|
571
|
+
// 📝 NOVO: Registrar ação automaticamente no Statement ativo COM LOGS COMPLETOS
|
|
572
|
+
try {
|
|
573
|
+
// 🆕 Preparar request headers mascarados
|
|
574
|
+
const maskedReqHeaders = {};
|
|
575
|
+
if (request.options?.headers) {
|
|
576
|
+
for (const [key, value] of Object.entries(request.options.headers)) {
|
|
577
|
+
maskedReqHeaders[key] = ApiActions.shouldMaskHeader(key) ? '***' : String(value);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// 🆕 Preparar request body
|
|
581
|
+
const reqBody = request.options?.data !== undefined
|
|
582
|
+
? (typeof request.options.data === 'string'
|
|
583
|
+
? request.options.data
|
|
584
|
+
: JSON.stringify(request.options.data, null, 2))
|
|
585
|
+
: request.options?.form !== undefined
|
|
586
|
+
? JSON.stringify(request.options.form)
|
|
587
|
+
: undefined;
|
|
588
|
+
// 🆕 Preparar response headers mascarados
|
|
589
|
+
const maskedRespHeaders = {};
|
|
590
|
+
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
591
|
+
maskedRespHeaders[key] = ApiActions.shouldMaskHeader(key) ? '***' : String(value);
|
|
592
|
+
}
|
|
593
|
+
StatementTracker.recordAction('API', `${method.toUpperCase()} ${request.url}`, statusCode >= 200 && statusCode < 300, duration, {
|
|
594
|
+
method: method.toUpperCase(),
|
|
595
|
+
url: request.url,
|
|
596
|
+
statusCode,
|
|
597
|
+
// 🆕 Logs completos de API
|
|
598
|
+
requestHeaders: maskedReqHeaders,
|
|
599
|
+
requestBody: ApiActions.formatAndMaskBody(reqBody || ''),
|
|
600
|
+
responseHeaders: maskedRespHeaders,
|
|
601
|
+
responseBody: ApiActions.formatAndMaskBody(responseBody),
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
// Falha silenciosa - não afetar funcionalidade principal
|
|
606
|
+
}
|
|
607
|
+
// 🆕 Finalizar statement com sucesso
|
|
608
|
+
if (statementTimestamp) {
|
|
609
|
+
try {
|
|
610
|
+
const success = statusCode >= 200 && statusCode < 300;
|
|
611
|
+
const statusMessage = success
|
|
612
|
+
? `✅ ${method.toUpperCase()} ${descricao}: ${statusCode} (${duration}ms)`
|
|
613
|
+
: `⚠️ ${method.toUpperCase()} ${descricao}: ${statusCode} (${duration}ms)`;
|
|
614
|
+
StatementTracker.finishStatement(statementTimestamp, success, statusMessage);
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
console.warn('Não foi possível finalizar statement no StatementTracker:', error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return apiResponse;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
const endTime = Date.now();
|
|
624
|
+
const duration = endTime - startTime;
|
|
625
|
+
// 🔐 FALLBACK PROGRESSIVO: Se erro de certificado e existe certificado, tentar outras estratégias
|
|
626
|
+
if (error instanceof Error &&
|
|
627
|
+
request.certificate &&
|
|
628
|
+
(error.message.includes('mac verify failure') ||
|
|
629
|
+
error.message.includes('certificate verify failed') ||
|
|
630
|
+
error.message.includes('ssl routines') ||
|
|
631
|
+
error.message.includes('SSL') ||
|
|
632
|
+
error.message.includes('TLS'))) {
|
|
633
|
+
console.warn(`🔐 SSL ERROR DETECTADO: ${error.message}`);
|
|
634
|
+
console.warn('🚀 FALLBACK PROGRESSIVO: Tentando outras estratégias de certificado...');
|
|
635
|
+
try {
|
|
636
|
+
// ✅ NOVO: Tentar próximas estratégias ordenadas por eficácia (SEM PROXY primeiro)
|
|
637
|
+
const fallbackStrategies = [
|
|
638
|
+
{
|
|
639
|
+
name: 'SEM PROXY',
|
|
640
|
+
useProxy: false,
|
|
641
|
+
ignoreSSL: true,
|
|
642
|
+
certificate: true,
|
|
643
|
+
specialHeaders: false,
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: 'COMPLETO',
|
|
647
|
+
useProxy: true,
|
|
648
|
+
ignoreSSL: true,
|
|
649
|
+
certificate: true,
|
|
650
|
+
specialHeaders: false,
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
name: 'SEM HEADERS',
|
|
654
|
+
useProxy: true,
|
|
655
|
+
ignoreSSL: true,
|
|
656
|
+
certificate: true,
|
|
657
|
+
specialHeaders: false,
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: 'SSL ONLY',
|
|
661
|
+
useProxy: false,
|
|
662
|
+
ignoreSSL: true,
|
|
663
|
+
certificate: false,
|
|
664
|
+
specialHeaders: false,
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: 'FALLBACK',
|
|
668
|
+
useProxy: false,
|
|
669
|
+
ignoreSSL: true,
|
|
670
|
+
certificate: false,
|
|
671
|
+
specialHeaders: false,
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: 'PADRÃO',
|
|
675
|
+
useProxy: false,
|
|
676
|
+
ignoreSSL: false,
|
|
677
|
+
certificate: false,
|
|
678
|
+
specialHeaders: false,
|
|
679
|
+
},
|
|
680
|
+
];
|
|
681
|
+
let fallbackSuccess = false;
|
|
682
|
+
for (const strategy of fallbackStrategies) {
|
|
683
|
+
console.log(`🔄 FALLBACK: Tentando estratégia ${strategy.name}...`);
|
|
684
|
+
try {
|
|
685
|
+
const fallbackContext = await ApiActions.tryProgressiveStrategy(strategy, request.certificate, request.url, 'http://proxy.redecorp.br:8080');
|
|
686
|
+
if (fallbackContext) {
|
|
687
|
+
console.log(`🔄 Executando requisição com estratégia ${strategy.name}...`);
|
|
688
|
+
const fallbackResponse = await fallbackContext[method](request.url, request.options);
|
|
689
|
+
if (!fallbackResponse) {
|
|
690
|
+
throw new Error(`Response inválido recebido para ${method.toUpperCase()} ${request.url}`);
|
|
691
|
+
}
|
|
692
|
+
const fallbackStatusCode = fallbackResponse.status();
|
|
693
|
+
const fallbackHeaders = await fallbackResponse.headers();
|
|
694
|
+
const fallbackBody = await fallbackResponse.text();
|
|
695
|
+
const fallbackEndTime = Date.now();
|
|
696
|
+
const fallbackDuration = fallbackEndTime - startTime;
|
|
697
|
+
console.log(`✅ FALLBACK FUNCIONOU! Estratégia ${strategy.name} - Status: ${fallbackStatusCode} em ${fallbackDuration}ms`);
|
|
698
|
+
fallbackSuccess = true;
|
|
699
|
+
return ApiActions.createApiResponse(fallbackStatusCode, fallbackHeaders, fallbackBody, fallbackDuration);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch (strategyError) {
|
|
703
|
+
console.log(`❌ Estratégia ${strategy.name} falhou: ${strategyError instanceof Error ? strategyError.message : String(strategyError)}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!fallbackSuccess) {
|
|
707
|
+
throw new Error('Todas as estratégias de fallback falharam');
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch (fallbackError) {
|
|
711
|
+
console.error(`❌ FALLBACK PROGRESSIVO também falhou: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
712
|
+
// Continuar com erro original
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// ✅ DIAGNÓSTICO DETALHADO: Log completo do erro
|
|
716
|
+
console.error('\n-------------------ERROR DETAILS-------------------');
|
|
717
|
+
console.error(`❌ Erro durante ${method.toUpperCase()} para ${request.url}`);
|
|
718
|
+
console.error(`❌ Tipo do erro: ${error instanceof Error ? error.constructor.name : typeof error}`);
|
|
719
|
+
console.error(`❌ Mensagem: ${error instanceof Error ? error.message : String(error)}`);
|
|
720
|
+
console.error(`❌ Stack: ${error instanceof Error ? error.stack : 'N/A'}`);
|
|
721
|
+
console.error(`❌ Duração até falha: ${duration}ms`);
|
|
722
|
+
console.error(`❌ Certificado usado: ${request.certificate ? 'SIM' : 'NÃO'}`);
|
|
723
|
+
console.error(`❌ SSL bypass aplicado: ${effectiveIgnoreSSL}`);
|
|
724
|
+
console.error('---------------------------------------------------\n');
|
|
725
|
+
// 📚 CRÍTICO: Atualizar documentação automática com erro
|
|
726
|
+
try {
|
|
727
|
+
// 🔄 ExecutionTracker - Sistema único de atualização (evita duplicação)
|
|
728
|
+
if (docId) {
|
|
729
|
+
if (ExecutionTracker) {
|
|
730
|
+
ExecutionTracker.updateAPIResult(docId, {
|
|
731
|
+
error: true,
|
|
732
|
+
message: error instanceof Error ? error.message : String(error),
|
|
733
|
+
}, 500, error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
catch (updateError) {
|
|
738
|
+
// Log de debug apenas se debug ativo
|
|
739
|
+
if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
|
|
740
|
+
console.log(`📚 AutoDocs error update falhou: ${String(updateError)}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
ApiActions.failureCount++;
|
|
744
|
+
ApiActions.logTestResult(descricao, false, duration);
|
|
745
|
+
// 🎯 NOVO: Criar erro especializado com base no tipo da falha
|
|
746
|
+
let apiError;
|
|
747
|
+
if (error instanceof Error) {
|
|
748
|
+
// Verificar tipo específico do erro
|
|
749
|
+
if (error.message.includes('net::ERR_') ||
|
|
750
|
+
error.message.includes('ECONNREFUSED') ||
|
|
751
|
+
error.message.includes('ETIMEDOUT')) {
|
|
752
|
+
// Erro de rede
|
|
753
|
+
apiError = IntegrationError.networkError(request.url, error, {
|
|
754
|
+
metadata: {
|
|
755
|
+
method: method.toUpperCase(),
|
|
756
|
+
description: descricao,
|
|
757
|
+
duration,
|
|
758
|
+
ignoreSSL,
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
else if (error.message.includes('timeout')) {
|
|
763
|
+
// Erro de timeout
|
|
764
|
+
apiError = IntegrationError.timeoutError(`${method.toUpperCase()} ${request.url}`, 30_000, {
|
|
765
|
+
metadata: {
|
|
766
|
+
method: method.toUpperCase(),
|
|
767
|
+
description: descricao,
|
|
768
|
+
duration,
|
|
769
|
+
url: request.url,
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
else if (error.message.includes('Invalid URL') ||
|
|
774
|
+
error.message.includes('Protocol')) {
|
|
775
|
+
// Erro de URL inválida
|
|
776
|
+
apiError = ValidationError.invalidUrl(request.url, error.message, {
|
|
777
|
+
metadata: {
|
|
778
|
+
method: method.toUpperCase(),
|
|
779
|
+
description: descricao,
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// Erro genérico de API
|
|
785
|
+
apiError = IntegrationError.apiRequestFailed(request.url, 0, error.message, {
|
|
786
|
+
metadata: {
|
|
787
|
+
method: method.toUpperCase(),
|
|
788
|
+
description: descricao,
|
|
789
|
+
duration,
|
|
790
|
+
originalError: error.message,
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// Erro desconhecido
|
|
797
|
+
apiError = IntegrationError.apiRequestFailed(request.url, 0, String(error), {
|
|
798
|
+
metadata: {
|
|
799
|
+
method: method.toUpperCase(),
|
|
800
|
+
description: descricao,
|
|
801
|
+
duration,
|
|
802
|
+
originalError: String(error),
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
// 📝 Log do erro especializado
|
|
807
|
+
Logger.error(`❌ ${apiError.getUserFriendlyMessage()}`);
|
|
808
|
+
ApiActions.logEvent(`❌ ${apiError.userMessage}`);
|
|
809
|
+
// ✅ CRÍTICO: SEMPRE retornar ApiResponse válida com informações úteis do erro
|
|
810
|
+
const errorResponse = ApiActions.createApiResponse(500, {}, JSON.stringify({
|
|
811
|
+
error: true,
|
|
812
|
+
code: apiError.code,
|
|
813
|
+
message: apiError.userMessage,
|
|
814
|
+
type: 'API_REQUEST_FAILED',
|
|
815
|
+
details: apiError.message,
|
|
816
|
+
suggestions: apiError.suggestions,
|
|
817
|
+
errorId: apiError.id,
|
|
818
|
+
timestamp: apiError.timestamp,
|
|
819
|
+
recoverable: apiError.recoverable,
|
|
820
|
+
}), duration);
|
|
821
|
+
// 📝 NOVO: Registrar ação com falha no Statement ativo
|
|
822
|
+
try {
|
|
823
|
+
StatementTracker.recordAction('API', `${method.toUpperCase()} ${request.url}`, false, duration, {
|
|
824
|
+
method: method.toUpperCase(),
|
|
825
|
+
url: request.url,
|
|
826
|
+
statusCode: 500,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
// Falha silenciosa - não afetar funcionalidade principal
|
|
831
|
+
}
|
|
832
|
+
// 🆕 Finalizar statement com erro
|
|
833
|
+
if (statementTimestamp) {
|
|
834
|
+
try {
|
|
835
|
+
StatementTracker.finishStatement(statementTimestamp, false, `❌ ${method.toUpperCase()} ${descricao}: ${apiError.userMessage}`);
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
console.warn('Não foi possível finalizar statement no StatementTracker:', error);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
Logger.info(`🔧 Retornando ApiResponse com erro estruturado - ID: ${apiError.id}`);
|
|
842
|
+
return errorResponse;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
static logTestResult(descricao, success, duration) {
|
|
846
|
+
const timestamp = new Date().toLocaleTimeString('pt-BR', {
|
|
847
|
+
hour12: false,
|
|
848
|
+
hour: '2-digit',
|
|
849
|
+
minute: '2-digit',
|
|
850
|
+
second: '2-digit',
|
|
851
|
+
fractionalSecondDigits: 3,
|
|
852
|
+
});
|
|
853
|
+
const status = success ? 'PASSED' : 'FAILED';
|
|
854
|
+
const statusIcon = success ? '✅' : '❌';
|
|
855
|
+
Logger.info(`[ STEP ] ${descricao} [ ${status} ] `);
|
|
856
|
+
console.log('');
|
|
857
|
+
const logMessage = `${statusIcon} ${descricao} - ${status} (${duration}ms)`;
|
|
858
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
859
|
+
const testId = testInfo?.title || 'global';
|
|
860
|
+
if (!ApiActions.logsPerTest[testId])
|
|
861
|
+
ApiActions.logsPerTest[testId] = [];
|
|
862
|
+
ApiActions.logsPerTest[testId].push(logMessage);
|
|
863
|
+
if (testInfo?.title) {
|
|
864
|
+
try {
|
|
865
|
+
UnifiedReportManager.adicionarLog(testInfo.title, logMessage);
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
// Silenciosamente ignorar
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Valida se dois valores são iguais.
|
|
874
|
+
* Registra sucesso ou falha nos logs e incrementa métricas.
|
|
875
|
+
* Lança erro se os valores não forem iguais.
|
|
876
|
+
* @param actual Valor atual obtido.
|
|
877
|
+
* @param expected Valor esperado.
|
|
878
|
+
* @param message Mensagem descritiva da validação.
|
|
879
|
+
* @throws {ValidationError} Se os valores não forem iguais.
|
|
880
|
+
*/
|
|
881
|
+
static validateEquals(actual, expected, message) {
|
|
882
|
+
ApiActions.marcarUsoAutomatico();
|
|
883
|
+
const timestamp = new Date().toLocaleTimeString('pt-BR', {
|
|
884
|
+
hour12: false,
|
|
885
|
+
hour: '2-digit',
|
|
886
|
+
minute: '2-digit',
|
|
887
|
+
second: '2-digit',
|
|
888
|
+
fractionalSecondDigits: 3,
|
|
889
|
+
});
|
|
890
|
+
if (actual === expected) {
|
|
891
|
+
ApiActions.successCount++;
|
|
892
|
+
const successMessage = `✅ Validação passou: ${message} (${actual} === ${expected})`;
|
|
893
|
+
ApiActions.logEvent(successMessage);
|
|
894
|
+
Logger.success(`Validação passou: ${message}`);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
ApiActions.failureCount++;
|
|
898
|
+
console.log(`${timestamp}`);
|
|
899
|
+
console.log(`[ STEP ] ${message} [ FAILED ] `);
|
|
900
|
+
const errorMessage = `❌ Validação falhou: ${message} (${actual} !== ${expected})`;
|
|
901
|
+
ApiActions.logEvent(errorMessage);
|
|
902
|
+
Logger.error(`Validação falhou: ${message} - Esperado: ${expected}, Atual: ${actual}`);
|
|
903
|
+
throw new ValidationError('API_VALIDATION_FAILED', `Validation failed: ${message} - Expected: ${expected}, Actual: ${actual}`, `Validação falhou: ${message} - Esperado: ${expected}, Atual: ${actual}`, undefined, { metadata: { expected, actual, context: message } });
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Valida se um valor não é nulo ou undefined.
|
|
908
|
+
* Registra sucesso ou falha nos logs e incrementa métricas.
|
|
909
|
+
* Lança erro se o valor for nulo ou undefined.
|
|
910
|
+
* @param value Valor a ser validado.
|
|
911
|
+
* @param message Mensagem descritiva da validação.
|
|
912
|
+
* @throws {ValidationError} Se o valor for nulo ou undefined.
|
|
913
|
+
*/
|
|
914
|
+
static validateNotNull(value, message) {
|
|
915
|
+
ApiActions.marcarUsoAutomatico();
|
|
916
|
+
if (value != null && value !== undefined) {
|
|
917
|
+
ApiActions.successCount++;
|
|
918
|
+
const successMessage = `✅ Validação passou: ${message} (valor não é nulo)`;
|
|
919
|
+
ApiActions.logEvent(successMessage);
|
|
920
|
+
Logger.success(`Validação passou: ${message}`);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
ApiActions.failureCount++;
|
|
924
|
+
const errorMessage = `❌ Validação falhou: ${message} (valor é nulo/undefined)`;
|
|
925
|
+
ApiActions.logEvent(errorMessage);
|
|
926
|
+
Logger.error(`❌ Validação falhou: ${message} - Valor é nulo ou undefined`);
|
|
927
|
+
throw new ValidationError('API_NULL_VALUE', `Validation failed: ${message} - Value is null or undefined`, `Validação falhou: ${message} - Valor é nulo ou undefined`, undefined, { metadata: { value, context: message } });
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
static maskSensitiveDataInBody(body) {
|
|
931
|
+
if (!body)
|
|
932
|
+
return body;
|
|
933
|
+
try {
|
|
934
|
+
const maskedBody = body
|
|
935
|
+
.replace(/"access_token":\s*"[^"]+"/g, '"access_token":"***"')
|
|
936
|
+
.replace(/"password":\s*"[^"]+"/g, '"password":"***"')
|
|
937
|
+
.replace(/password=[^&\s]+/g, 'password=***')
|
|
938
|
+
.replace(/token=[^&\s]+/g, 'token=***');
|
|
939
|
+
return maskedBody;
|
|
940
|
+
}
|
|
941
|
+
catch {
|
|
942
|
+
return body;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* 🎨 Formata e mascara corpo JSON para exibição legível
|
|
947
|
+
* @param body String contendo JSON ou texto
|
|
948
|
+
* @returns JSON formatado com indentação ou texto original
|
|
949
|
+
*/
|
|
950
|
+
static formatAndMaskBody(body) {
|
|
951
|
+
if (!body)
|
|
952
|
+
return body;
|
|
953
|
+
try {
|
|
954
|
+
// Tenta fazer parse como JSON
|
|
955
|
+
const parsed = JSON.parse(body);
|
|
956
|
+
// Converte para JSON formatado com indentação
|
|
957
|
+
let formatted = JSON.stringify(parsed, null, 2);
|
|
958
|
+
// Aplica mascaramento de dados sensíveis
|
|
959
|
+
formatted = formatted
|
|
960
|
+
.replace(/"access_token":\s*"[^"]+"/g, '"access_token": "***"')
|
|
961
|
+
.replace(/"password":\s*"[^"]+"/g, '"password": "***"')
|
|
962
|
+
.replace(/"token":\s*"[^"]+"/g, '"token": "***"')
|
|
963
|
+
.replace(/"senha":\s*"[^"]+"/g, '"senha": "***"');
|
|
964
|
+
return formatted;
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
// Se não for JSON válido, aplica mascaramento básico e retorna
|
|
968
|
+
return ApiActions.maskSensitiveDataInBody(body);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* 🔧 Normaliza URL removendo barras duplicadas na junção baseURL + endpoint
|
|
973
|
+
* @param baseURL URL base (pode terminar com /)
|
|
974
|
+
* @param endpoint Caminho do endpoint (pode começar com /)
|
|
975
|
+
* @returns URL completa sem barras duplicadas
|
|
976
|
+
*/
|
|
977
|
+
static normalizeUrl(baseURL, endpoint) {
|
|
978
|
+
const cleanBase = baseURL.replace(/\/+$/, ''); // Remove trailing slashes
|
|
979
|
+
const cleanPath = endpoint.replace(/^\/+/, ''); // Remove leading slashes
|
|
980
|
+
return `${cleanBase}/${cleanPath}`;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* ✅ NOVO: Obtém o timeout do projeto Playwright ou usa padrão
|
|
984
|
+
*/
|
|
985
|
+
static tryParseJson(text) {
|
|
986
|
+
try {
|
|
987
|
+
return JSON.parse(text);
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
return text;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* ✅ NOVO: Método utilitário para resolver caminho do certificado
|
|
995
|
+
*/
|
|
996
|
+
static resolveCertPath(certName) {
|
|
997
|
+
// Se já tem path completo, usar direto
|
|
998
|
+
if (certName.startsWith('/') || certName.includes(':')) {
|
|
999
|
+
return certName;
|
|
1000
|
+
}
|
|
1001
|
+
// ✅ CORRIGIDO: Usar createRequire ao invés de require direto
|
|
1002
|
+
const require = createRequire(import.meta.url);
|
|
1003
|
+
const path = require('path');
|
|
1004
|
+
return path.resolve(process.cwd(), certName);
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* ✅ NOVO: Executa uma tentativa específica da estratégia progressiva
|
|
1008
|
+
*/
|
|
1009
|
+
static async tryProgressiveStrategy(strategy, certificate, requestUrl, proxyUrl) {
|
|
1010
|
+
const origin = requestUrl ? new URL(requestUrl).origin : 'https://localhost';
|
|
1011
|
+
// ✅ CORRIGIDO: Só obter baseURL do AutoCore Hub se o CT atual tem sistema configurado E a URL for relativa
|
|
1012
|
+
let baseURL;
|
|
1013
|
+
const isAbsoluteUrl = requestUrl &&
|
|
1014
|
+
(requestUrl.startsWith('http://') || requestUrl.startsWith('https://'));
|
|
1015
|
+
// ✅ NOVO: Obter sistema do CT atual via StatementTracker
|
|
1016
|
+
let hasSystemConfigured = null;
|
|
1017
|
+
try {
|
|
1018
|
+
hasSystemConfigured = StatementTracker.getCurrentCTSystem();
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
// Ignora se não conseguir importar
|
|
1022
|
+
}
|
|
1023
|
+
if (!isAbsoluteUrl && hasSystemConfigured) {
|
|
1024
|
+
try {
|
|
1025
|
+
const configManager = DynamicConfigManager.getInstance();
|
|
1026
|
+
const apiConfig = configManager.getApiConfig();
|
|
1027
|
+
if (apiConfig && apiConfig.baseUrl) {
|
|
1028
|
+
// ✅ CORRIGIDO: Remover barra final se existir (Playwright adiciona automaticamente)
|
|
1029
|
+
baseURL = apiConfig.baseUrl.replace(/\/$/, '');
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
catch {
|
|
1033
|
+
// Ignora se não conseguir importar
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
// Configurar opções base
|
|
1037
|
+
const contextOptions = {
|
|
1038
|
+
baseURL, // ✅ NOVO: baseURL do sistema selecionado
|
|
1039
|
+
ignoreHTTPSErrors: strategy.ignoreSSL,
|
|
1040
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1041
|
+
extraHTTPHeaders: strategy.specialHeaders
|
|
1042
|
+
? {
|
|
1043
|
+
'User-Agent': 'AutoCore-Progressive-Client/1.0',
|
|
1044
|
+
Accept: 'application/json, text/plain, */*',
|
|
1045
|
+
'Cache-Control': 'no-cache',
|
|
1046
|
+
'X-AutoCore-Strategy': strategy.name,
|
|
1047
|
+
'X-Request-ID': `autocore-${Date.now()}`,
|
|
1048
|
+
}
|
|
1049
|
+
: {
|
|
1050
|
+
'User-Agent': 'AutoCore-Client/1.0',
|
|
1051
|
+
Accept: 'application/json, text/plain, */*',
|
|
1052
|
+
},
|
|
1053
|
+
};
|
|
1054
|
+
// Adicionar proxy se solicitado
|
|
1055
|
+
if (strategy.useProxy && proxyUrl) {
|
|
1056
|
+
contextOptions.proxy = {
|
|
1057
|
+
server: proxyUrl,
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
// Adicionar certificado se solicitado
|
|
1061
|
+
if (strategy.certificate) {
|
|
1062
|
+
const certPath = ApiActions.resolveCertPath(certificate.name);
|
|
1063
|
+
if (certificate.name.toLowerCase().endsWith('.p12') ||
|
|
1064
|
+
certificate.name.toLowerCase().endsWith('.pfx')) {
|
|
1065
|
+
// NOVO: Para P12/PFX - tentar múltiplas senhas em ordem (password por último)
|
|
1066
|
+
const passwordAttempts = [
|
|
1067
|
+
certificate.password || '', // Senha fornecida ou string vazia
|
|
1068
|
+
'', // Sem senha (string vazia)
|
|
1069
|
+
undefined, // Sem senha (undefined)
|
|
1070
|
+
'password', // Senha padrão como ÚLTIMA tentativa
|
|
1071
|
+
].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
|
|
1072
|
+
// Tentar cada senha até uma funcionar - SE QUALQUER FALHAR, LANÇAR ERRO PARA TENTAR PRÓXIMA ESTRATÉGIA
|
|
1073
|
+
let lastError = null;
|
|
1074
|
+
for (const attemptPassword of passwordAttempts) {
|
|
1075
|
+
try {
|
|
1076
|
+
contextOptions.clientCertificates = [
|
|
1077
|
+
{
|
|
1078
|
+
origin,
|
|
1079
|
+
pfxPath: certPath,
|
|
1080
|
+
passphrase: attemptPassword === '' ? undefined : attemptPassword,
|
|
1081
|
+
},
|
|
1082
|
+
];
|
|
1083
|
+
// Criar contexto de teste apenas para verificar se a senha funciona
|
|
1084
|
+
const testContext = await playwrightRequest.newContext(contextOptions);
|
|
1085
|
+
await testContext.dispose(); // Limpar contexto de teste
|
|
1086
|
+
break; // Sair do loop se a senha funcionou
|
|
1087
|
+
}
|
|
1088
|
+
catch (passwordError) {
|
|
1089
|
+
const errorMsg = passwordError instanceof Error
|
|
1090
|
+
? passwordError.message
|
|
1091
|
+
: String(passwordError);
|
|
1092
|
+
let passwordDesc = '';
|
|
1093
|
+
if (attemptPassword === '') {
|
|
1094
|
+
passwordDesc = '(vazia)';
|
|
1095
|
+
}
|
|
1096
|
+
else if (attemptPassword === undefined) {
|
|
1097
|
+
passwordDesc = '(undefined)';
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
passwordDesc = "'" + attemptPassword + "'";
|
|
1101
|
+
}
|
|
1102
|
+
console.log('Senha ' +
|
|
1103
|
+
passwordDesc +
|
|
1104
|
+
' falhou: ' +
|
|
1105
|
+
errorMsg.substring(0, 50) +
|
|
1106
|
+
'...');
|
|
1107
|
+
lastError =
|
|
1108
|
+
passwordError instanceof Error
|
|
1109
|
+
? passwordError
|
|
1110
|
+
: new Error(String(passwordError));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
// Se todas as senhas falharam, lançar erro para tentar próxima estratégia
|
|
1114
|
+
if (lastError) {
|
|
1115
|
+
throw new Error('Todas as senhas falharam para estrategia ' +
|
|
1116
|
+
strategy.name +
|
|
1117
|
+
': ' +
|
|
1118
|
+
lastError.message);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
// Para PEM (se tiver keyPath)
|
|
1123
|
+
if (certificate.keyPath) {
|
|
1124
|
+
contextOptions.clientCertificates = [
|
|
1125
|
+
{
|
|
1126
|
+
origin,
|
|
1127
|
+
certPath,
|
|
1128
|
+
keyPath: certificate.keyPath,
|
|
1129
|
+
passphrase: certificate.password || undefined,
|
|
1130
|
+
},
|
|
1131
|
+
];
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
// Tentar criar contexto
|
|
1136
|
+
try {
|
|
1137
|
+
const context = await playwrightRequest.newContext(contextOptions);
|
|
1138
|
+
// ✅ CORREÇÃO: Não fazer teste de conectividade para evitar interferência com AutoDocs
|
|
1139
|
+
// O contexto será testado na requisição real do usuário
|
|
1140
|
+
return context;
|
|
1141
|
+
}
|
|
1142
|
+
catch (error) {
|
|
1143
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1144
|
+
console.log('Falha ao criar contexto:', errorMsg);
|
|
1145
|
+
throw error;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
static async prepareContext(ignoreSSL = false, certificate, requestUrl, baseURL) {
|
|
1149
|
+
if (certificate) {
|
|
1150
|
+
// SISTEMA PROGRESSIVO: Múltiplas tentativas do mais completo ao mais simples
|
|
1151
|
+
const corporateProxy = process.env.PROXY_URL || 'http://proxy.redecorp.br:8080';
|
|
1152
|
+
// TENTATIVAS PROGRESSIVAS: 10 estratégias ordenadas por eficácia comprovada
|
|
1153
|
+
const strategies = [
|
|
1154
|
+
// 1. SEM PROXY: COMPROVADAMENTE EFICAZ - Certificado + SSL bypass (sem proxy)
|
|
1155
|
+
{
|
|
1156
|
+
name: 'SEM PROXY',
|
|
1157
|
+
useProxy: false,
|
|
1158
|
+
ignoreSSL: true,
|
|
1159
|
+
certificate: true,
|
|
1160
|
+
specialHeaders: false,
|
|
1161
|
+
},
|
|
1162
|
+
// 2. MÁXIMO: Certificado + Proxy + SSL bypass + Headers especiais
|
|
1163
|
+
{
|
|
1164
|
+
name: 'MÁXIMO',
|
|
1165
|
+
useProxy: true,
|
|
1166
|
+
ignoreSSL: true,
|
|
1167
|
+
certificate: true,
|
|
1168
|
+
specialHeaders: true,
|
|
1169
|
+
},
|
|
1170
|
+
// 3. COMPLETO: Certificado + Proxy + SSL bypass
|
|
1171
|
+
{
|
|
1172
|
+
name: 'COMPLETO',
|
|
1173
|
+
useProxy: true,
|
|
1174
|
+
ignoreSSL: true,
|
|
1175
|
+
certificate: true,
|
|
1176
|
+
specialHeaders: false,
|
|
1177
|
+
},
|
|
1178
|
+
// 4. SEM HEADERS: Certificado + Proxy + SSL bypass (sem headers especiais)
|
|
1179
|
+
{
|
|
1180
|
+
name: 'SEM HEADERS',
|
|
1181
|
+
useProxy: true,
|
|
1182
|
+
ignoreSSL: true,
|
|
1183
|
+
certificate: true,
|
|
1184
|
+
specialHeaders: false,
|
|
1185
|
+
},
|
|
1186
|
+
// 5. SSL ONLY: Apenas SSL bypass (sem proxy nem certificado)
|
|
1187
|
+
{
|
|
1188
|
+
name: 'SSL ONLY',
|
|
1189
|
+
useProxy: false,
|
|
1190
|
+
ignoreSSL: true,
|
|
1191
|
+
certificate: false,
|
|
1192
|
+
specialHeaders: false,
|
|
1193
|
+
},
|
|
1194
|
+
// 6. PROXY ONLY: Apenas Proxy + SSL bypass (sem certificado)
|
|
1195
|
+
{
|
|
1196
|
+
name: 'PROXY ONLY',
|
|
1197
|
+
useProxy: true,
|
|
1198
|
+
ignoreSSL: true,
|
|
1199
|
+
certificate: false,
|
|
1200
|
+
specialHeaders: false,
|
|
1201
|
+
},
|
|
1202
|
+
// 7. CERTIFICADO SIMPLES: Certificado sem extras
|
|
1203
|
+
{
|
|
1204
|
+
name: 'CERT SIMPLES',
|
|
1205
|
+
useProxy: false,
|
|
1206
|
+
ignoreSSL: false,
|
|
1207
|
+
certificate: true,
|
|
1208
|
+
specialHeaders: false,
|
|
1209
|
+
},
|
|
1210
|
+
// 8. PROXY SIMPLES: Proxy sem SSL bypass
|
|
1211
|
+
{
|
|
1212
|
+
name: 'PROXY SIMPLES',
|
|
1213
|
+
useProxy: true,
|
|
1214
|
+
ignoreSSL: false,
|
|
1215
|
+
certificate: false,
|
|
1216
|
+
specialHeaders: false,
|
|
1217
|
+
},
|
|
1218
|
+
// 9. FALLBACK MÍNIMO: SSL bypass básico
|
|
1219
|
+
{
|
|
1220
|
+
name: 'FALLBACK',
|
|
1221
|
+
useProxy: false,
|
|
1222
|
+
ignoreSSL: true,
|
|
1223
|
+
certificate: false,
|
|
1224
|
+
specialHeaders: false,
|
|
1225
|
+
},
|
|
1226
|
+
// 10. PADRÃO: Contexto básico sem nada
|
|
1227
|
+
{
|
|
1228
|
+
name: 'PADRÃO',
|
|
1229
|
+
useProxy: false,
|
|
1230
|
+
ignoreSSL: false,
|
|
1231
|
+
certificate: false,
|
|
1232
|
+
specialHeaders: false,
|
|
1233
|
+
},
|
|
1234
|
+
];
|
|
1235
|
+
for (let i = 0; i < strategies.length; i++) {
|
|
1236
|
+
const strategy = strategies[i];
|
|
1237
|
+
try {
|
|
1238
|
+
const context = await ApiActions.tryProgressiveStrategy(strategy, certificate, requestUrl, corporateProxy);
|
|
1239
|
+
if (context) {
|
|
1240
|
+
return context;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
catch (error) {
|
|
1244
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1245
|
+
console.log('Falha na tentativa ' +
|
|
1246
|
+
(i + 1) +
|
|
1247
|
+
': ' +
|
|
1248
|
+
errorMsg.substring(0, 100) +
|
|
1249
|
+
'...');
|
|
1250
|
+
// Se for erro crítico, pular para estratégias mais simples
|
|
1251
|
+
if (errorMsg.includes('certificate') || errorMsg.includes('proxy')) {
|
|
1252
|
+
console.log('Erro relacionado a certificado/proxy - continuando...');
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
// Se chegamos aqui, todas as estratégias falharam
|
|
1257
|
+
console.error('TODAS as 10 estrategias falharam para:', certificate.name);
|
|
1258
|
+
console.warn('Continuando com contexto padrão sem certificado...');
|
|
1259
|
+
}
|
|
1260
|
+
// Contexto padrão sem certificado (com controle SSL configurável)
|
|
1261
|
+
// ✅ CORRIGIDO: Incluir baseURL E sistema na chave do cache para evitar reutilização entre CTs
|
|
1262
|
+
// Obter sistema do CT atual para incluir na chave
|
|
1263
|
+
let currentSystem = 'nosystem';
|
|
1264
|
+
try {
|
|
1265
|
+
const systemFromCT = StatementTracker.getCurrentCTSystem();
|
|
1266
|
+
if (systemFromCT) {
|
|
1267
|
+
currentSystem = systemFromCT;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
catch {
|
|
1271
|
+
// Ignora se não conseguir obter sistema
|
|
1272
|
+
}
|
|
1273
|
+
const cacheKey = 'ssl_' + ignoreSSL + '_' + (certificate ? 'cert' : 'nocert') + '_base_' + (baseURL || 'none') + '_sys_' + currentSystem;
|
|
1274
|
+
if (ApiActions.contextCache.has(cacheKey)) {
|
|
1275
|
+
return ApiActions.contextCache.get(cacheKey);
|
|
1276
|
+
}
|
|
1277
|
+
// ✅ SEM CERTIFICADO: SSL configurável via variável de ambiente ou parâmetro
|
|
1278
|
+
const shouldIgnoreSSL = process.env.IGNORE_SSL_ERRORS === 'true' || ignoreSSL;
|
|
1279
|
+
let sslSource = 'parâmetro';
|
|
1280
|
+
if (certificate) {
|
|
1281
|
+
sslSource = 'certificado automático';
|
|
1282
|
+
}
|
|
1283
|
+
else if (process.env.IGNORE_SSL_ERRORS === 'true') {
|
|
1284
|
+
sslSource = 'IGNORE_SSL_ERRORS=true';
|
|
1285
|
+
}
|
|
1286
|
+
console.log('Configuracao SSL: ' +
|
|
1287
|
+
(shouldIgnoreSSL ? 'DESABILITADA' : 'HABILITADA') +
|
|
1288
|
+
' (via ' +
|
|
1289
|
+
sslSource +
|
|
1290
|
+
')');
|
|
1291
|
+
// ✅ CORRIGIDO: Usar baseURL do parâmetro (já foi calculado em _request)
|
|
1292
|
+
const context = await playwrightRequest.newContext({
|
|
1293
|
+
baseURL, // ✅ baseURL do sistema selecionado (apenas para URLs relativas e se setSystem() foi chamado)
|
|
1294
|
+
ignoreHTTPSErrors: shouldIgnoreSSL,
|
|
1295
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1296
|
+
extraHTTPHeaders: {
|
|
1297
|
+
'User-Agent': 'AutoCore-Client/1.0',
|
|
1298
|
+
Accept: 'application/json, text/plain, */*',
|
|
1299
|
+
'Cache-Control': 'no-cache',
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
ApiActions.contextCache.set(cacheKey, context);
|
|
1303
|
+
return context;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* ✅ ESTRATÉGIA FINAL: SSL Bypass completo para ambientes QA
|
|
1307
|
+
* @param requestUrl URL da requisição (opcional)
|
|
1308
|
+
* @returns Contexto com SSL completamente desabilitado
|
|
1309
|
+
*/
|
|
1310
|
+
static async createSSLBypassContext(requestUrl) {
|
|
1311
|
+
try {
|
|
1312
|
+
console.log('🔓 Criando contexto SSL bypass para ambiente QA');
|
|
1313
|
+
// ✅ FORÇAR SSL bypass sempre em ambientes QA
|
|
1314
|
+
const isQAEnvironment = process.env.NODE_ENV === 'qa' ||
|
|
1315
|
+
process.env.NODE_ENV === 'test' ||
|
|
1316
|
+
(requestUrl &&
|
|
1317
|
+
(requestUrl.includes('qa') || requestUrl.includes('test')));
|
|
1318
|
+
if (!isQAEnvironment) {
|
|
1319
|
+
console.warn('⚠️ SSL Bypass solicitado em ambiente não-QA - usando configuração padrão');
|
|
1320
|
+
}
|
|
1321
|
+
const context = await playwrightRequest.newContext({
|
|
1322
|
+
ignoreHTTPSErrors: true, // ✅ SEMPRE ignorar SSL em bypass
|
|
1323
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1324
|
+
// ✅ NOVO: Proxy corporativo para SSL bypass com certificados
|
|
1325
|
+
proxy: {
|
|
1326
|
+
server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
|
|
1327
|
+
},
|
|
1328
|
+
extraHTTPHeaders: {
|
|
1329
|
+
'User-Agent': 'AutoCore-QA-Client/1.0',
|
|
1330
|
+
Accept: 'application/json, text/plain, */*',
|
|
1331
|
+
'Cache-Control': 'no-cache',
|
|
1332
|
+
'X-QA-Environment': 'ssl-bypass',
|
|
1333
|
+
},
|
|
1334
|
+
});
|
|
1335
|
+
console.log('Contexto SSL bypass criado com sucesso');
|
|
1336
|
+
console.log('Ambiente detectado: ' + (isQAEnvironment ? 'QA' : 'Outro'));
|
|
1337
|
+
return context;
|
|
1338
|
+
}
|
|
1339
|
+
catch (error) {
|
|
1340
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1341
|
+
console.error('Falha ao criar contexto SSL bypass:', errorMessage);
|
|
1342
|
+
throw new Error('SSL bypass falhou: ' + errorMessage);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* ✅ NOVA ESTRATÉGIA 1: Usar P12 diretamente com clientCertificates (CORRIGIDO ESM)
|
|
1347
|
+
* @param certificate Certificado P12
|
|
1348
|
+
* @param requestUrl URL da requisição para determinar origem
|
|
1349
|
+
* @returns Contexto configurado com certificado P12 direto
|
|
1350
|
+
*/
|
|
1351
|
+
static async tryDirectP12Strategy(certificate, requestUrl) {
|
|
1352
|
+
try {
|
|
1353
|
+
console.log('Configurando P12 direto com clientCertificates:', certificate.name);
|
|
1354
|
+
// ✅ CORRIGIDO: Usar createRequire ao invés de require direto
|
|
1355
|
+
const require = createRequire(import.meta.url);
|
|
1356
|
+
const fs = require('fs');
|
|
1357
|
+
const certPath = ApiActions.resolveCertPath(certificate.name);
|
|
1358
|
+
// Verificar se arquivo existe
|
|
1359
|
+
if (!fs.existsSync(certPath)) {
|
|
1360
|
+
throw new Error('Arquivo P12 nao encontrado: ' + certPath);
|
|
1361
|
+
}
|
|
1362
|
+
// ✅ MÚLTIPLAS TENTATIVAS DE SENHA (password por último como sugerido)
|
|
1363
|
+
const passwordAttempts = [
|
|
1364
|
+
certificate.password || '', // Senha fornecida ou string vazia
|
|
1365
|
+
'', // Sem senha (string vazia)
|
|
1366
|
+
undefined, // Sem senha (undefined)
|
|
1367
|
+
'password', // Senha padrão como ÚLTIMA tentativa
|
|
1368
|
+
].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
|
|
1369
|
+
let context = null;
|
|
1370
|
+
for (const attemptPassword of passwordAttempts) {
|
|
1371
|
+
try {
|
|
1372
|
+
let passwordDesc = '(fornecida)';
|
|
1373
|
+
if (attemptPassword === '') {
|
|
1374
|
+
passwordDesc = '(vazia)';
|
|
1375
|
+
}
|
|
1376
|
+
else if (attemptPassword === undefined) {
|
|
1377
|
+
passwordDesc = '(undefined)';
|
|
1378
|
+
}
|
|
1379
|
+
console.log('Tentando P12 direto com senha:', passwordDesc);
|
|
1380
|
+
const origin = requestUrl
|
|
1381
|
+
? new URL(requestUrl).origin
|
|
1382
|
+
: 'https://api-qa1.telefonica.com.br';
|
|
1383
|
+
context = await playwrightRequest.newContext({
|
|
1384
|
+
ignoreHTTPSErrors: true, // ✅ IGNORAR SSL EM QA SEMPRE
|
|
1385
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1386
|
+
clientCertificates: [
|
|
1387
|
+
{
|
|
1388
|
+
origin,
|
|
1389
|
+
pfxPath: certPath,
|
|
1390
|
+
passphrase: attemptPassword === undefined ? undefined : attemptPassword,
|
|
1391
|
+
},
|
|
1392
|
+
],
|
|
1393
|
+
// ✅ NOVO: Proxy corporativo automático para certificados
|
|
1394
|
+
proxy: {
|
|
1395
|
+
server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
|
|
1396
|
+
},
|
|
1397
|
+
extraHTTPHeaders: {
|
|
1398
|
+
'User-Agent': 'AutoCore-Client/1.0',
|
|
1399
|
+
Accept: 'application/json, text/plain, */*',
|
|
1400
|
+
'Cache-Control': 'no-cache',
|
|
1401
|
+
},
|
|
1402
|
+
});
|
|
1403
|
+
let passwordDesc2 = '(fornecida)';
|
|
1404
|
+
if (attemptPassword === '') {
|
|
1405
|
+
passwordDesc2 = '(vazia)';
|
|
1406
|
+
}
|
|
1407
|
+
else if (attemptPassword === undefined) {
|
|
1408
|
+
passwordDesc2 = '(undefined)';
|
|
1409
|
+
}
|
|
1410
|
+
console.log('P12 direto configurado com sucesso usando senha:', passwordDesc2);
|
|
1411
|
+
console.log(`📍 Origin configurado: ${origin}`);
|
|
1412
|
+
return context;
|
|
1413
|
+
}
|
|
1414
|
+
catch (passwordError) {
|
|
1415
|
+
const errorMsg = passwordError instanceof Error
|
|
1416
|
+
? passwordError.message
|
|
1417
|
+
: String(passwordError);
|
|
1418
|
+
let passwordDesc3 = '(fornecida)';
|
|
1419
|
+
if (attemptPassword === '') {
|
|
1420
|
+
passwordDesc3 = '(vazia)';
|
|
1421
|
+
}
|
|
1422
|
+
else if (attemptPassword === undefined) {
|
|
1423
|
+
passwordDesc3 = '(undefined)';
|
|
1424
|
+
}
|
|
1425
|
+
console.log('P12 direto falhou com senha ' + passwordDesc3 + ':', errorMsg);
|
|
1426
|
+
if (context) {
|
|
1427
|
+
await context.dispose();
|
|
1428
|
+
context = null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
throw new Error('P12 direto falhou com todas as tentativas de senha');
|
|
1433
|
+
}
|
|
1434
|
+
catch (error) {
|
|
1435
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1436
|
+
console.error(`❌ Falha na estratégia P12 direto: ${errorMessage}`);
|
|
1437
|
+
throw new Error(`P12 direto falhou: ${errorMessage}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* ✅ NOVO: Extrai certificado e chave privada de arquivo P12/PKCS12 usando node-forge
|
|
1442
|
+
* @param p12Path Caminho para o arquivo P12/PKCS12
|
|
1443
|
+
* @param password Senha do arquivo P12 (opcional - deixar vazio se não tiver senha)
|
|
1444
|
+
* @returns Objeto com certificado e chave privada em formato PEM
|
|
1445
|
+
*/
|
|
1446
|
+
static extractP12Certificate(p12Path, password) {
|
|
1447
|
+
try {
|
|
1448
|
+
console.log('Extraindo certificado P12:', p12Path);
|
|
1449
|
+
// ✅ CORRIGIDO: Usar createRequire para importar dependências
|
|
1450
|
+
const require = createRequire(import.meta.url);
|
|
1451
|
+
const forge = require('node-forge');
|
|
1452
|
+
const fs = require('fs');
|
|
1453
|
+
// Verificar se node-forge foi carregado corretamente
|
|
1454
|
+
if (!(forge && forge.asn1 && forge.pkcs12)) {
|
|
1455
|
+
throw new Error('node-forge não carregado corretamente');
|
|
1456
|
+
}
|
|
1457
|
+
// Ler arquivo P12
|
|
1458
|
+
const p12Data = fs.readFileSync(p12Path);
|
|
1459
|
+
// ✅ ESTRATÉGIA DE FALLBACK: Tentar várias abordagens para senhas
|
|
1460
|
+
const passwordAttempts = [
|
|
1461
|
+
password || '', // Senha fornecida ou string vazia
|
|
1462
|
+
'', // Sem senha (string vazia)
|
|
1463
|
+
undefined, // Sem senha (undefined)
|
|
1464
|
+
null, // Sem senha (null)
|
|
1465
|
+
].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
|
|
1466
|
+
// biome-ignore lint/suspicious/noExplicitAny: node-forge pkcs12FromAsn1 return type is complex
|
|
1467
|
+
let p12 = null;
|
|
1468
|
+
let usedPassword;
|
|
1469
|
+
// Tentar cada abordagem de senha
|
|
1470
|
+
for (const attemptPassword of passwordAttempts) {
|
|
1471
|
+
try {
|
|
1472
|
+
let passwordDesc4 = '(fornecida)';
|
|
1473
|
+
if (attemptPassword === '') {
|
|
1474
|
+
passwordDesc4 = '(vazia)';
|
|
1475
|
+
}
|
|
1476
|
+
else if (attemptPassword === undefined) {
|
|
1477
|
+
passwordDesc4 = '(undefined)';
|
|
1478
|
+
}
|
|
1479
|
+
else if (attemptPassword === null) {
|
|
1480
|
+
passwordDesc4 = '(null)';
|
|
1481
|
+
}
|
|
1482
|
+
console.log('Tentando extrair P12 com senha:', passwordDesc4);
|
|
1483
|
+
const p12Asn1 = forge.asn1.fromDer(p12Data.toString('binary'));
|
|
1484
|
+
p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, attemptPassword);
|
|
1485
|
+
usedPassword = attemptPassword;
|
|
1486
|
+
let passwordDesc5 = '(fornecida)';
|
|
1487
|
+
if (attemptPassword === '') {
|
|
1488
|
+
passwordDesc5 = '(vazia)';
|
|
1489
|
+
}
|
|
1490
|
+
else if (attemptPassword === undefined) {
|
|
1491
|
+
passwordDesc5 = '(undefined)';
|
|
1492
|
+
}
|
|
1493
|
+
else if (attemptPassword === null) {
|
|
1494
|
+
passwordDesc5 = '(null)';
|
|
1495
|
+
}
|
|
1496
|
+
console.log('P12 extraido com sucesso usando senha:', passwordDesc5);
|
|
1497
|
+
break;
|
|
1498
|
+
}
|
|
1499
|
+
catch (passwordError) {
|
|
1500
|
+
const errorMsg = passwordError instanceof Error
|
|
1501
|
+
? passwordError.message
|
|
1502
|
+
: String(passwordError);
|
|
1503
|
+
let passwordDesc6 = '(fornecida)';
|
|
1504
|
+
if (attemptPassword === '') {
|
|
1505
|
+
passwordDesc6 = '(vazia)';
|
|
1506
|
+
}
|
|
1507
|
+
else if (attemptPassword === undefined) {
|
|
1508
|
+
passwordDesc6 = '(undefined)';
|
|
1509
|
+
}
|
|
1510
|
+
else if (attemptPassword === null) {
|
|
1511
|
+
passwordDesc6 = '(null)';
|
|
1512
|
+
}
|
|
1513
|
+
console.log('Falha com senha ' + passwordDesc6 + ':', errorMsg);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if (!p12) {
|
|
1517
|
+
throw new Error('Não foi possível extrair o P12 com nenhuma abordagem de senha testada');
|
|
1518
|
+
}
|
|
1519
|
+
// Extrair certificado
|
|
1520
|
+
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
|
|
1521
|
+
const certBagArray = certBags?.[forge.pki.oids.certBag];
|
|
1522
|
+
if (!certBagArray || certBagArray.length === 0) {
|
|
1523
|
+
throw new Error('Nenhum certificado encontrado no arquivo P12');
|
|
1524
|
+
}
|
|
1525
|
+
const cert = certBagArray[0]?.cert;
|
|
1526
|
+
if (!cert) {
|
|
1527
|
+
throw new Error('Certificado inválido no arquivo P12');
|
|
1528
|
+
}
|
|
1529
|
+
// Extrair chave privada
|
|
1530
|
+
const keyBags = p12.getBags({
|
|
1531
|
+
bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
|
|
1532
|
+
});
|
|
1533
|
+
let privateKey = null;
|
|
1534
|
+
const shroudedKeyBagArray = keyBags?.[forge.pki.oids.pkcs8ShroudedKeyBag];
|
|
1535
|
+
if (shroudedKeyBagArray && shroudedKeyBagArray.length > 0) {
|
|
1536
|
+
privateKey = shroudedKeyBagArray[0]?.key;
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
// Tentar keyBag não shrouded
|
|
1540
|
+
const unshroudedKeyBags = p12.getBags({
|
|
1541
|
+
bagType: forge.pki.oids.keyBag,
|
|
1542
|
+
});
|
|
1543
|
+
const unshroudedKeyBagArray = unshroudedKeyBags?.[forge.pki.oids.keyBag];
|
|
1544
|
+
if (unshroudedKeyBagArray && unshroudedKeyBagArray.length > 0) {
|
|
1545
|
+
privateKey = unshroudedKeyBagArray[0]?.key;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (!privateKey) {
|
|
1549
|
+
throw new Error('Chave privada não encontrada no arquivo P12');
|
|
1550
|
+
}
|
|
1551
|
+
// Converter para PEM
|
|
1552
|
+
const certPem = forge.pki.certificateToPem(cert);
|
|
1553
|
+
const keyPem = forge.pki.privateKeyToPem(privateKey);
|
|
1554
|
+
console.log('Certificado P12 extraido com sucesso');
|
|
1555
|
+
console.log(`📋 Certificado válido de: ${cert.subject.getField('CN')?.value || 'N/A'}`);
|
|
1556
|
+
console.log(`📅 Válido até: ${cert.validity.notAfter}`);
|
|
1557
|
+
return { cert: certPem, key: keyPem };
|
|
1558
|
+
}
|
|
1559
|
+
catch (error) {
|
|
1560
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1561
|
+
console.error(`❌ Erro ao extrair certificado P12: ${errorMessage}`);
|
|
1562
|
+
throw new Error(`Falha na extração P12: ${errorMessage}`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* ✅ NOVO: Configura certificado P12 extraído com Playwright
|
|
1567
|
+
* @param origin Origem do certificado (protocolo + host)
|
|
1568
|
+
* @param certPath Caminho para arquivo P12
|
|
1569
|
+
* @param password Senha do arquivo P12
|
|
1570
|
+
* @returns Contexto configurado com certificado extraído
|
|
1571
|
+
*/
|
|
1572
|
+
static async configureExtractedP12Certificate(origin, certPath, password) {
|
|
1573
|
+
try {
|
|
1574
|
+
// Extrair certificado e chave do P12
|
|
1575
|
+
const { cert, key } = await ApiActions.extractP12Certificate(certPath, password);
|
|
1576
|
+
// ✅ CORRIGIDO: Usar createRequire para fs e path
|
|
1577
|
+
const require = createRequire(import.meta.url);
|
|
1578
|
+
const fs = require('fs');
|
|
1579
|
+
const path = require('path');
|
|
1580
|
+
// Criar arquivos temporários para cert e key
|
|
1581
|
+
const tempDir = path.join(process.cwd(), '.temp-certs');
|
|
1582
|
+
if (!fs.existsSync(tempDir)) {
|
|
1583
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
1584
|
+
}
|
|
1585
|
+
const tempCertPath = path.join(tempDir, 'cert-' + Date.now() + '.pem');
|
|
1586
|
+
const tempKeyPath = path.join(tempDir, 'key-' + Date.now() + '.pem');
|
|
1587
|
+
fs.writeFileSync(tempCertPath, cert);
|
|
1588
|
+
fs.writeFileSync(tempKeyPath, key);
|
|
1589
|
+
console.log('Certificado temporário criado:', tempCertPath);
|
|
1590
|
+
console.log('Chave temporária criada:', tempKeyPath);
|
|
1591
|
+
// Configurar contexto Playwright com certificado extraído
|
|
1592
|
+
const context = await playwrightRequest.newContext({
|
|
1593
|
+
ignoreHTTPSErrors: true, // ✅ SEMPRE IGNORAR SSL EM QA
|
|
1594
|
+
clientCertificates: [
|
|
1595
|
+
{
|
|
1596
|
+
origin,
|
|
1597
|
+
certPath: tempCertPath,
|
|
1598
|
+
keyPath: tempKeyPath,
|
|
1599
|
+
},
|
|
1600
|
+
],
|
|
1601
|
+
// ✅ NOVO: Proxy corporativo para certificados
|
|
1602
|
+
proxy: {
|
|
1603
|
+
server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
|
|
1604
|
+
},
|
|
1605
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1606
|
+
extraHTTPHeaders: {
|
|
1607
|
+
'User-Agent': 'AutoCore-P12-Client/1.0',
|
|
1608
|
+
Accept: 'application/json, text/plain, */*',
|
|
1609
|
+
'Cache-Control': 'no-cache',
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
1612
|
+
// Limpar arquivos temporários após uso (cleanup assíncrono)
|
|
1613
|
+
setTimeout(() => {
|
|
1614
|
+
try {
|
|
1615
|
+
if (fs.existsSync(tempCertPath))
|
|
1616
|
+
fs.unlinkSync(tempCertPath);
|
|
1617
|
+
if (fs.existsSync(tempKeyPath))
|
|
1618
|
+
fs.unlinkSync(tempKeyPath);
|
|
1619
|
+
console.log('Arquivos temporários de certificado removidos');
|
|
1620
|
+
}
|
|
1621
|
+
catch (cleanupError) {
|
|
1622
|
+
console.warn('Falha ao limpar arquivos temporários:', cleanupError);
|
|
1623
|
+
}
|
|
1624
|
+
}, 30_000); // Limpar após 30 segundos
|
|
1625
|
+
console.log('Contexto P12 configurado com sucesso');
|
|
1626
|
+
return context;
|
|
1627
|
+
}
|
|
1628
|
+
catch (error) {
|
|
1629
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1630
|
+
console.error('Erro ao configurar P12:', errorMessage);
|
|
1631
|
+
throw new Error('Falha na configuracao P12: ' + errorMessage);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* ✅ MELHORADO: Tentar configuração de certificado com estratégia P12 + fallback
|
|
1636
|
+
*/
|
|
1637
|
+
static async tryClientCertificateConfig(origin, certPath, password) {
|
|
1638
|
+
const baseConfig = {
|
|
1639
|
+
ignoreHTTPSErrors: true,
|
|
1640
|
+
timeout: TestContext.getProjectTimeout(),
|
|
1641
|
+
extraHTTPHeaders: {
|
|
1642
|
+
'User-Agent': 'AutoCore-Certificate-Client/1.0',
|
|
1643
|
+
Accept: 'application/json, text/plain, */*',
|
|
1644
|
+
'Cache-Control': 'no-cache',
|
|
1645
|
+
},
|
|
1646
|
+
storageState: undefined,
|
|
1647
|
+
httpCredentials: undefined,
|
|
1648
|
+
// ✅ NOVO: Proxy corporativo automático para certificados
|
|
1649
|
+
proxy: {
|
|
1650
|
+
server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
|
|
1651
|
+
},
|
|
1652
|
+
};
|
|
1653
|
+
// ESTRATEGIA 1: Tentar extração P12 com node-forge (NOVO)
|
|
1654
|
+
try {
|
|
1655
|
+
console.log('Tentativa 1: Extração P12 com node-forge');
|
|
1656
|
+
const context = await ApiActions.configureExtractedP12Certificate(origin, certPath, password);
|
|
1657
|
+
console.log('Certificado P12 extraído e configurado com sucesso');
|
|
1658
|
+
return context;
|
|
1659
|
+
}
|
|
1660
|
+
catch (p12Error) {
|
|
1661
|
+
const p12ErrorMessage = p12Error instanceof Error ? p12Error.message : String(p12Error);
|
|
1662
|
+
console.log('Falha na extração P12:', p12ErrorMessage);
|
|
1663
|
+
// ESTRATEGIA 2: Tentar abordagem original SEM senha (compatível com projetos Java)
|
|
1664
|
+
try {
|
|
1665
|
+
console.log('Tentativa 2: Abordagem original SEM senha');
|
|
1666
|
+
const contextWithoutPassword = await playwrightRequest.newContext({
|
|
1667
|
+
...baseConfig,
|
|
1668
|
+
clientCertificates: [
|
|
1669
|
+
{
|
|
1670
|
+
origin,
|
|
1671
|
+
pfxPath: certPath,
|
|
1672
|
+
// passphrase omitida intencionalmente
|
|
1673
|
+
},
|
|
1674
|
+
],
|
|
1675
|
+
});
|
|
1676
|
+
console.log('Certificado configurado com sucesso SEM senha (abordagem original)');
|
|
1677
|
+
return contextWithoutPassword;
|
|
1678
|
+
}
|
|
1679
|
+
catch (errorWithoutPassword) {
|
|
1680
|
+
const errorMessage = errorWithoutPassword instanceof Error
|
|
1681
|
+
? errorWithoutPassword.message
|
|
1682
|
+
: String(errorWithoutPassword);
|
|
1683
|
+
console.log('Falha sem senha (original):', errorMessage);
|
|
1684
|
+
// ESTRATEGIA 3: Se falhar sem senha E temos senha, tentar COM senha
|
|
1685
|
+
if (password && password.trim() !== '') {
|
|
1686
|
+
try {
|
|
1687
|
+
console.log('Tentativa 3: Abordagem original COM senha');
|
|
1688
|
+
const contextWithPassword = await playwrightRequest.newContext({
|
|
1689
|
+
...baseConfig,
|
|
1690
|
+
clientCertificates: [
|
|
1691
|
+
{
|
|
1692
|
+
origin,
|
|
1693
|
+
pfxPath: certPath,
|
|
1694
|
+
passphrase: password,
|
|
1695
|
+
},
|
|
1696
|
+
],
|
|
1697
|
+
});
|
|
1698
|
+
console.log('Certificado configurado com sucesso COM senha (abordagem original)');
|
|
1699
|
+
return contextWithPassword;
|
|
1700
|
+
}
|
|
1701
|
+
catch (errorWithPassword) {
|
|
1702
|
+
const errorWithPasswordMessage = errorWithPassword instanceof Error
|
|
1703
|
+
? errorWithPassword.message
|
|
1704
|
+
: String(errorWithPassword);
|
|
1705
|
+
console.error('Falha com senha (original):', errorWithPasswordMessage);
|
|
1706
|
+
// ESTRATEGIA 4: FALLBACK - Contexto sem certificado
|
|
1707
|
+
try {
|
|
1708
|
+
console.log('Tentativa 4: FALLBACK sem certificado');
|
|
1709
|
+
const fallbackContext = await playwrightRequest.newContext({
|
|
1710
|
+
...baseConfig,
|
|
1711
|
+
ignoreHTTPSErrors: true,
|
|
1712
|
+
// Remover certificado e usar apenas ignoreHTTPSErrors
|
|
1713
|
+
});
|
|
1714
|
+
console.log('FALLBACK: Contexto criado SEM certificado (apenas ignoreHTTPSErrors)');
|
|
1715
|
+
console.log('SUGESTAO: Verifique se o certificado P12 está correto ou se o servidor aceita conexões sem certificado');
|
|
1716
|
+
return fallbackContext;
|
|
1717
|
+
}
|
|
1718
|
+
catch (fallbackError) {
|
|
1719
|
+
const fallbackMessage = fallbackError instanceof Error
|
|
1720
|
+
? fallbackError.message
|
|
1721
|
+
: String(fallbackError);
|
|
1722
|
+
throw new Error('Certificado falhou em todas as tentativas:\\n1. Extração P12: ' +
|
|
1723
|
+
p12ErrorMessage +
|
|
1724
|
+
'\\n2. Original sem senha: ' +
|
|
1725
|
+
errorMessage +
|
|
1726
|
+
'\\n3. Original com senha: ' +
|
|
1727
|
+
errorWithPasswordMessage +
|
|
1728
|
+
'\\n4. Fallback: ' +
|
|
1729
|
+
fallbackMessage);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
else {
|
|
1734
|
+
// ESTRATEGIA 4: Se não tem senha, tentar FALLBACK
|
|
1735
|
+
try {
|
|
1736
|
+
console.log('Tentativa 3 (sem senha): FALLBACK sem certificado');
|
|
1737
|
+
const fallbackContext = await playwrightRequest.newContext({
|
|
1738
|
+
...baseConfig,
|
|
1739
|
+
ignoreHTTPSErrors: true,
|
|
1740
|
+
// Remover certificado e usar apenas ignoreHTTPSErrors
|
|
1741
|
+
});
|
|
1742
|
+
console.log('FALLBACK: Contexto criado SEM certificado (apenas ignoreHTTPSErrors)');
|
|
1743
|
+
console.log('SUGESTAO: Verifique se o certificado P12 está correto ou configure uma senha');
|
|
1744
|
+
return fallbackContext;
|
|
1745
|
+
}
|
|
1746
|
+
catch (fallbackError) {
|
|
1747
|
+
const fallbackMessage = fallbackError instanceof Error
|
|
1748
|
+
? fallbackError.message
|
|
1749
|
+
: String(fallbackError);
|
|
1750
|
+
throw new Error('Certificado falhou em todas as tentativas:\\n1. Extração P12: ' +
|
|
1751
|
+
p12ErrorMessage +
|
|
1752
|
+
'\\n2. Original sem senha: ' +
|
|
1753
|
+
errorMessage +
|
|
1754
|
+
'\\n3. Fallback: ' +
|
|
1755
|
+
fallbackMessage);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
static getMetrics() {
|
|
1762
|
+
return {
|
|
1763
|
+
totalRequisicoes: ApiActions.requestCount,
|
|
1764
|
+
totalSuccesso: ApiActions.successCount,
|
|
1765
|
+
totalFalhas: ApiActions.failureCount,
|
|
1766
|
+
foiUtilizado: ApiActions.foiUsado,
|
|
1767
|
+
logsPerTest: ApiActions.logsPerTest,
|
|
1768
|
+
duracoes: ApiActions.duracoes,
|
|
1769
|
+
dadosGerais: ApiActions.dadosGerais,
|
|
1770
|
+
inicioExecucao: ApiActions.inicioExecucao,
|
|
1771
|
+
fimExecucao: ApiActions.fimExecucao,
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
static hasLogsForTest(testName) {
|
|
1775
|
+
return !!(ApiActions.logsPerTest[testName] &&
|
|
1776
|
+
ApiActions.logsPerTest[testName].length > 0);
|
|
1777
|
+
}
|
|
1778
|
+
static getLogsForTest(testName) {
|
|
1779
|
+
return ApiActions.logsPerTest[testName] || [];
|
|
1780
|
+
}
|
|
1781
|
+
static async coletarDadosDoTeste() {
|
|
1782
|
+
try {
|
|
1783
|
+
await UnifiedReportManager.coletarDadosDoTeste();
|
|
1784
|
+
}
|
|
1785
|
+
catch (error) {
|
|
1786
|
+
Logger.error('Erro ao coletar dados do teste via UnifiedReportManager', error);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* ✅ Anexar resumo de logs
|
|
1791
|
+
*/
|
|
1792
|
+
static async anexarResumoDeLogs() {
|
|
1793
|
+
try {
|
|
1794
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
1795
|
+
if (!testInfo)
|
|
1796
|
+
return;
|
|
1797
|
+
const logResumo = Object.entries(ApiActions.logsPerTest)
|
|
1798
|
+
.map(([teste, logs]) => '### ' + teste + '\\n' + logs.map((l) => '- ' + l).join('\\n'))
|
|
1799
|
+
.join('\\n\\n');
|
|
1800
|
+
await testInfo.attach('Resumo de Logs dos Testes', {
|
|
1801
|
+
body: Buffer.from(logResumo, 'utf-8'),
|
|
1802
|
+
contentType: 'text/markdown',
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
catch (error) {
|
|
1806
|
+
Logger.error('Erro ao anexar resumo de logs:', error);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* ✅ Anexar relatório final
|
|
1811
|
+
*/
|
|
1812
|
+
static async anexarRelatorioFinal() {
|
|
1813
|
+
try {
|
|
1814
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
1815
|
+
if (!testInfo)
|
|
1816
|
+
return;
|
|
1817
|
+
Logger.bloco('Finalizado contexto de requisição API');
|
|
1818
|
+
const resultados = Object.entries(ApiActions.logsPerTest).map(([nome, logs]) => {
|
|
1819
|
+
const temFalha = logs.some((l) => l.includes('X'));
|
|
1820
|
+
return { nome, resultado: temFalha ? 'FAIL' : 'PASS' };
|
|
1821
|
+
});
|
|
1822
|
+
// Tentar usar UnifiedReportManager se disponível
|
|
1823
|
+
try {
|
|
1824
|
+
await UnifiedReportManager.finalizarTeste();
|
|
1825
|
+
return; // Sucesso, sair da função
|
|
1826
|
+
}
|
|
1827
|
+
catch {
|
|
1828
|
+
// Continuar para fallback
|
|
1829
|
+
}
|
|
1830
|
+
// Fallback para relatório simples em texto
|
|
1831
|
+
const relatorioTexto = '# Relatório de Execução API\n\n' +
|
|
1832
|
+
'## Resumo\n' +
|
|
1833
|
+
'- Total de Testes: ' +
|
|
1834
|
+
resultados.length +
|
|
1835
|
+
'\n' +
|
|
1836
|
+
'- Sucessos: ' +
|
|
1837
|
+
resultados.filter((r) => r.resultado === 'PASS').length +
|
|
1838
|
+
'\n' +
|
|
1839
|
+
'- Falhas: ' +
|
|
1840
|
+
resultados.filter((r) => r.resultado === 'FAIL').length +
|
|
1841
|
+
'\n\n' +
|
|
1842
|
+
'## Detalhes\n' +
|
|
1843
|
+
resultados.map((r) => '- ' + r.nome + ': ' + r.resultado).join('\n');
|
|
1844
|
+
await testInfo.attach('Relatório de Execução API', {
|
|
1845
|
+
body: Buffer.from(relatorioTexto, 'utf-8'),
|
|
1846
|
+
contentType: 'text/markdown',
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
catch (error) {
|
|
1850
|
+
Logger.error('Erro ao anexar relatórios finais', error);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Verifica se o header deve ser mascarado nos logs.
|
|
1855
|
+
* @param key Nome do header.
|
|
1856
|
+
* @returns true se deve mascarar, false caso contrário.
|
|
1857
|
+
*/
|
|
1858
|
+
static shouldMaskHeader(key) {
|
|
1859
|
+
const sensitiveHeaders = [
|
|
1860
|
+
'authorization',
|
|
1861
|
+
'x-api-key',
|
|
1862
|
+
'set-cookie',
|
|
1863
|
+
'cookie',
|
|
1864
|
+
'proxy-authorization',
|
|
1865
|
+
];
|
|
1866
|
+
return sensitiveHeaders.includes(key.toLowerCase());
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Aguarda um tempo fixo em segundos.
|
|
1870
|
+
* Útil para aguardar processamento assíncrono, propagação de dados,
|
|
1871
|
+
* ou cooldown entre chamadas de API.
|
|
1872
|
+
*
|
|
1873
|
+
* @param segundos - Tempo de espera em segundos
|
|
1874
|
+
* @param descricao - Descrição do motivo da espera (para logs)
|
|
1875
|
+
*/
|
|
1876
|
+
static async fixedWait(segundos, descricao = '') {
|
|
1877
|
+
const ms = segundos * 1000;
|
|
1878
|
+
const actionDesc = descricao || `Aguardando ${segundos}s`;
|
|
1879
|
+
Logger.info(`[ApiActions] ⏱️ ${actionDesc}`);
|
|
1880
|
+
let statementTimestamp = null;
|
|
1881
|
+
try {
|
|
1882
|
+
statementTimestamp = StatementTracker.startStatement('ApiActions', `fixedWait: ${actionDesc}`);
|
|
1883
|
+
}
|
|
1884
|
+
catch { }
|
|
1885
|
+
const startTime = Date.now();
|
|
1886
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1887
|
+
const duration = Date.now() - startTime;
|
|
1888
|
+
Logger.success(`[ApiActions] ✅ Espera concluída: ${actionDesc} (real: ${duration}ms)`);
|
|
1889
|
+
try {
|
|
1890
|
+
StatementTracker.recordAction('API', actionDesc, true, duration, { action: 'fixedWait' });
|
|
1891
|
+
}
|
|
1892
|
+
catch { }
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
// 🚫 AUTO-ATIVAÇÃO REMOVIDA: ApiActions são métodos internos que não devem ser interceptados
|
|
1896
|
+
// ApiActions.post(), .get(), etc. não são CTs válidos e devem ser filtrados
|
|
1897
|
+
// A interceptação foi removida para evitar falsos CTs nos relatórios
|
|
1898
|
+
// CÓDIGO REMOVIDO:
|
|
1899
|
+
// try {
|
|
1900
|
+
// installDatabaseInterceptor(ApiActions, 'ApiActions')
|
|
1901
|
+
// } catch (error) {
|
|
1902
|
+
// if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
|
|
1903
|
+
// process.stderr.write(`[DatabaseInterceptor] Falha na auto-ativação ApiActions: ${String(error)}\n`)
|
|
1904
|
+
// }
|
|
1905
|
+
// }
|