import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { hasCampaignsPausedByRestart, notifyInitialReconnectFailure, notifyStartupState, notifyWhatsAppTransition, startInitialWhatsAppReconnect } from '../src/alerts/runtime-alerts.mjs'; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); test('startup alerts notify restart pause and QR requirement once', async () => { const alerts = []; const alerter = { alert: async (key, text) => alerts.push({ key, text }) }; await notifyStartupState({ alerter, whatsappStatus: 'QR_REQUIRED', campaignsPausedByRestart: true }); assert.deepEqual(alerts.map((alert) => alert.key), ['campaigns-paused-restart', 'whatsapp-qr']); }); test('startup alert delivery failures are logged and do not reject startup', async () => { const logs = []; const alerter = { alert: async () => { throw new Error('telegram token SECRET failed'); } }; await assert.doesNotReject(() => notifyStartupState({ alerter, whatsappStatus: 'QR_REQUIRED', campaignsPausedByRestart: true, log: (message) => logs.push(message) })); assert.equal(logs.length, 2); assert.match(logs[0], /telegram: alerta campaigns-paused-restart falhou/); assert.match(logs[1], /telegram: alerta whatsapp-qr falhou/); assert.doesNotMatch(logs.join('\n'), /SECRET|token/i); }); test('initial WhatsApp reconnect failure alerts operator without real send', async () => { const alerts = []; const logs = []; let resolveAlert; const alerted = new Promise((resolve) => { resolveAlert = resolve; }); const alerter = { alert: async (key, text) => { alerts.push({ key, text }); resolveAlert(); } }; const started = await startInitialWhatsAppReconnect({ hasSession: async () => true, whatsapp: { connect: async () => { throw new Error('falha fake'); } }, alerter, log: (message) => logs.push(message) }); await alerted; assert.equal(started, true); assert.deepEqual(alerts.map((alert) => alert.key), ['whatsapp-initial-reconnect-failed']); assert.match(logs[0], /reconexao inicial falhou: falha fake/); }); test('initial reconnect alert delivery failures are logged and do not reject', async () => { const logs = []; const alerter = { alert: async () => { throw new Error('telegram token SECRET failed'); } }; await assert.doesNotReject(() => notifyInitialReconnectFailure({ alerter, error: new Error('falha fake'), log: (message) => logs.push(message) })); assert.match(logs[0], /reconexao inicial falhou: falha fake/); assert.match(logs[1], /telegram: alerta whatsapp-initial-reconnect-failed falhou/); assert.doesNotMatch(logs.join('\n'), /SECRET|token/i); }); test('WhatsApp transition alerts when leaving READY and requiring QR', async () => { const alerts = []; const alerter = { alert: async (key, text) => alerts.push({ key, text }) }; await notifyWhatsAppTransition({ alerter, previousState: 'READY', nextState: 'QR_REQUIRED' }); assert.deepEqual(alerts.map((alert) => alert.key), ['whatsapp-left-ready', 'whatsapp-qr']); }); test('restart pause detector only matches paused campaigns with server_restart reason', () => { let sql; const db = { prepare: (statement) => { sql = statement; return { get: () => ({ found: 1 }) }; } }; assert.equal(hasCampaignsPausedByRestart(db), true); assert.match(sql, /status = 'paused'/); assert.match(sql, /pause_reason = 'server_restart'/); }); test('server startup does not await Telegram startup alert delivery before listen', async () => { const source = await fs.readFile(path.join(root, 'scripts', 'servidor.mjs'), 'utf8'); assert.doesNotMatch(source, /await notifyStartupState/); assert.match(source, /void notifyStartupState\(\{[^}]*log/s); assert.ok(source.indexOf('notifyStartupState') < source.indexOf('server.listen')); });