diff --git a/core/automation_engine.py b/core/automation_engine.py index a8a7b9c9..ff4a35c5 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -453,6 +453,7 @@ class AutomationEngine: self._fix_video_scan_default() self._fix_airing_automation_schedule() self._fix_deep_scan_schedules() + self._fix_wishlist_processor_rename() def _fix_video_scan_default(self): """Remove the obsolete standalone 'Scan Video Library' SYSTEM automation — it's @@ -473,6 +474,31 @@ class AutomationEngine: except Exception: logger.exception("video scan cleanup failed") + def _fix_wishlist_processor_rename(self): + """Migrate the wishlist processors' 'Download' → 'Process' rename so a DB seeded + under the old names doesn't show stale duplicates. + + The movie/episode ACTIONS were renamed (``video_download_*`` → ``video_process_*``), + so the old seeded rows are now orphaned (dead action_type) while the new ones reseed + alongside them — delete the orphans. ``delete_automation`` refuses system rows, so + clear ``is_system`` first. The YouTube action kept its type but its label changed, so + rename that row in place. Idempotent — no-ops once the DB is clean.""" + try: + for dead in ('video_download_movie_wishlist', 'video_download_episode_wishlist'): + auto = self.db.get_system_automation_by_action(dead) + if auto: + self.db.update_automation(auto['id'], is_system=0) # lift the delete guard + self.db.delete_automation(auto['id']) + logger.info("Removed orphaned '%s' system automation (renamed to process, id=%s)", + auto.get('name'), auto.get('id')) + yt = self.db.get_system_automation_by_action('video_process_youtube_wishlist') + if yt and yt.get('name') == 'Auto-Download YouTube Wishlist': + self.db.update_automation(yt['id'], name='Auto-Process YouTube Wishlist') + logger.info("Renamed YouTube wishlist automation → 'Auto-Process YouTube Wishlist' (id=%s)", + yt.get('id')) + except Exception: + logger.exception("wishlist processor rename migration failed") + def _fix_airing_automation_schedule(self): """Migrate 'Auto-Wishlist Episodes Airing Today' from the old rolling 24h interval to a fixed daily 1am run. diff --git a/tests/automation/test_engine_schedule_integration.py b/tests/automation/test_engine_schedule_integration.py index 083c6edd..59961134 100644 --- a/tests/automation/test_engine_schedule_integration.py +++ b/tests/automation/test_engine_schedule_integration.py @@ -528,3 +528,39 @@ def test_deep_scan_migration_is_idempotent_and_safe_when_absent(): db2.get_system_automation_by_action.return_value = None AutomationEngine(db2)._fix_deep_scan_schedules() db2.update_automation.assert_not_called() + + +def test_wishlist_processor_rename_removes_orphans_and_renames_youtube(): + """The Download→Process rename: a DB seeded under the old names has orphaned + 'Auto-Download Movie/Episode Wishlist' rows (dead action_type) — they're deleted (after + lifting the is_system delete-guard); the YouTube row (action_type unchanged) is renamed + in place, never deleted.""" + rows = { + 'video_download_movie_wishlist': {'id': 10, 'is_system': 1, 'name': 'Auto-Download Movie Wishlist'}, + 'video_download_episode_wishlist': {'id': 11, 'is_system': 1, 'name': 'Auto-Download Episode Wishlist'}, + 'video_process_youtube_wishlist': {'id': 12, 'is_system': 1, 'name': 'Auto-Download YouTube Wishlist'}, + } + db = MagicMock() + db.get_system_automation_by_action.side_effect = lambda a: rows.get(a) + AutomationEngine(db)._fix_wishlist_processor_rename() + + db.delete_automation.assert_any_call(10) + db.delete_automation.assert_any_call(11) + # is_system=0 lifted on each orphan before deleting + cleared = {c.args[0] for c in db.update_automation.call_args_list if c.kwargs.get('is_system') == 0} + assert cleared == {10, 11} + # youtube renamed (in place), NOT deleted + renamed = [c for c in db.update_automation.call_args_list if c.kwargs.get('name')] + assert renamed and renamed[0].args[0] == 12 + assert renamed[0].kwargs['name'] == 'Auto-Process YouTube Wishlist' + assert all(c.args[0] != 12 for c in db.delete_automation.call_args_list) + + +def test_wishlist_processor_rename_is_idempotent_when_clean(): + # orphans already gone + youtube already renamed → no deletes, no rename + rows = {'video_process_youtube_wishlist': {'id': 12, 'is_system': 1, 'name': 'Auto-Process YouTube Wishlist'}} + db = MagicMock() + db.get_system_automation_by_action.side_effect = lambda a: rows.get(a) + AutomationEngine(db)._fix_wishlist_processor_rename() + db.delete_automation.assert_not_called() + assert not any(c.kwargs.get('name') for c in db.update_automation.call_args_list)