Files
SLACK_COORD_RELATORIO/test/sendGuard.test.js
T
Automacao GLPIandClaude Sonnet 5 64ddfb9b7b V2: guarda de envio real (SEND_ENABLED/FORCE_SEND), idempotencia por send_key e Slack nativo
- src/v2/sendGuard.js (novo): canSend() reduz SHADOW_MODE/DEV_MODE/
  SEND_ENABLED/FORCE_SEND a uma formula unica testada exaustivamente
  (secao 15 do ciclo final: matriz completa de guarda de seguranca).
  computeMessageHash/buildSendKey/isDuplicate implementam a chave
  logica de idempotencia da secao 13 (report_type+periodo+destino+hash).
- src/v2/config.js: SEND_ENABLED, FORCE_SEND, SLACK_DESTINATION.
- src/v2/pipeline.js: buildReport agora calcula envio.pode_enviar/
  motivo_guarda/duplicata_bloqueada/send_key a partir de triggerType
  (.mode) e sendLogRows (Data Table de log de envios).
- tools/build_v2_workflow*.js: novo Data Table 'Buscar Log de Envios',
  troca do HTTP Slack com webhook placeholder por node Slack nativo
  (credencial real lNqwBBrtLfkRqw2X, reaproveitada do projeto irmao
  Coordenador GLPI), branches de sucesso/falha gravando o log de envio
  so apos confirmacao real do Slack.
- test/sendGuard.test.js, test/workflowStructure.test.js (novos):
  cobrem a matriz de guarda de seguranca e travam regressao do bug de
  timeout (executeOnce=true nos 8 nodes de leitura em lote) e do bug
  de sendGuard.js nao concatenado no Code node gerado.
- Corrigido bug real encontrado nesta sessao: sendGuard.js nao estava
  na lista de modulos concatenados pelo gerador, deixando canSend/
  computeMessageHash/buildSendKey undefined em runtime.
- Deployado e validado ao vivo no DEV (workflow 3x5kb58TeBCvirm6):
  n8n_validate_workflow -> valid:true, 0 erros, executeOnce preservado
  nos 9 nodes de leitura em lote.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 09:22:42 -03:00

115 lines
4.6 KiB
JavaScript

'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { canSend, computeMessageHash, buildSendKey, isDuplicate } = require('../src/v2/sendGuard');
const BASE = { SHADOW_MODE: false, DEV_MODE: false, SEND_ENABLED: true, FORCE_SEND: false };
// --- Matriz de guarda de seguranca completa (secao 15 do ciclo final) ---
test('SHADOW_MODE=true nunca envia, mesmo scheduled com tudo mais ligado', () => {
const r = canSend({ triggerType: 'scheduled', config: { ...BASE, SHADOW_MODE: true } });
assert.equal(r.podeEnviar, false);
});
test('SHADOW_MODE=true nunca envia, mesmo manual com FORCE_SEND=true', () => {
const r = canSend({ triggerType: 'manual', config: { ...BASE, SHADOW_MODE: true, FORCE_SEND: true } });
assert.equal(r.podeEnviar, false);
});
test('DEV_MODE=true sem override nunca envia para producao (scheduled)', () => {
const r = canSend({ triggerType: 'scheduled', config: { ...BASE, DEV_MODE: true } });
assert.equal(r.podeEnviar, false);
});
test('DEV_MODE=true bloqueia mesmo manual+FORCE_SEND=true (sem override possivel)', () => {
const r = canSend({ triggerType: 'manual', config: { ...BASE, DEV_MODE: true, FORCE_SEND: true } });
assert.equal(r.podeEnviar, false);
});
test('SEND_ENABLED=false nunca envia (scheduled)', () => {
const r = canSend({ triggerType: 'scheduled', config: { ...BASE, SEND_ENABLED: false } });
assert.equal(r.podeEnviar, false);
});
test('SEND_ENABLED=false nunca envia (manual + FORCE_SEND=true)', () => {
const r = canSend({ triggerType: 'manual', config: { ...BASE, SEND_ENABLED: false, FORCE_SEND: true } });
assert.equal(r.podeEnviar, false);
});
test('scheduled + producao (SEND_ENABLED=true, SHADOW_MODE=false, DEV_MODE=false) pode enviar', () => {
const r = canSend({ triggerType: 'scheduled', config: { ...BASE } });
assert.equal(r.podeEnviar, true);
});
test('manual + FORCE_SEND=false nunca envia', () => {
const r = canSend({ triggerType: 'manual', config: { ...BASE, FORCE_SEND: false } });
assert.equal(r.podeEnviar, false);
});
test('manual + FORCE_SEND=true pode enviar (sujeito a idempotencia a parte)', () => {
const r = canSend({ triggerType: 'manual', config: { ...BASE, FORCE_SEND: true } });
assert.equal(r.podeEnviar, true);
});
test('motivo sempre presente e legivel, mesmo quando pode enviar', () => {
const r = canSend({ triggerType: 'scheduled', config: { ...BASE } });
assert.equal(typeof r.motivo, 'string');
assert.ok(r.motivo.length > 0);
});
// --- Hash / chave / duplicata (secao 13) ---
test('computeMessageHash e deterministico para o mesmo texto', () => {
const a = computeMessageHash('Resumo diario: 11 abertos, 11 fechados');
const b = computeMessageHash('Resumo diario: 11 abertos, 11 fechados');
assert.equal(a, b);
});
test('computeMessageHash muda quando o texto muda', () => {
const a = computeMessageHash('Resumo diario: 11 abertos');
const b = computeMessageHash('Resumo diario: 12 abertos');
assert.notEqual(a, b);
});
test('computeMessageHash lida com texto vazio/nulo sem lancar erro', () => {
assert.doesNotThrow(() => computeMessageHash(null));
assert.doesNotThrow(() => computeMessageHash(undefined));
assert.doesNotThrow(() => computeMessageHash(''));
});
test('buildSendKey combina os 5 campos da chave logica de idempotencia', () => {
const key = buildSendKey({
reportType: 'diario',
periodStart: '2026-08-24',
periodEnd: '2026-08-24',
destination: 'C0BJNBTPFT5',
messageHash: 'abc123',
});
assert.equal(key, 'diario|2026-08-24|2026-08-24|C0BJNBTPFT5|abc123');
});
test('isDuplicate detecta uma chave ja registrada com status=success', () => {
const key = 'diario|2026-08-24|2026-08-24|C0BJNBTPFT5|abc123';
const rows = [{ send_key: key, status: 'success' }];
assert.equal(isDuplicate(rows, key), true);
});
test('isDuplicate ignora registros com status diferente de success', () => {
const key = 'diario|2026-08-24|2026-08-24|C0BJNBTPFT5|abc123';
const rows = [{ send_key: key, status: 'failed' }, { send_key: key, status: 'blocked_duplicate' }];
assert.equal(isDuplicate(rows, key), false);
});
test('isDuplicate retorna false quando a lista de logs esta vazia ou ausente', () => {
assert.equal(isDuplicate([], 'qualquer'), false);
assert.equal(isDuplicate(null, 'qualquer'), false);
assert.equal(isDuplicate(undefined, 'qualquer'), false);
});
test('isDuplicate nao confunde chaves de periodos diferentes', () => {
const rows = [{ send_key: 'diario|2026-08-24|2026-08-24|C0BJNBTPFT5|abc123', status: 'success' }];
assert.equal(isDuplicate(rows, 'diario|2026-08-25|2026-08-25|C0BJNBTPFT5|abc123'), false);
});