233 lines
7.4 KiB
Python
233 lines
7.4 KiB
Python
"""Integration tests for ``rescan_movie``.
|
|
|
|
Real filesystem (``tmp_path``), real release KB, real v2 movie
|
|
repository. Only the media prober is stubbed — ffprobe needs real
|
|
bytes and a binary.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from alfred.application.movies_TO_CHECK import MovieRescanFailed, rescan_movie
|
|
from alfred.domain.shared_TO_CHECK.media import (
|
|
AudioTrack,
|
|
MediaInfo,
|
|
SubtitleTrack,
|
|
VideoTrack,
|
|
)
|
|
from alfred.domain.shared_TO_CHECK.value_objects import ImdbId, TmdbId
|
|
from alfred.infrastructure.knowledge_TO_CHECK.release_kb import YamlReleaseKnowledge
|
|
from alfred.infrastructure.persistence_TO_CHECK.dot_alfred.v2.repository import (
|
|
SIDECAR_FILENAME,
|
|
DotAlfredMovieReleaseRepository,
|
|
)
|
|
|
|
_KB = YamlReleaseKnowledge()
|
|
_TMDB_ID = TmdbId(27205)
|
|
_IMDB_ID = ImdbId("tt1375666")
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Helpers #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
_MISSING = object()
|
|
|
|
|
|
class _StubProber:
|
|
def __init__(self, info=_MISSING) -> None:
|
|
self._info: MediaInfo | None = (
|
|
_default_info() if info is _MISSING else info # type: ignore[assignment]
|
|
)
|
|
self.calls: list[Path] = []
|
|
|
|
def probe(self, video: Path) -> MediaInfo | None:
|
|
self.calls.append(video)
|
|
return self._info
|
|
|
|
def list_subtitle_streams(self, video: Path): # pragma: no cover
|
|
return []
|
|
|
|
|
|
def _default_info() -> MediaInfo:
|
|
return MediaInfo(
|
|
video_tracks=(VideoTrack(index=0, codec="hevc", width=1920, height=1080),),
|
|
audio_tracks=(
|
|
AudioTrack(
|
|
index=0,
|
|
codec="eac3",
|
|
channels=6,
|
|
channel_layout="5.1",
|
|
language="eng",
|
|
),
|
|
),
|
|
subtitle_tracks=(
|
|
SubtitleTrack(
|
|
index=0,
|
|
codec="subrip",
|
|
language="eng",
|
|
is_default=False,
|
|
is_forced=False,
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def _make_inception_folder(root: Path) -> Path:
|
|
"""Build a fake Inception movie folder under ``root``."""
|
|
movie_dir = root / "Inception (2010)"
|
|
movie_dir.mkdir()
|
|
(movie_dir / "Inception.2010.1080p.BluRay.x264-GROUP.mkv").write_bytes(b"")
|
|
return movie_dir
|
|
|
|
|
|
def _library_with_movie(tmp_path: Path) -> tuple[Path, Path]:
|
|
library = tmp_path / "movies"
|
|
library.mkdir()
|
|
movie_dir = _make_inception_folder(library)
|
|
return library, movie_dir
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Happy path #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
class TestHappyPath:
|
|
def test_builds_release_with_tracks(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
prober = _StubProber()
|
|
|
|
release = rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
imdb_id=_IMDB_ID,
|
|
movie_repo=repo,
|
|
prober=prober,
|
|
kb=_KB,
|
|
)
|
|
|
|
assert release.tmdb_id == _TMDB_ID
|
|
assert release.imdb_id == _IMDB_ID
|
|
assert release.folder == "Inception (2010)"
|
|
assert str(release.file_path) == "Inception.2010.1080p.BluRay.x264-GROUP.mkv"
|
|
assert isinstance(release.added_at, datetime)
|
|
assert len(release.tracks.audio_tracks) == 1
|
|
assert release.tracks.audio_tracks[0].language == "eng"
|
|
assert len(release.tracks.subtitle_tracks) == 1
|
|
|
|
def test_file_path_relative_to_movie_dir(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
release = rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(),
|
|
kb=_KB,
|
|
)
|
|
assert not Path(str(release.file_path)).is_absolute()
|
|
|
|
def test_persists_sidecar_on_disk_and_round_trips(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
imdb_id=_IMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(),
|
|
kb=_KB,
|
|
)
|
|
assert (movie_dir / SIDECAR_FILENAME).is_file()
|
|
recovered = repo.find_by_tmdb_id(_TMDB_ID)
|
|
assert recovered is not None
|
|
assert recovered.tmdb_id == _TMDB_ID
|
|
assert recovered.folder == "Inception (2010)"
|
|
|
|
def test_probe_called_exactly_once(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
prober = _StubProber()
|
|
rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=prober,
|
|
kb=_KB,
|
|
)
|
|
assert len(prober.calls) == 1
|
|
|
|
def test_imdb_id_optional(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
release = rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(),
|
|
kb=_KB,
|
|
)
|
|
assert release.imdb_id is None
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Edge cases #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
class TestEdgeCases:
|
|
def test_no_video_raises(self, tmp_path):
|
|
library = tmp_path / "movies"
|
|
library.mkdir()
|
|
movie_dir = library / "Empty (2010)"
|
|
movie_dir.mkdir()
|
|
(movie_dir / "notes.txt").write_text("x")
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
with pytest.raises(MovieRescanFailed):
|
|
rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(),
|
|
kb=_KB,
|
|
)
|
|
|
|
def test_prober_returning_none_still_produces_release(self, tmp_path):
|
|
library, movie_dir = _library_with_movie(tmp_path)
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
release = rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(info=None),
|
|
kb=_KB,
|
|
)
|
|
assert release.tracks.audio_tracks == ()
|
|
assert release.tracks.subtitle_tracks == ()
|
|
|
|
def test_video_in_subfolder_is_found(self, tmp_path):
|
|
library = tmp_path / "movies"
|
|
library.mkdir()
|
|
movie_dir = library / "Inception (2010)"
|
|
movie_dir.mkdir()
|
|
sub = movie_dir / "extras"
|
|
sub.mkdir()
|
|
(sub / "Inception.2010.1080p.mkv").write_bytes(b"")
|
|
repo = DotAlfredMovieReleaseRepository(library)
|
|
release = rescan_movie(
|
|
movie_dir,
|
|
tmdb_id=_TMDB_ID,
|
|
movie_repo=repo,
|
|
prober=_StubProber(),
|
|
kb=_KB,
|
|
)
|
|
# Relative path keeps the subfolder.
|
|
assert str(release.file_path) == "extras/Inception.2010.1080p.mkv"
|