feat: añadir reintentos en fondo para webhook y tests

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
borja 4 weeks ago
parent 15c7d638aa
commit ecff2a5643

@ -9,22 +9,20 @@ import { MaintenanceService } from '../services/maintenance';
export async function startServices(_db: Database): Promise<void> { export async function startServices(_db: Database): Promise<void> {
// Exponer la DB globalmente vía locator para servicios que lo usen. // Exponer la DB globalmente vía locator para servicios que lo usen.
try { setDb(_db); } catch {} try { setDb(_db); } catch {}
await WebhookManager.registerWebhook();
// Add small delay to allow webhook to propagate // Iniciar aseguramiento automático del webhook en background (reintentos con backoff)
await new Promise(resolve => setTimeout(resolve, 1000)); try {
const isActive = await WebhookManager.verifyWebhook(); WebhookManager.startAutoEnsure();
if (!isActive) { } catch (e) {
console.error('❌ Webhook verification failed - retrying in 2 seconds...'); console.error('⚠️ Failed to start webhook auto-ensure loop:', e);
await new Promise(resolve => setTimeout(resolve, 2000));
const isActiveRetry = await WebhookManager.verifyWebhook();
if (!isActiveRetry) {
console.error('❌ Webhook verification failed after retry');
process.exit(1);
}
} }
// Initialize groups - critical for operation // Initialize groups - critical for operation (no abortar si falla)
await GroupSyncService.checkInitialGroups(); try {
await GroupSyncService.checkInitialGroups();
} catch (e) {
console.error('⚠️ Initial groups check failed (will rely on schedulers to recover):', e);
}
// Start groups scheduler (periodic sync of groups) // Start groups scheduler (periodic sync of groups)
try { try {
@ -73,6 +71,9 @@ export async function startServices(_db: Database): Promise<void> {
} }
export function stopServices(): void { export function stopServices(): void {
try {
WebhookManager.stopAutoEnsure();
} catch {}
try { try {
ResponseQueue.stopCleanupScheduler(); ResponseQueue.stopCleanupScheduler();
} catch {} } catch {}

@ -80,4 +80,74 @@ describe('WebhookManager', () => {
} }
}).toThrow(); }).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;
}
});
}); });

Loading…
Cancel
Save