video automations: migrate the Download->Process rename in seeded DBs

bug: renaming the action_types only changed code — a db already seeded under the
old names kept the stale rows (the seeder only CREATES missing ones, never removes
or renames). so on restart you got BOTH 'Auto-Download Movie Wishlist' (orphaned,
dead action) AND 'Auto-Process Movie Wishlist'.

add _fix_wishlist_processor_rename (runs in ensure_system_automations like the
other _fix_* migrations): deletes the orphaned video_download_movie/episode_wishlist
system rows (clearing is_system first, since delete_automation guards system rows)
and renames the youtube row in place (its action_type was unchanged). idempotent.
tested.
This commit is contained in:
BoulderBadgeDad 2026-06-25 22:33:03 -07:00
parent 7d7d242e5d
commit 10895a7c16
2 changed files with 62 additions and 0 deletions

View file

@ -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.

View file

@ -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)