Files
SLACK_COORD_RELATORIO/relatorio/tools/smoke_test_generated_nodes.js
T
Gustavo Santos d21c4596e1 chore: import relatorio (subtree)
git-subtree-dir: relatorio
git-subtree-mainline: e322cec92b
git-subtree-split: 34bcb132f3
2026-10-01 14:26:02 -03:00

221 lines
7.7 KiB
JavaScript

'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.');