Add Discord webhook refresh notifications
This commit is contained in:
+122
@@ -1090,6 +1090,8 @@ class ConfigSnapshotTests(unittest.TestCase):
|
||||
"watchlist_auto_refresh_enabled": False,
|
||||
"watchlist_auto_refresh_minutes": 60,
|
||||
"watchlist_refresh_delay_seconds": 5,
|
||||
"discord_webhook_url": "",
|
||||
"discord_webhook_events": [],
|
||||
}
|
||||
try:
|
||||
snapshot = APP.get_config_snapshot()
|
||||
@@ -1103,6 +1105,8 @@ class ConfigSnapshotTests(unittest.TestCase):
|
||||
self.assertFalse(config["watchlist_auto_refresh_enabled"])
|
||||
self.assertEqual(config["watchlist_auto_refresh_minutes"], 60)
|
||||
self.assertEqual(config["watchlist_refresh_delay_seconds"], 5)
|
||||
self.assertEqual(config["discord_webhook_url"], "")
|
||||
self.assertEqual(config["discord_webhook_events"], [])
|
||||
|
||||
def test_normalize_config_parses_watchlist_schedule_fields(self):
|
||||
config = APP.normalize_config(
|
||||
@@ -1116,6 +1120,44 @@ class ConfigSnapshotTests(unittest.TestCase):
|
||||
self.assertEqual(config["watchlist_auto_refresh_minutes"], 15)
|
||||
self.assertEqual(config["watchlist_refresh_delay_seconds"], 7)
|
||||
|
||||
def test_normalize_config_filters_discord_webhook_events(self):
|
||||
config = APP.normalize_config(
|
||||
{
|
||||
"discord_webhook_url": " https://discord.example/webhook ",
|
||||
"discord_webhook_events": [
|
||||
"watchlist_refresh_new_episodes",
|
||||
"runtime_error",
|
||||
"runtime_error",
|
||||
"nope",
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(config["discord_webhook_url"], "https://discord.example/webhook")
|
||||
self.assertEqual(
|
||||
config["discord_webhook_events"],
|
||||
["watchlist_refresh_new_episodes", "runtime_error"],
|
||||
)
|
||||
|
||||
def test_test_discord_webhook_config_requires_url(self):
|
||||
with self.assertRaises(ValueError):
|
||||
APP.test_discord_webhook_config({"discord_webhook_url": ""})
|
||||
|
||||
def test_test_discord_webhook_config_sends_test_event(self):
|
||||
with mock.patch.object(APP, "send_discord_webhook_event") as send_webhook:
|
||||
result = APP.test_discord_webhook_config(
|
||||
{
|
||||
"discord_webhook_url": "https://discord.example/webhook",
|
||||
"discord_webhook_events": [],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(result["message"], "Discord webhook test notification sent.")
|
||||
send_webhook.assert_called_once()
|
||||
config, event_name = send_webhook.call_args.args[:2]
|
||||
self.assertEqual(config["discord_webhook_url"], "https://discord.example/webhook")
|
||||
self.assertEqual(event_name, "test")
|
||||
self.assertTrue(send_webhook.call_args.kwargs["force"])
|
||||
|
||||
|
||||
class WatchlistAutoRefreshSchedulerTests(unittest.TestCase):
|
||||
def test_tick_waits_then_triggers_refresh(self):
|
||||
@@ -1153,6 +1195,57 @@ class WatchlistAutoRefreshSchedulerTests(unittest.TestCase):
|
||||
self.assertEqual(wait_mock.call_count, 2)
|
||||
wait_mock.assert_called_with(5)
|
||||
|
||||
def test_refresh_job_sends_new_episode_and_failure_notifications(self):
|
||||
class FakeStore:
|
||||
def all_show_ids(self):
|
||||
return ["show-a", "show-b"]
|
||||
|
||||
def refresh(self, show_id, include_thumbnail=True):
|
||||
if show_id == "show-a":
|
||||
return {
|
||||
"show_id": "show-a",
|
||||
"title": "Show A",
|
||||
"status": "updated",
|
||||
"had_new_episodes": True,
|
||||
"sub_count": 12,
|
||||
"dub_count": 4,
|
||||
"sub_delta": 1,
|
||||
"dub_delta": 0,
|
||||
"expected_count": 12,
|
||||
"airing_status": "Finished",
|
||||
"thumbnail_path": None,
|
||||
}
|
||||
return {
|
||||
"show_id": "show-b",
|
||||
"title": "Show B",
|
||||
"status": "error",
|
||||
"status_message": "Could not refresh episodes: boom",
|
||||
"had_new_episodes": False,
|
||||
}
|
||||
|
||||
service = APP.WatchlistRefreshJobs(
|
||||
FakeStore(),
|
||||
config_getter=lambda: {
|
||||
"discord_webhook_url": "https://discord.example/webhook",
|
||||
"discord_webhook_events": [
|
||||
"watchlist_refresh_new_episodes",
|
||||
"watchlist_refresh_failed",
|
||||
],
|
||||
},
|
||||
start_worker=False,
|
||||
)
|
||||
job = service.start()["job"]
|
||||
job = service._next_pending()
|
||||
|
||||
with mock.patch.object(queue_jobs, "send_discord_webhook_event") as send_webhook:
|
||||
service._run_job(job)
|
||||
|
||||
self.assertEqual(send_webhook.call_count, 2)
|
||||
self.assertEqual(send_webhook.call_args_list[0].args[1], "watchlist_refresh_new_episodes")
|
||||
self.assertEqual(send_webhook.call_args_list[0].args[2]["items"][0]["title"], "Show A")
|
||||
self.assertEqual(send_webhook.call_args_list[1].args[1], "watchlist_refresh_failed")
|
||||
self.assertEqual(send_webhook.call_args_list[1].args[2]["items"][0]["title"], "Show B")
|
||||
|
||||
def test_tick_resets_timer_when_schedule_changes(self):
|
||||
config = {
|
||||
"watchlist_auto_refresh_enabled": True,
|
||||
@@ -1238,6 +1331,8 @@ class StartupBehaviorTests(unittest.TestCase):
|
||||
"watchlist_auto_refresh_enabled": False,
|
||||
"watchlist_auto_refresh_minutes": 60,
|
||||
"watchlist_refresh_delay_seconds": 5,
|
||||
"discord_webhook_url": "",
|
||||
"discord_webhook_events": [],
|
||||
}
|
||||
APP.WATCHLIST_AUTO_REFRESH = mock.Mock()
|
||||
saved = APP.save_runtime_config(
|
||||
@@ -1248,6 +1343,8 @@ class StartupBehaviorTests(unittest.TestCase):
|
||||
"watchlist_auto_refresh_enabled": True,
|
||||
"watchlist_auto_refresh_minutes": 15,
|
||||
"watchlist_refresh_delay_seconds": 8,
|
||||
"discord_webhook_url": "https://discord.example/webhook",
|
||||
"discord_webhook_events": ["runtime_error"],
|
||||
}
|
||||
)
|
||||
self.assertEqual(saved["mode"], "dub")
|
||||
@@ -1256,6 +1353,8 @@ class StartupBehaviorTests(unittest.TestCase):
|
||||
self.assertTrue(APP.CONFIG["watchlist_auto_refresh_enabled"])
|
||||
self.assertEqual(APP.CONFIG["watchlist_auto_refresh_minutes"], 15)
|
||||
self.assertEqual(APP.CONFIG["watchlist_refresh_delay_seconds"], 8)
|
||||
self.assertEqual(APP.CONFIG["discord_webhook_url"], "https://discord.example/webhook")
|
||||
self.assertEqual(APP.CONFIG["discord_webhook_events"], ["runtime_error"])
|
||||
APP.WATCHLIST_AUTO_REFRESH.notify_config_changed.assert_called_once_with()
|
||||
finally:
|
||||
APP.CONFIG = original
|
||||
@@ -1299,6 +1398,29 @@ class HandlerRouteTests(unittest.TestCase):
|
||||
self.assertEqual(handler.json_payload["watchlist_auto_refresh_minutes"], 25)
|
||||
self.assertEqual(handler.json_payload["watchlist_refresh_delay_seconds"], 9)
|
||||
|
||||
def test_config_post_accepts_discord_webhook_fields(self):
|
||||
body = b'{"download_dir":"/tmp/example","mode":"sub","quality":"best","discord_webhook_url":"https://discord.example/webhook","discord_webhook_events":["runtime_error","watchlist_refresh_failed","bad"]}'
|
||||
handler = DummyHandler("/api/config", body=body, content_length=len(body))
|
||||
APP.Handler.do_POST(handler)
|
||||
self.assertEqual(handler.json_status, HTTPStatus.OK)
|
||||
self.assertEqual(handler.json_payload["discord_webhook_url"], "https://discord.example/webhook")
|
||||
self.assertEqual(
|
||||
handler.json_payload["discord_webhook_events"],
|
||||
["runtime_error", "watchlist_refresh_failed"],
|
||||
)
|
||||
|
||||
def test_config_webhook_test_route_uses_unsaved_payload(self):
|
||||
body = b'{"discord_webhook_url":"https://discord.example/webhook","discord_webhook_events":["runtime_error"]}'
|
||||
handler = DummyHandler("/api/config/webhook/test", body=body, content_length=len(body))
|
||||
test_webhook = mock.Mock(return_value={"message": "ok"})
|
||||
handler.handler_context = mock.Mock(test_discord_webhook_config=test_webhook)
|
||||
APP.Handler.do_POST(handler)
|
||||
self.assertEqual(handler.json_status, HTTPStatus.OK)
|
||||
self.assertEqual(handler.json_payload["message"], "ok")
|
||||
test_webhook.assert_called_once_with(
|
||||
{"discord_webhook_url": "https://discord.example/webhook", "discord_webhook_events": ["runtime_error"]}
|
||||
)
|
||||
|
||||
def test_body_json_rejects_oversized_request(self):
|
||||
handler = DummyHandler("/api/watchlist/upload-thumbnail", body=b"{}", content_length=APP.MAX_JSON_BODY_BYTES + 1)
|
||||
with self.assertRaises(APP.HttpError) as ctx:
|
||||
|
||||
Reference in New Issue
Block a user