'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 report_type + periodo + destino (SEM message_hash)', () => { const key = buildSendKey({ reportType: 'diario', periodStart: '2026-08-24', periodEnd: '2026-08-24', destination: 'C0BJNBTPFT5', }); assert.equal(key, 'diario|2026-08-24|2026-08-24|C0BJNBTPFT5'); }); test('regressao: send_key IGUAL mesmo com message_hash diferente (bug real de 2026-08-25 — 2 mensagens reais enviadas ao Slack porque a IA nao e deterministica e mudou o texto entre duas chamadas do MESMO periodo, gerando hashes diferentes e escapando da deteccao de duplicata)', () => { const base = { reportType: 'diario', periodStart: '2026-08-24', periodEnd: '2026-08-24', destination: 'C0BJNBTPFT5' }; const keyTentativa1 = buildSendKey(base); const keyTentativa2 = buildSendKey(base); assert.equal(keyTentativa1, keyTentativa2); const rows = [{ send_key: keyTentativa1, status: 'success' }]; assert.equal(isDuplicate(rows, keyTentativa2), true); }); 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); });