Video automations: Clean Search History twin (phase 2)
The music side's 'Clean Search History' automation now has a video counterpart so it appears on the video Automations page too. Distinct action_type video_clean_search_history (the system seeder keys on action_type, so reusing the music key would collide), registered to the SAME shared handler so behaviour is identical, scope='video' block (registry — users can build their own), and an owned_by='video' system automation on the same 1h cadence. The music action/row is untouched. Seam tests: video-scoped only (not on music), music action still music-scoped, exactly one video-owned system row at the 1h cadence, and it reuses the music handler. Registration contract (EXPECTED_ACTION_NAMES) updated.
This commit is contained in:
parent
b26f664326
commit
3ef0990ffc
5 changed files with 94 additions and 0 deletions
|
|
@ -265,6 +265,13 @@ ACTIONS: list[dict] = [
|
|||
"config_fields": [
|
||||
{"key": "prune_ended", "type": "checkbox", "label": "Also remove ended/canceled shows from the watchlist", "default": True}
|
||||
]},
|
||||
# Video twins of the music maintenance actions. Distinct action_type (the
|
||||
# system seeder keys on action_type, so a shared key would collide with the
|
||||
# music row) but the SAME shared handler — the cleanup operates on the common
|
||||
# download/search state, so behaviour stays identical. scope='video' keeps
|
||||
# them on the video builder only; the music blocks above are untouched.
|
||||
{"type": "video_clean_search_history", "label": "Clean Search History", "icon": "trash-2", "scope": "video",
|
||||
"description": "Remove old searches from Soulseek", "available": True},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,12 @@ def register_all(deps: AutomationDeps) -> None:
|
|||
'clean_search_history',
|
||||
lambda config: auto_clean_search_history(config, deps),
|
||||
)
|
||||
# Video twin — same handler, distinct action_type so the system seeder
|
||||
# (keyed on action_type) creates a separate video-owned row.
|
||||
engine.register_action_handler(
|
||||
'video_clean_search_history',
|
||||
lambda config: auto_clean_search_history(config, deps),
|
||||
)
|
||||
engine.register_action_handler(
|
||||
'clean_completed_downloads',
|
||||
lambda config: auto_clean_completed_downloads(config, deps),
|
||||
|
|
|
|||
|
|
@ -182,6 +182,17 @@ SYSTEM_AUTOMATIONS = [
|
|||
'action_type': 'video_add_airing_episodes',
|
||||
'owned_by': 'video',
|
||||
},
|
||||
# Video twins of the music maintenance jobs — same schedule + shared handler,
|
||||
# distinct action_type + owned_by='video' so they seed as separate rows and
|
||||
# show on the video Automations page (music's copies are untouched).
|
||||
{
|
||||
'name': 'Clean Search History',
|
||||
'trigger_type': 'schedule',
|
||||
'trigger_config': {'interval': 1, 'unit': 'hours'},
|
||||
'action_type': 'video_clean_search_history',
|
||||
'initial_delay': 600,
|
||||
'owned_by': 'video',
|
||||
},
|
||||
# Video twin of music's 'Auto-Deep Scan Library', split into TWO because Movies
|
||||
# and TV are independent libraries — a TV scan never pulls in new movies and
|
||||
# vice-versa. Fixed weekly deep scan (re-read + prune removed) at 02:00 server-
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ EXPECTED_ACTION_NAMES = frozenset({
|
|||
'video_scan_server',
|
||||
'video_update_database',
|
||||
'video_add_airing_episodes',
|
||||
'video_clean_search_history',
|
||||
})
|
||||
|
||||
# Action names that MUST register a guard (duplicate-run prevention).
|
||||
|
|
|
|||
69
tests/automation/test_video_maintenance_twins.py
Normal file
69
tests/automation/test_video_maintenance_twins.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"""Video twins of the music maintenance automations.
|
||||
|
||||
The music side has "Clean Search History" / "Clean Completed Downloads" / "Full
|
||||
Cleanup" / "Auto-Backup Database". The video side gets its own copies so they
|
||||
appear on the video Automations page too — distinct ``video_*`` action_type
|
||||
(the system seeder keys on action_type, so a shared key would collide), the SAME
|
||||
shared handler where the behaviour is identical, and ``owned_by='video'`` so the
|
||||
music page is never disrupted.
|
||||
|
||||
These pin the registry + scope + seed contract, one twin per phase.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from core.automation.blocks import blocks_for_scope
|
||||
from core.automation_engine import SYSTEM_AUTOMATIONS
|
||||
|
||||
|
||||
def _action_types(scope):
|
||||
return {a["type"] for a in blocks_for_scope(scope)["actions"]}
|
||||
|
||||
|
||||
def _system_by_action(action_type):
|
||||
return [s for s in SYSTEM_AUTOMATIONS if s.get("action_type") == action_type]
|
||||
|
||||
|
||||
# ── Phase 2: Clean Search History ───────────────────────────────────────────
|
||||
|
||||
def test_video_clean_search_history_is_video_scoped_only():
|
||||
# Appears on the video builder, NOT the music builder (music block untouched).
|
||||
assert "video_clean_search_history" in _action_types("video")
|
||||
assert "video_clean_search_history" not in _action_types("music")
|
||||
|
||||
|
||||
def test_music_clean_search_history_is_untouched():
|
||||
# The original music action still exists and is still music-scoped — no disruption.
|
||||
assert "clean_search_history" in _action_types("music")
|
||||
assert "clean_search_history" not in _action_types("video")
|
||||
|
||||
|
||||
def test_video_clean_search_history_seeds_one_video_owned_system_row():
|
||||
rows = _system_by_action("video_clean_search_history")
|
||||
assert len(rows) == 1
|
||||
row = rows[0]
|
||||
assert row["owned_by"] == "video"
|
||||
assert row["trigger_type"] == "schedule"
|
||||
# mirrors the music cadence (every 1 hour)
|
||||
assert row["trigger_config"] == {"interval": 1, "unit": "hours"}
|
||||
|
||||
|
||||
def test_video_clean_search_history_reuses_the_music_handler():
|
||||
# Registered to the SAME handler function as the music action (identical behaviour).
|
||||
from core.automation.handlers import register_all
|
||||
|
||||
class _Eng:
|
||||
def __init__(self):
|
||||
self.handlers = {}
|
||||
def register_action_handler(self, t, fn, guard_fn=None):
|
||||
self.handlers[t] = fn
|
||||
def register_progress_callbacks(self, *a, **k):
|
||||
pass
|
||||
|
||||
import core.automation.handlers.registration as reg
|
||||
eng = _Eng()
|
||||
# Build minimal deps via the test helper the registration suite uses.
|
||||
from tests.automation.test_handler_registration import _build_deps
|
||||
register_all(_build_deps(eng))
|
||||
assert "video_clean_search_history" in eng.handlers
|
||||
assert "clean_search_history" in eng.handlers
|
||||
Loading…
Reference in a new issue