import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test'; import { WebhookManager } from '../../../src/services/webhook-manager'; describe('WebhookManager', () => { const envBackup = process.env; beforeEach(() => { process.env = { ...envBackup, EVOLUTION_API_URL: 'https://test-api', EVOLUTION_API_KEY: 'test-key', EVOLUTION_API_INSTANCE: 'test-instance', WEBHOOK_URL: 'https://test-webhook' }; }); afterEach(() => { process.env = envBackup; }); test('should warn about missing port in internal URLs', () => { process.env.WEBHOOK_URL = 'http://srv-captain--taskbot/webhook'; const consoleSpy = mock(() => {}); console.warn = consoleSpy; WebhookManager['validateConfig'](); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining('missing port number') ); }); test('should accept internal URLs with ports', () => { process.env.WEBHOOK_URL = 'http://srv-captain--taskbot:3007/webhook'; const consoleSpy = mock(() => {}); console.warn = consoleSpy; WebhookManager['validateConfig'](); expect(consoleSpy).not.toHaveBeenCalled(); }); test('should create correct config structure', () => { const config = WebhookManager['getConfig'](); expect(config).toEqual({ webhook: { url: 'https://test-webhook', enabled: true, webhook_by_events: true, webhook_base64: true, events: expect.any(Array) } }); }); test('should validate successful response', () => { const validResponse = { id: 'test-id', url: 'https://test-webhook', enabled: true, events: ['APPLICATION_STARTUP'] }; // Mock the private validateResponse method const mockValidate = mock(() => {}); WebhookManager['validateResponse'] = mockValidate; WebhookManager['validateResponse'](validResponse); expect(mockValidate).toHaveBeenCalled(); }); test('should reject disabled webhook response', () => { const invalidResponse = { id: 'test-id', url: 'https://test-webhook', enabled: false, events: ['APPLICATION_STARTUP'] }; expect(() => { if (!invalidResponse.enabled) { throw new Error('Webhook not enabled'); } }).toThrow(); }); test('should retry registration in background until success', async () => { // Acelerar reintentos process.env.WEBHOOK_RETRY_INITIAL_DELAY_MS = '10'; process.env.WEBHOOK_RETRY_MAX_DELAY_MS = '20'; process.env.WEBHOOK_RETRY_DEGRADED_INTERVAL_MS = '50'; const originalFetch: any = globalThis.fetch; let setCalls = 0; const fetchSpy = mock(async (input: any, init?: any) => { const url = typeof input === 'string' ? input : input.url; // Self-test of our own webhook endpoint if (url === process.env.WEBHOOK_URL) { return new Response('ok', { status: 200, headers: { 'Content-Type': 'application/json' } }); } // Register webhook endpoint (first fails, then succeeds) if (typeof url === 'string' && url.includes('/webhook/set/')) { setCalls++; if (setCalls < 2) { return new Response('unavailable', { status: 503, statusText: 'Service Unavailable' }); } const body = JSON.stringify({ id: 'test-id', url: process.env.WEBHOOK_URL, enabled: true, events: ['APPLICATION_STARTUP'] }); return new Response(body, { status: 200, headers: { 'Content-Type': 'application/json' } }); } // Verify webhook endpoint (returns enabled) if (typeof url === 'string' && url.includes('/webhook/find/')) { const body = JSON.stringify({ enabled: true, url: process.env.WEBHOOK_URL, events: ['APPLICATION_STARTUP'] }); return new Response(body, { status: 200, headers: { 'Content-Type': 'application/json' } }); } return new Response('not found', { status: 404 }); }); // @ts-ignore globalThis.fetch = fetchSpy; try { // Asegurar que no haya un lazo previo corriendo try { (WebhookManager as any).stopAutoEnsure(); } catch {} WebhookManager['startAutoEnsure'](); // Esperar a que el lazo logre activar el webhook (o agote timeout) const deadline = Date.now() + 2000; while (WebhookManager['autoEnsureActive'] && Date.now() < deadline) { await new Promise(res => setTimeout(res, 20)); } expect(WebhookManager['autoEnsureActive']).toBe(false); expect(fetchSpy).toHaveBeenCalled(); expect(setCalls).toBeGreaterThan(1); } finally { try { WebhookManager['stopAutoEnsure'](); } catch {} // @ts-ignore globalThis.fetch = originalFetch; } }); });