soulsync/tests/test_video_download_tracking.py
BoulderBadgeDad efa64db04d Video downloads: best-in-class post-process pipeline
End-to-end import for video grabs, mirroring the music side's rigor and the
Radarr/Sonarr standard, fully isolated in core/video + api/video.

- Importer: parse release -> ffprobe-verify (true resolution, reject corrupt/
  samples) -> templated rename into Movie (Year)/ + Show/Season NN/ -> copy or
  move, carry subtitles, upgrade-replace a worse copy.
- Library Organization settings: editable $token path templates + toggles
  (transfer mode, verify, replace, carry subs, save artwork, write NFO,
  download subtitles + langs). Stored in video.db; matches the music File
  Organization section's look.
- Sidecar writer: movie.nfo / tvshow.nfo + full artwork set (poster, fanart,
  clearlogo, season posters) from on-demand TMDB detail, and external .srt from
  OpenSubtitles. Owned re-grabs resolve their library tmdb_id; tmdb_full_detail
  bypasses the owned->library redirect so they enrich too.
- Import page: surfaces import_failed downloads, resolve by hand (library-first
  -> TMDB picker -> force-place) or dismiss; fires a library refresh on place.
- "Grab whole season": episode-level batch (reuses searchInto + _autoPick).
- Brutalist redesign of the download modal sources + result cards.

All new logic has seam-level tests (pure parsers/planners + injected I/O);
sidecars/subtitles are best-effort and never break an import.
2026-06-21 01:44:35 -07:00

98 lines
4.3 KiB
Python

"""Live download tracking wiring: after a grab the user must (a) see WHICH release
was selected, (b) get a live progress bar on it, and (c) get a button to the
Downloads page — and a movie's detail page shows live progress for an in-flight
download that jumps back to Downloads.
Backed by GET /api/video/downloads/status (tested in test_video_api.py
::test_downloads_status_lookup_by_id_and_media). These pin the frontend wiring so
a refactor can't quietly unhook the tracker, the detail chip, or the navigation.
"""
from __future__ import annotations
from pathlib import Path
_ROOT = Path(__file__).resolve().parent.parent
_VIEW = (_ROOT / "webui" / "static" / "video" / "video-download-view.js").read_text(encoding="utf-8")
_DETAIL = (_ROOT / "webui" / "static" / "video" / "video-detail.js").read_text(encoding="utf-8")
_SIDE = (_ROOT / "webui" / "static" / "video" / "video-side.js").read_text(encoding="utf-8")
_GETMODAL = (_ROOT / "webui" / "static" / "video" / "video-get-modal.js").read_text(encoding="utf-8")
_CSS = (_ROOT / "webui" / "static" / "video" / "video-side.css").read_text(encoding="utf-8")
# --- result card: redesign + selected/tracking state ----------------------
def test_result_card_redesigned_as_column_with_get_button():
assert 'vdl-res-main' in _VIEW # row wrapper so a tracker can dock below
assert 'data-vdl-card="' in _VIEW # addressable card (auto-pick targets it)
assert '[ GET ]' in _VIEW # brutalist grab button is a bracketed label
assert '.vdl-res-main' in _CSS
def test_grab_begins_live_tracking_on_the_card():
assert 'function beginTracking(' in _VIEW
assert 'vdl-res--grabbed' in _VIEW
assert 'data-vdl-track-fill' in _VIEW # the progress bar fill
assert "/api/video/downloads/status?id=" in _VIEW
# both the manual grab and the auto-pick start tracking the chosen card
assert _VIEW.count('beginTracking(') >= 3 # 1 def + doGrab + _autoPick
def test_track_button_goes_to_downloads_and_closes_modal():
assert 'Track on Downloads' in _VIEW
assert 'function gotoDownloads(' in _VIEW
assert 'VideoGet.close' in _VIEW
assert "'soulsync:video-navigate'" in _VIEW
assert '.vdl-res-track' in _CSS
def test_modal_exposes_close():
assert 'close: closeModal' in _GETMODAL
# --- movie detail page: live download chip --------------------------------
def test_detail_page_watches_movie_download():
assert 'function watchMovieDownload(' in _DETAIL
assert '/api/video/downloads/status?media_id=' in _DETAIL
assert 'data-vd-dlchip' in _DETAIL
# the chip jumps to the Downloads page
assert "'soulsync:video-navigate'" in _DETAIL
assert '.vd-dlchip' in _CSS
def test_detail_watch_started_for_library_movies():
assert 'watchMovieDownload(id)' in _DETAIL
assert 'stopMovieDownloadWatch()' in _DETAIL # cleared on (re)load / navigate away
# --- result cards: flat / brutalist redesign ------------------------------
def test_result_cards_are_flat_brutalist_three_line():
# 3 hard lines: [QUALITY] + title, an UPPERCASE spec line, then verdict + GET.
assert 'vdl-r-l1' in _VIEW and 'vdl-r-q' in _VIEW and 'vdl-r-title' in _VIEW
assert 'vdl-r-l2' in _VIEW and 'vdl-r-l3' in _VIEW and 'vdl-r-verdict' in _VIEW
assert '.vdl-r-q' in _CSS and '.vdl-r-l2' in _CSS and '.vdl-r-verdict' in _CSS
# the cinematic pill/badge language was redesigned out
assert 'vdl-info-tags' not in _VIEW and 'vdl-tag' not in _VIEW
assert 'vdl-q-res' not in _VIEW and 'vdl-flag' not in _VIEW
# sharp corners + monospace are the brutalist signature
assert 'border-radius: 0' in _CSS and 'monospace' in _CSS
# --- modal resumes an in-flight download on reopen ------------------------
def test_modal_resumes_active_download_on_reopen():
assert 'data-vdl-active' in _VIEW
assert 'function watchActiveDownload(' in _VIEW
assert '/api/video/downloads/status?media_id=' in _VIEW
# suppressed while a result card is already tracking inline (no double indicator)
assert "querySelector('[data-vdl-track]')" in _VIEW
assert '.vdl-active' in _CSS
# --- navigation plumbing --------------------------------------------------
def test_video_side_handles_navigate_event():
assert "'soulsync:video-navigate'" in _SIDE
assert 'navigate(pageId)' in _SIDE