From 373692ac657eb416435463f08f527975bfc72dff Mon Sep 17 00:00:00 2001 From: jacinli Date: Sun, 5 Apr 2026 14:05:59 +0800 Subject: [PATCH] fix: parse string boolean values when updating subscriptions --- app/subscriptions.py | 15 +++++++- app/tests/test_subscriptions.py | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/app/subscriptions.py b/app/subscriptions.py index d2b3521..4db28c8 100644 --- a/app/subscriptions.py +++ b/app/subscriptions.py @@ -231,6 +231,19 @@ def _subscription_from_record(record: Any) -> Optional[SubscriptionInfo]: return None +def _coerce_bool(value: Any) -> bool: + """Accept JSON booleans and common string forms used by API clients.""" + if isinstance(value, bool): + return value + if isinstance(value, str): + lowered = value.strip().lower() + if lowered in {"true", "1", "on"}: + return True + if lowered in {"false", "0", "off"}: + return False + raise ValueError("enabled must be a boolean") + + class SubscriptionNotifier: """Hook for Socket.IO / UI updates.""" @@ -546,7 +559,7 @@ class SubscriptionManager: old_enabled = sub.enabled if "enabled" in changes: - sub.enabled = bool(changes["enabled"]) + sub.enabled = _coerce_bool(changes["enabled"]) if "check_interval_minutes" in changes: sub.check_interval_minutes = max(1, int(changes["check_interval_minutes"])) if "name" in changes and changes["name"]: diff --git a/app/tests/test_subscriptions.py b/app/tests/test_subscriptions.py index 9fc465b..e3f6229 100644 --- a/app/tests/test_subscriptions.py +++ b/app/tests/test_subscriptions.py @@ -386,6 +386,73 @@ class SubscriptionPersistenceTests(unittest.IsolatedAsyncioTestCase): self.assertEqual(sub.seen_ids[:2], ["v2", "v1"]) self.assertEqual([entry["webpage_url"] for entry, _, _ in queue.entries], ["https://example.com/v2"]) + async def test_update_subscription_parses_string_false_enabled(self): + with tempfile.TemporaryDirectory() as tmp: + queue = _Queue() + mgr = SubscriptionManager(_Config(tmp), queue, _Notifier()) + + with patch( + "subscriptions.extract_flat_playlist", + return_value=( + {"_type": "channel", "title": "Channel"}, + [{"id": "v1", "title": "One", "webpage_url": "https://example.com/v1"}], + ), + ): + result = await mgr.add_subscription( + "https://example.com/channel", + check_interval_minutes=60, + download_type="video", + codec="auto", + format="any", + quality="best", + folder="", + custom_name_prefix="", + auto_start=True, + playlist_item_limit=0, + split_by_chapters=False, + chapter_template="", + subtitle_language="en", + subtitle_mode="prefer_manual", + ) + + sub_id = result["subscription"]["id"] + update = await mgr.update_subscription(sub_id, {"enabled": "false"}) + self.assertEqual(update["status"], "ok") + self.assertFalse(mgr.list_all()[0].enabled) + + async def test_update_subscription_rejects_invalid_enabled_value(self): + with tempfile.TemporaryDirectory() as tmp: + queue = _Queue() + mgr = SubscriptionManager(_Config(tmp), queue, _Notifier()) + + with patch( + "subscriptions.extract_flat_playlist", + return_value=( + {"_type": "channel", "title": "Channel"}, + [{"id": "v1", "title": "One", "webpage_url": "https://example.com/v1"}], + ), + ): + result = await mgr.add_subscription( + "https://example.com/channel", + check_interval_minutes=60, + download_type="video", + codec="auto", + format="any", + quality="best", + folder="", + custom_name_prefix="", + auto_start=True, + playlist_item_limit=0, + split_by_chapters=False, + chapter_template="", + subtitle_language="en", + subtitle_mode="prefer_manual", + ) + + sub_id = result["subscription"]["id"] + with self.assertRaises(ValueError): + await mgr.update_subscription(sub_id, {"enabled": "maybe"}) + class ExtractFlatPlaylistTests(unittest.TestCase): def test_descends_one_level_when_root_entries_are_nested_collections(self): responses = iter(