'use strict'; /** * Smoke test do workflow GERADO (nao dos modulos fonte, que ja tem testes * de unidade em test/*.test.js). Executa, com Node puro + vm, exatamente o * texto que sera colado em cada Code node do n8n, simulando $()/$input/$json * com dados de exemplo (mesmos numeros de referencia da missao, 20/08/2026). * * Uso: node tools/smoke_test_generated_nodes.js */ const fs = require('fs'); const path = require('path'); const vm = require('vm'); const WF_PATH = path.join(__dirname, '..', 'Resumo Operacional GLPI v2 - IA.n8n.json'); const workflow = JSON.parse(fs.readFileSync(WF_PATH, 'utf8')); function nodeByName(name) { const n = workflow.nodes.find((x) => x.name === name); if (!n) throw new Error(`node nao encontrado no workflow gerado: ${name}`); return n; } /** registry simula a memoria de execucao do n8n: nome do node -> lista de items {json} */ const registry = new Map(); function makeDollar() { return function $(nodeName) { const items = registry.get(nodeName); if (!items) throw new Error(`$('${nodeName}') chamado antes do node rodar (ordem errada no smoke test)`); return { first: () => items[0], all: () => items, }; }; } function runCodeNode(nodeName, inputItems, extraGlobals) { const node = nodeByName(nodeName); const code = node.parameters.jsCode; const sandbox = { $: makeDollar(), $json: inputItems[0] ? inputItems[0].json : {}, $input: { first: () => inputItems[0], all: () => inputItems, }, // $execution.mode: 'trigger' simula o Schedule Trigger (producao real); // o smoke test usa isso por padrao porque e o caminho mais exercitado // pelo relatorio (execucao manual e coberta por sendGuard.test.js). $execution: { mode: 'trigger' }, console, Date, JSON, Math, Number, String, Boolean, Array, Object, Map, Set, RegExp, ...extraGlobals, }; vm.createContext(sandbox); const wrapped = `(function(){ ${code} \n})()`; const result = vm.runInContext(wrapped, sandbox, { filename: nodeName }); registry.set(nodeName, result); return result; } // ----------------------------------------------------------------------- // Fixtures (mesmos numeros de referencia da missao: 20/08/2026) // ----------------------------------------------------------------------- const configItem = [ { json: { data_alvo: '2026-08-20', slack_webhook_url: 'https://hooks.slack.com/services/TESTE', AI_ENABLED: true, AI_MODEL: 'gpt-5.5', AI_MAX_TICKETS: 40, AI_MAX_CONTEXT_CHARS: 12000, AI_MIN_CONFIDENCE: 0.6, AI_TIMEOUT_MS: 30000, HISTORICAL_BASELINE_DAYS: 20, HISTORICAL_BASELINE_WEEKS: 4, MAX_REPORT_INSIGHTS: 5, DEV_MODE: true, SHADOW_MODE: true, SEND_ENABLED: true, FORCE_SEND: false, SLACK_DESTINATION: 'C0000TESTE', EXTENDED_METRICS_ENABLED: false, ANOMALY_MULTIPLIER: 2.0, ANOMALY_MIN_DELTA: 3, CLIENT_CONCENTRATION_MIN_COUNT: 3, CLIENT_CONCENTRATION_MIN_SHARE: 0.4, PENDING_AGE_ATTENTION_DAYS: 15, TICKET_CONTEXT_SQL_LIMIT: 300, }, }, ]; console.log('--- 1) Code - Periodo e Queries SQL ---'); const out1 = runCodeNode('Code - Periodo e Queries SQL', configItem); console.log('tipo_periodo:', out1[0].json.period.tipo_periodo, '| periodo_label:', out1[0].json.period.periodo_label); console.log('query indicadores (trecho):', out1[0].json.queries.indicadores.slice(0, 80).replace(/\n/g, ' '), '...'); registry.set('MySQL - Indicadores do Periodo', [ { json: { tipo_periodo: 'manual_diario', periodo_label: '2026-08-20', data_inicio: '2026-08-20', data_fim: '2026-08-20', abertos: 12, fechados: 11, saldo: -1, pendentes: 22, alta_prioridade_pendentes: 3, categorias_recorrentes: 'Software / SO: 4\nBug / Falha do Sistema: 2', clientes_volume: 'IMDI: 2 chamados\nLaboratorio da Cidade: 2 chamados', }, }, ]); registry.set('MySQL - Ranking Categorias', [ { json: { id: 10, nome: 'Software / SO', total: 4 } }, { json: { id: 11, nome: 'Bug / Falha do Sistema', total: 2 } }, ]); registry.set('MySQL - Ranking Clientes', [ { json: { id: 20, nome: 'IMDI', total: 2 } }, { json: { id: 21, nome: 'Laboratorio da Cidade', total: 2 } }, ]); registry.set( 'MySQL - Contexto de Chamados', Array.from({ length: 5 }, (_, i) => ({ json: { id: i + 1, title: `Chamado de teste ${i + 1}`, category: 'Software / SO', client: 'IMDI', priority: 3, status: 5, opened_at: '2026-08-20', closed_at: null, solved_at: null, description: 'Descricao de teste para o smoke test.', solution: null, }, })) ); registry.set('MySQL - Baseline Diario Categoria', [ { dia: '2026-08-13', id: 10, total: 1 }, { dia: '2026-08-14', id: 10, total: 1 }, ].map((r) => ({ json: r }))); registry.set('MySQL - Baseline Diario Cliente', [ { dia: '2026-08-13', id: 20, total: 1 }, ].map((r) => ({ json: r }))); registry.set('Data Table - Buscar Memoria de Insights', []); registry.set('Data Table - Buscar Historico Anterior', []); registry.set('Data Table - Buscar Log de Envios', []); console.log('\n--- 2) Code - Preparar Contexto IA ---'); const out2 = runCodeNode('Code - Preparar Contexto IA', [{ json: {} }]); console.log('tickets no contexto:', out2[0].json.ticketContext.tickets.length); console.log('modelo solicitado:', out2[0].json.openai_request_body.model); console.log('\n--- 3) Code - Normalizar Resposta IA (simulando resposta OpenAI valida) ---'); const respostaIaSimulada = { choices: [ { message: { content: JSON.stringify({ period_assessment: { status: 'stable', summary: 'Operacao dentro do padrao.' }, insights: [ { type: 'CATEGORY_SPIKE', severity: 'medium', title: 'Software/SO acima do padrao', summary: 'Volume de Software/SO ficou acima da media recente.', evidence_ticket_ids: [1, 2], confidence: 0.85, reportable: true, subject_key: 'categoria:software_so', }, ], }), }, }, ], }; const out3 = runCodeNode('Code - Normalizar Resposta IA', [{ json: respostaIaSimulada }]); console.log('aiCall:', out3[0].json.aiCall.attempted, out3[0].json.aiCall.error); console.log('\n--- 4) Code - Motor V2: Relatorio Completo ---'); const out4 = runCodeNode('Code - Motor V2: Relatorio Completo', [{ json: out3[0].json }]); const relatorio = out4[0].json; console.log('status_operacional:', relatorio.status_operacional); console.log('insights_aceitos:', relatorio.insights_aceitos.length); console.log('envio:', JSON.stringify(relatorio.envio, null, 2)); console.log('observabilidade:', JSON.stringify(relatorio.observabilidade, null, 2)); console.log('\n=== mensagem_v1 (fallback / legado) ===\n' + relatorio.mensagem_v1); console.log('\n=== mensagem_v2 (V2 curta) ===\n' + relatorio.mensagem_v2); console.log('\n--- 5) Code - Preparar Historico ---'); const out5 = runCodeNode('Code - Preparar Historico', [{ json: relatorio }]); console.log(JSON.stringify(out5[0].json, null, 2)); console.log('\n--- 6) Code - Preparar Log de Envio (Sucesso/Falha) ---'); registry.set('Code - Motor V2: Relatorio Completo', [{ json: relatorio }]); const out6a = runCodeNode('Code - Preparar Log de Envio (Sucesso)', [{ json: {} }]); console.log('log sucesso:', JSON.stringify(out6a[0].json)); const out6b = runCodeNode('Code - Preparar Log de Envio (Falha)', [{ json: { error: { message: 'timeout simulado' } } }]); console.log('log falha:', JSON.stringify(out6b[0].json)); console.log('\nSMOKE TEST OK — todos os Code nodes gerados executaram sem excecao.');