Files
SLACK_COORD_RELATORIO/tools/build-workflow.test.js
T
GustavoandClaude Sonnet 5 edecd271b9 fix(v2): fix missing transitive dependency (normalize.js) in 3 node drivers
Found while syncing this cycle's fixes to the live DEV workflow and
preparing Run A: tools/build-workflow.js's readLogicSource() only inlines
whatever a node's own `@requires` header lists -- it does not resolve
transitive dependencies. context.js started doing `require('./normalize')`
earlier this cycle, but the three node drivers that need normalizeTicket/
buildAiContextText (which call normalizeGlpiText internally) still only
declared `@requires context.js`, never `normalize.js` itself.

The result compiles fine (new Function(...) syntax-checks pass -- a
ReferenceError is a runtime error, not a syntax error) but would throw
"normalizeGlpiText is not defined" the moment any of these three nodes
actually ran:
- Code - Mesclar Contexto e Calcular Hash (merge-context-hash.node.js)
- Code - Normalizar Candidatos Base (normalize-candidates.node.js)
- Code - Preparar Prompt IA (prepare-ai-prompt.node.js)

This never surfaced in Run 1 (prior session) because normalize.js/the
descricao field didn't exist yet at that point, and it didn't surface in
this cycle's local test suite because node--test only exercises the pure
logic/*.js modules directly (with real requires), never the inlined,
concatenated jsCode a node actually runs in n8n.

Fixed by adding normalize.js explicitly to each of the three headers,
matching the codebase's existing convention for multi-file deps (e.g.
compute-score.node.js already lists `@requires cooldown.js score.js`
together instead of relying on any transitive resolution).

Added a regression test that scans every generated Code node for calls to
normalizeGlpiText/normalizeTicket/buildAiContextText and asserts the
normalizeGlpiText function definition is actually present in that same
inlined source -- would have caught this before it ever reached DEV.

102/102 tests passing (was 101/101 -- +1 new regression test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 20:01:14 -03:00

95 lines
4.5 KiB
JavaScript

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