import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { createTelegramAlerter } from '../src/alerts/telegram.mjs'; import { loadDotEnv, telegramConfig } from '../src/env.mjs'; test('telegram alert skips when disabled or missing config', async () => { const calls = []; const alerter = createTelegramAlerter({ enabled: false, token: '', chatId: '', fetchImpl: async (...args) => calls.push(args) }); assert.deepEqual(await alerter.alert('qr', 'QR necessario'), { sent: false, reason: 'disabled' }); assert.equal(calls.length, 0); const missingConfigAlerter = createTelegramAlerter({ enabled: true, token: '', chatId: '', fetchImpl: async (...args) => calls.push(args) }); assert.deepEqual(await missingConfigAlerter.alert('qr', 'QR necessario'), { sent: false, reason: 'not_configured' }); assert.equal(calls.length, 0); }); test('telegram alert sends message without leaking token to text', async () => { const calls = []; const fetchImpl = async (url, options) => { calls.push({ url, options }); return { ok: true, json: async () => ({ ok: true }) }; }; const alerter = createTelegramAlerter({ enabled: true, token: '123:SECRET', chatId: '456', fetchImpl }); assert.deepEqual(await alerter.alert('qr', 'WhatsApp precisa de QR'), { sent: true }); assert.equal(calls.length, 1); assert.match(calls[0].url, /sendMessage$/); const payload = JSON.parse(calls[0].options.body); assert.equal(payload.chat_id, '456'); assert.equal(payload.text, 'WhatsApp precisa de QR'); assert.doesNotMatch(payload.text, /SECRET/); }); test('telegram alert deduplicates per key within cooldown', async () => { let current = 1000; const calls = []; const fetchImpl = async (...args) => { calls.push(args); return { ok: true, json: async () => ({ ok: true }) }; }; const alerter = createTelegramAlerter({ enabled: true, token: 't', chatId: 'c', fetchImpl, now: () => current, cooldownMs: 1000 }); assert.equal((await alerter.alert('same', 'A')).sent, true); assert.deepEqual(await alerter.alert('same', 'A again'), { sent: false, reason: 'cooldown' }); current = 2501; assert.equal((await alerter.alert('same', 'A later')).sent, true); assert.equal(calls.length, 2); }); test('telegram alert sends first message when clock starts at zero', async () => { const calls = []; const fetchImpl = async (...args) => { calls.push(args); return { ok: true, json: async () => ({ ok: true }) }; }; const alerter = createTelegramAlerter({ enabled: true, token: 't', chatId: 'c', fetchImpl, now: () => 0, cooldownMs: 1000 }); assert.equal((await alerter.alert('zero', 'First')).sent, true); assert.deepEqual(await alerter.alert('zero', 'Second'), { sent: false, reason: 'cooldown' }); assert.equal(calls.length, 1); }); test('telegram config reflects values loaded from dotenv', async () => { const keys = ['TELEGRAM_ALERTS_ENABLED', 'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID']; const previous = Object.fromEntries(keys.map((key) => [key, process.env[key]])); const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'telegram-env-')); try { for (const key of keys) delete process.env[key]; await fs.writeFile( path.join(rootDir, '.env'), 'TELEGRAM_ALERTS_ENABLED=true\nTELEGRAM_BOT_TOKEN=123:SECRET\nTELEGRAM_CHAT_ID=456\n' ); await loadDotEnv(rootDir); assert.deepEqual(telegramConfig(), { enabled: true, botToken: '123:SECRET', chatId: '456' }); } finally { for (const key of keys) { if (previous[key] === undefined) delete process.env[key]; else process.env[key] = previous[key]; } } });