Files
alfred/tests/domain/test_subtitle_pattern_detector.py
T
francwa ced72547f7 refactor(knowledge): extract YAML loaders from domain to infrastructure
The domain layer no longer reads YAML files. All knowledge loaders move
from `alfred/domain/*/knowledge/` to `alfred/infrastructure/knowledge/`:

  domain/release/knowledge.py
    → infrastructure/knowledge/release.py
  domain/shared/knowledge/language_registry.py
    → infrastructure/knowledge/language_registry.py
  domain/subtitles/knowledge/{loader,base}.py
    → infrastructure/knowledge/subtitles/{loader,base}.py

Callers in domain/release/{services,value_objects}.py,
domain/subtitles/{aggregates,services/*}.py, and
application/filesystem/manage_subtitles.py updated to absolute imports.
Re-exports of KnowledgeLoader/SubtitleKnowledgeBase from
domain/subtitles/__init__.py dropped (no shim per project convention).
Tests follow the moved targets.
2026-05-19 14:35:18 +02:00

191 lines
7.4 KiB
Python

"""Tests for ``alfred.domain.subtitles.services.pattern_detector.PatternDetector``.
The detector inspects a release folder and returns the best-matching known
pattern + a confidence score.
Coverage:
- ``TestEmbeddedDetection`` — ffprobe is mocked; ``embedded`` pattern wins
when no external subs and ffprobe reports tracks.
- ``TestAdjacentDetection`` — .srt next to the video → ``adjacent``.
- ``TestFlatSubsFolder`` — ``Subs/*.srt`` → ``subs_flat``.
- ``TestEpisodeSubfolder`` — ``Subs/{ep}/*.srt`` → ``episode_subfolder``.
- ``TestNothingFound`` — empty release returns no pattern.
- ``TestDescribe`` — human-readable description mentions the right cues.
Uses the real ``SubtitleKnowledgeBase`` (loaded from the live builtin
``patterns/`` folder) since rebuilding all four patterns by hand would
just duplicate fixture state.
"""
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
import pytest
from alfred.infrastructure.knowledge.subtitles.base import SubtitleKnowledgeBase
from alfred.domain.subtitles.services.pattern_detector import PatternDetector
@pytest.fixture(scope="module")
def kb():
return SubtitleKnowledgeBase()
@pytest.fixture
def detector(kb):
return PatternDetector(kb)
def _make_video(folder: Path, name: str = "Show.S01E01.mkv") -> Path:
v = folder / name
v.write_bytes(b"")
return v
# --------------------------------------------------------------------------- #
# Embedded #
# --------------------------------------------------------------------------- #
class TestEmbeddedDetection:
def test_embedded_only(self, detector, tmp_path):
# Folder has video but no external .srt files anywhere.
video = _make_video(tmp_path)
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=True
):
result = detector.detect(tmp_path, video)
assert result["detected"] is not None
assert result["detected"].id == "embedded"
assert result["confidence"] > 0
assert "embedded" in result["description"].lower()
# --------------------------------------------------------------------------- #
# Adjacent #
# --------------------------------------------------------------------------- #
class TestAdjacentDetection:
def test_srt_next_to_video(self, detector, tmp_path):
video = _make_video(tmp_path)
(tmp_path / "Show.S01E01.English.srt").write_text("")
(tmp_path / "Show.S01E01.French.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
assert result["detected"] is not None
assert result["detected"].id == "adjacent"
assert "adjacent" in result["description"]
# --------------------------------------------------------------------------- #
# Subs flat folder #
# --------------------------------------------------------------------------- #
class TestFlatSubsFolder:
def test_flat_subs_folder_adjacent_to_video(self, detector, tmp_path):
video = _make_video(tmp_path)
subs = tmp_path / "Subs"
subs.mkdir()
(subs / "Show.S01E01.English.srt").write_text("")
(subs / "Show.S01E01.French.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
assert result["detected"] is not None
assert result["detected"].id == "subs_flat"
assert "flat" in result["description"]
def test_flat_subs_folder_at_release_root(self, detector, tmp_path):
# Sample video lives one level deep; Subs/ is at the release root.
season_dir = tmp_path / "Season.01"
season_dir.mkdir()
video = _make_video(season_dir)
subs = tmp_path / "Subs"
subs.mkdir()
(subs / "ep01.English.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
assert result["detected"] is not None
assert result["detected"].id == "subs_flat"
# --------------------------------------------------------------------------- #
# Episode subfolder #
# --------------------------------------------------------------------------- #
class TestEpisodeSubfolder:
def test_per_episode_subfolder(self, detector, tmp_path):
video = _make_video(tmp_path, name="Show.S01E01.mkv")
subs = tmp_path / "Subs" / "Show.S01E01"
subs.mkdir(parents=True)
(subs / "2_English.srt").write_text("")
(subs / "3_French.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
assert result["detected"] is not None
assert result["detected"].id == "episode_subfolder"
desc = result["description"]
assert "episode_subfolder" in desc
# Numeric-prefix cue should be reported.
assert "numeric prefix" in desc
# --------------------------------------------------------------------------- #
# Nothing #
# --------------------------------------------------------------------------- #
class TestNothingFound:
def test_empty_release_no_pattern(self, detector, tmp_path):
video = _make_video(tmp_path)
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
# No external subs and no embedded → adjacent strategy still scores
# 0.5 (no Subs folder bonus). Best pattern may exist or not depending
# on threshold (0.4). Either way the description must reflect emptiness.
assert "no external subtitle files" in result["description"]
# --------------------------------------------------------------------------- #
# Describe #
# --------------------------------------------------------------------------- #
class TestDescribe:
def test_describe_includes_language_token_cue(self, detector, tmp_path):
video = _make_video(tmp_path)
subs = tmp_path / "Subs"
subs.mkdir()
(subs / "ep01.English.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=False
):
result = detector.detect(tmp_path, video)
assert "language tokens" in result["description"]
def test_describe_combines_external_and_embedded(self, detector, tmp_path):
video = _make_video(tmp_path)
(tmp_path / "Show.S01E01.English.srt").write_text("")
with patch.object(
PatternDetector, "_has_embedded_subtitles", return_value=True
):
result = detector.detect(tmp_path, video)
desc = result["description"]
assert "adjacent" in desc
assert "embedded" in desc.lower()