diff --git a/core/automation_engine.py b/core/automation_engine.py index f0b90f4a..77a95bca 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -325,20 +325,21 @@ class AutomationEngine: self._fix_video_scan_default() def _fix_video_scan_default(self): - """One-time cleanup: remove the standalone 'Scan Video Library' system - automation — it's superseded by the post-download chain (Auto-Scan Video - After Downloads → Auto-Update Video Database After Scan). Only deletes the - SYSTEM row; a user can still build their own scan automation from the action. - Flag-guarded so it runs once.""" + """Remove the obsolete standalone 'Scan Video Library' SYSTEM automation — it's + superseded by the post-download chain (Auto-Scan Video After Downloads → + Auto-Update Video Database After Scan). + + ``get_system_automation_by_action`` matches ONLY a system-seeded row + (is_system=1), so a user's own scan automation is never touched. Idempotent — + safe to run on every startup; once the row is gone the lookup returns None and + it no-ops. (No flag guard: the old one could latch True without ever deleting, + which is exactly why the row survived earlier 'cleanups'.)""" try: - from config.settings import config_manager - if config_manager.get('video_scan_cleanup_v3', False): - return auto = self.db.get_system_automation_by_action('video_scan_library') - if auto and auto.get('is_system'): + if auto: self.db.delete_automation(auto['id']) - logger.info("Removed superseded 'Scan Video Library' system automation") - config_manager.set('video_scan_cleanup_v3', True) + logger.info("Removed superseded 'Scan Video Library' system automation (id=%s)", + auto.get('id')) except Exception: logger.exception("video scan cleanup failed") diff --git a/tests/automation/test_engine_schedule_integration.py b/tests/automation/test_engine_schedule_integration.py index d742155e..7eba1f03 100644 --- a/tests/automation/test_engine_schedule_integration.py +++ b/tests/automation/test_engine_schedule_integration.py @@ -438,21 +438,18 @@ def test_ensure_system_automations_seeds_video_with_owned_by_and_mode(): assert json.loads(created['scan_library']['action_config']) == {} -def test_video_scan_library_system_automation_is_cleaned_up(monkeypatch): - """The old standalone 'Scan Video Library' system automation is deleted once - (superseded by the post-download chain). Guard flag stops it re-running.""" - class _FakeCfg: - def __init__(self): self._d = {} - def get(self, k, default=None): return self._d.get(k, default) - def set(self, k, v): self._d[k] = v - monkeypatch.setattr('config.settings.config_manager', _FakeCfg()) - +def test_video_scan_library_system_automation_is_cleaned_up(): + """The obsolete standalone 'Scan Video Library' system automation is deleted when + present (superseded by the post-download chain). No stuck flag — it just keys off + the lookup, so once the row is gone it no-ops.""" + # present → deleted (matches only the is_system-seeded row) db = MagicMock() db.get_system_automation_by_action.return_value = {'id': 99, 'is_system': 1} - engine = AutomationEngine(db) + AutomationEngine(db)._fix_video_scan_default() + db.delete_automation.assert_any_call(99) - engine._fix_video_scan_default() - db.delete_automation.assert_called_once_with(99) - - engine._fix_video_scan_default() # flag set → no second delete - db.delete_automation.assert_called_once() + # absent (already gone) → idempotent no-op, never errors or deletes + db2 = MagicMock() + db2.get_system_automation_by_action.return_value = None + AutomationEngine(db2)._fix_video_scan_default() + db2.delete_automation.assert_not_called()