From 06818dbf2949adb6d03ac6ef45dddf565a59b7b8 Mon Sep 17 00:00:00 2001 From: Gronod Date: Thu, 28 May 2026 18:11:45 +0100 Subject: [PATCH 1/3] fix(webhooks): skip replay protection for Test events (closes #71) --- server/routes/webhook.js | 10 ++++++++-- tests/integration/webhook.test.js | 32 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/server/routes/webhook.js b/server/routes/webhook.js index 4629850..dd042ef 100644 --- a/server/routes/webhook.js +++ b/server/routes/webhook.js @@ -476,7 +476,10 @@ router.post('/sonarr', webhookLimiter, (req, res) => { // Content-aware replay key components (Issue #62) const contentId = req.body.downloadId || req.body.series?.id || null; - if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) { + // Skip replay protection for Test events + if (eventType === "Test") { + logToFile(`[Webhook] Sonarr Test event received — skipping replay protection`); + } else if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) { logToFile(`[Webhook] Sonarr duplicate event ignored: ${eventType} @ ${eventDate} (contentId=${contentId || 'none'})`); return res.status(200).json({ received: true, duplicate: true }); } @@ -637,7 +640,10 @@ router.post('/radarr', webhookLimiter, (req, res) => { // Content-aware replay key components (Issue #62) const contentId = req.body.downloadId || req.body.movie?.id || null; - if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) { + // Skip replay protection for Test events + if (eventType === "Test") { + logToFile(`[Webhook] Radarr Test event received — skipping replay protection`); + } else if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) { logToFile(`[Webhook] Radarr duplicate event ignored: ${eventType} @ ${eventDate} (contentId=${contentId || 'none'})`); return res.status(200).json({ received: true, duplicate: true }); } diff --git a/tests/integration/webhook.test.js b/tests/integration/webhook.test.js index 6baee0f..1910143 100644 --- a/tests/integration/webhook.test.js +++ b/tests/integration/webhook.test.js @@ -387,6 +387,38 @@ describe('Replay protection', () => { expect(first.body.duplicate).toBeUndefined(); expect(second.body.duplicate).toBeUndefined(); }); + + it('sonarr: Test events bypass replay protection and are not flagged as duplicates', async () => { + const app = makeApp(); + const payload = { + eventType: 'Test', + instanceName: 'Main Sonarr', + date: '2026-05-19T13:00:00.000Z' + }; + const first = await postSonarr(app, payload); + expect(first.status).toBe(200); + expect(first.body.duplicate).toBeUndefined(); + + const second = await postSonarr(app, payload); + expect(second.status).toBe(200); + expect(second.body.duplicate).toBeUndefined(); + }); + + it('radarr: Test events bypass replay protection and are not flagged as duplicates', async () => { + const app = makeApp(); + const payload = { + eventType: 'Test', + instanceName: 'Main Radarr', + date: '2026-05-19T13:00:00.000Z' + }; + const first = await postRadarr(app, payload); + expect(first.status).toBe(200); + expect(first.body.duplicate).toBeUndefined(); + + const second = await postRadarr(app, payload); + expect(second.status).toBe(200); + expect(second.body.duplicate).toBeUndefined(); + }); }); // --------------------------------------------------------------------------- From b9c8c0be8742e324d21cccb3dfeba644e2bdaa62 Mon Sep 17 00:00:00 2001 From: Gronod Date: Thu, 28 May 2026 18:12:53 +0100 Subject: [PATCH 2/3] style(ui): unify tab headers layout, typography, and icons (closes #72) --- public/index.html | 24 +++++++++---- public/style.css | 85 +++++++++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/public/index.html b/public/index.html index 8908e42..db79c07 100644 --- a/public/index.html +++ b/public/index.html @@ -170,8 +170,12 @@
-
-
+
+
+

📥 Active Downloads

+

Track and manage your active media downloads in real-time

+
+