const { test } = require('node:test'); const assert = require('node:assert/strict'); const { buildWorkflow } = require('./build-workflow'); test('workflow montado tem nome e nodes esperados', () => { const wf = buildWorkflow(); assert.equal(wf.name, '[DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow)'); const names = wf.nodes.map((n) => n.name); assert.ok(names.includes('Code - Montar SQL Candidatos')); }); test('Code - Montar SQL Candidatos tem a logica de config e o SQL inlined (sem marcador de build)', () => { const wf = buildWorkflow(); const node = wf.nodes.find((n) => n.name === 'Code - Montar SQL Candidatos'); assert.ok(node.parameters.jsCode.includes('function parseConfig')); assert.ok(node.parameters.jsCode.includes('WITH ultima_interacao')); assert.ok(!node.parameters.jsCode.includes('__BUILD__')); assert.ok(!node.parameters.jsCode.includes('__SQL_CANDIDATOS_V2__')); }); test('nenhum node de Code sobra com marcador nao resolvido', () => { const wf = buildWorkflow(); for (const node of wf.nodes) { if (node.type === 'n8n-nodes-base.code') { assert.ok(!node.parameters.jsCode.includes('__BUILD__'), `node ${node.name} tem marcador nao resolvido`); } } }); test('todo jsCode gerado e sintaticamente valido (pega identificador duplicado, chaves erradas, etc.)', () => { const wf = buildWorkflow(); for (const node of wf.nodes) { if (node.type === 'n8n-nodes-base.code') { assert.doesNotThrow(() => { // eslint-disable-next-line no-new-func new Function('$input', '$env', '$', node.parameters.jsCode); }, `node ${node.name} tem jsCode com erro de sintaxe`); } } }); test('nenhum jsCode gerado contem "require(" sobrando (modulos devem estar totalmente inlined)', () => { const wf = buildWorkflow(); for (const node of wf.nodes) { if (node.type === 'n8n-nodes-base.code') { assert.ok(!/\brequire\(/.test(node.parameters.jsCode), `node ${node.name} ainda tem require() nao resolvido`); } } }); test('workflow gerado NAO tem nenhum node de webhook de debug', () => { const wf = buildWorkflow(); const names = wf.nodes.map((n) => n.name); assert.ok(!names.includes('Debug - Ver Painel')); assert.ok(!names.includes('Debug - Responder')); assert.ok(!names.includes('Code - Montar Painel Debug')); const webhookNodes = wf.nodes.filter((n) => n.type === 'n8n-nodes-base.webhook'); assert.equal(webhookNodes.length, 0, 'nenhum node webhook deveria sobrar no workflow gerado'); }); test('glpi_base_url no Config nao e mais o placeholder do V1', () => { const wf = buildWorkflow(); const configNode = wf.nodes.find((n) => n.name === 'Config - Coordenador GLPI v2'); const glpiField = configNode.parameters.assignments.assignments.find((a) => a.name === 'glpi_base_url'); assert.notEqual(glpiField.value, 'https://glpi.suaempresa.com.br'); assert.equal(glpiField.value, 'https://glpi.gtigrupo.com.br'); }); test('todo node que chama normalizeGlpiText/normalizeTicket/buildAiContextText tem a definicao dessas funcoes inlined (deps transitivas de context.js -> normalize.js)', () => { // Regressao: context.js passou a fazer require('./normalize') nesta correcao, mas // readLogicSource() so inlina o que o header @requires do node lista -- nao resolve // deps transitivas. Um node com "@requires context.js" e sem "normalize.js" no header // compila (sintaxe valida), mas explode em runtime com "normalizeGlpiText is not defined". const wf = buildWorkflow(); for (const node of wf.nodes) { if (node.type !== 'n8n-nodes-base.code') continue; const code = node.parameters.jsCode; const callsHelper = /\bnormalizeGlpiText\(|\bnormalizeTicket\(|\bbuildAiContextText\(/.test(code); if (!callsHelper) continue; assert.ok( code.includes('function normalizeGlpiText'), `node ${node.name} chama uma funcao de normalize.js mas nao tem normalizeGlpiText inlined (adicionar normalize.js ao @requires)`, ); } }); test('link_glpi na query de candidatos usa a variavel de config, nao um dominio fixo no SQL', () => { const fs = require('node:fs'); const path = require('node:path'); const sql = fs.readFileSync(path.join(__dirname, '..', 'sql', 'candidatos_v2_glpi.sql'), 'utf8'); assert.ok(sql.includes("CONCAT('{GLPI_BASE_URL}'"), 'o SQL deve continuar usando o placeholder substituido pelo node, nunca um dominio hardcoded'); assert.ok(!sql.includes('suaempresa.com.br')); assert.ok(!sql.includes('gtigrupo.com.br'), 'o dominio real deve vir so da config, nunca hardcoded no SQL'); });