6e252d1e81
aggregates.py used to call SubtitleKnowledgeBase().default_rules() via a DEFAULT_RULES() helper, which silently pulled the infrastructure layer (YAML loader) into the domain on every resolve. Make the dependency explicit: resolve() now takes the default rules as a parameter, and the caller (the ManageSubtitles use case) loads them from the KB once and passes them in. Domain stays I/O-free. - Drop DEFAULT_RULES helper and the SubtitleKnowledgeBase import from alfred/domain/subtitles/aggregates.py - SubtitleRuleSet.resolve(default_rules: SubtitleMatchingRules) - manage_subtitles use case passes kb.default_rules() at the call site - Tests use a local SubtitleMatchingRules stand-in instead of relying on KB defaults
191 lines
7.1 KiB
Python
191 lines
7.1 KiB
Python
"""Tests for ``alfred.infrastructure.subtitle.rule_repository.RuleSetRepository``.
|
|
|
|
Loads/saves the SubtitleRuleSet inheritance chain from ``.alfred/`` YAML.
|
|
|
|
Coverage:
|
|
|
|
- ``TestLoad`` — no files → ``global_default``; rules.yaml override applied
|
|
on top; release_groups/{NAME}.yaml override applied;
|
|
SubtitlePreferences seeds the base when provided; full 3-level chain.
|
|
- ``TestFilterOverride`` — unknown keys discarded.
|
|
- ``TestSaveLocal`` — atomic write, merges with existing, creates .alfred/.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from alfred.domain.subtitles.value_objects import SubtitleMatchingRules
|
|
from alfred.infrastructure.persistence.memory.ltm.components.subtitle_preferences import (
|
|
SubtitlePreferences,
|
|
)
|
|
from alfred.infrastructure.subtitle.rule_repository import (
|
|
RuleSetRepository,
|
|
_filter_override,
|
|
)
|
|
|
|
# Stand-in for KB defaults, injected at resolve().
|
|
_DEFAULT_RULES = SubtitleMatchingRules(
|
|
preferred_languages=["eng"],
|
|
preferred_formats=["srt"],
|
|
allowed_types=["standard"],
|
|
format_priority=["srt", "ass"],
|
|
min_confidence=0.7,
|
|
)
|
|
|
|
|
|
def _write(path: Path, data: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(yaml.safe_dump(data), encoding="utf-8")
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# _filter_override #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
class TestFilterOverride:
|
|
def test_keeps_only_valid_keys(self):
|
|
out = _filter_override(
|
|
{
|
|
"languages": ["fra"],
|
|
"formats": ["srt"],
|
|
"types": ["standard"],
|
|
"format_priority": ["srt"],
|
|
"min_confidence": 0.8,
|
|
"unknown_key": "ignored",
|
|
"another": 42,
|
|
}
|
|
)
|
|
assert set(out) == {
|
|
"languages",
|
|
"formats",
|
|
"types",
|
|
"format_priority",
|
|
"min_confidence",
|
|
}
|
|
assert "unknown_key" not in out
|
|
|
|
def test_empty(self):
|
|
assert _filter_override({}) == {}
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# load #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
class TestLoad:
|
|
def test_no_files_returns_global_default(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
rs = repo.load()
|
|
# With no overrides, resolve returns the injected defaults unchanged.
|
|
rules = rs.resolve(_DEFAULT_RULES)
|
|
assert rules.preferred_languages == _DEFAULT_RULES.preferred_languages
|
|
assert rules.min_confidence == _DEFAULT_RULES.min_confidence
|
|
|
|
def test_subtitle_preferences_override_base(self, tmp_path):
|
|
prefs = SubtitlePreferences(
|
|
languages=["jpn"], formats=["ass"], types=["standard"]
|
|
)
|
|
repo = RuleSetRepository(tmp_path)
|
|
rules = repo.load(subtitle_preferences=prefs).resolve(_DEFAULT_RULES)
|
|
assert rules.preferred_languages == ["jpn"]
|
|
assert rules.preferred_formats == ["ass"]
|
|
assert rules.allowed_types == ["standard"]
|
|
|
|
def test_local_rules_yaml_applied(self, tmp_path):
|
|
_write(
|
|
tmp_path / ".alfred" / "rules.yaml",
|
|
{"override": {"languages": ["spa"], "min_confidence": 0.95}},
|
|
)
|
|
repo = RuleSetRepository(tmp_path)
|
|
rules = repo.load().resolve(_DEFAULT_RULES)
|
|
assert rules.preferred_languages == ["spa"]
|
|
assert rules.min_confidence == 0.95
|
|
|
|
def test_release_group_override_applied(self, tmp_path):
|
|
_write(
|
|
tmp_path / ".alfred" / "release_groups" / "KONTRAST.yaml",
|
|
{"override": {"format_priority": ["ass", "srt"]}},
|
|
)
|
|
repo = RuleSetRepository(tmp_path)
|
|
rules = repo.load(release_group="KONTRAST").resolve(_DEFAULT_RULES)
|
|
assert rules.format_priority == ["ass", "srt"]
|
|
|
|
def test_full_three_level_chain(self, tmp_path):
|
|
# Base: prefs sets languages=["jpn"]
|
|
prefs = SubtitlePreferences(languages=["jpn"])
|
|
# Group: overrides format_priority
|
|
_write(
|
|
tmp_path / ".alfred" / "release_groups" / "GRP.yaml",
|
|
{"override": {"format_priority": ["ass"]}},
|
|
)
|
|
# Local: overrides min_confidence
|
|
_write(
|
|
tmp_path / ".alfred" / "rules.yaml",
|
|
{"override": {"min_confidence": 0.99}},
|
|
)
|
|
repo = RuleSetRepository(tmp_path)
|
|
rules = repo.load(release_group="GRP", subtitle_preferences=prefs).resolve(
|
|
_DEFAULT_RULES
|
|
)
|
|
# All three levels visible — local overrides on top
|
|
assert rules.preferred_languages == ["jpn"]
|
|
assert rules.format_priority == ["ass"]
|
|
assert rules.min_confidence == 0.99
|
|
|
|
def test_release_group_yaml_without_override_section_ignored(self, tmp_path):
|
|
_write(
|
|
tmp_path / ".alfred" / "release_groups" / "GRP.yaml",
|
|
{"name": "GRP"}, # no 'override' key
|
|
)
|
|
# Must not crash and must not introduce an intermediate node.
|
|
repo = RuleSetRepository(tmp_path)
|
|
rs = repo.load(release_group="GRP")
|
|
# No extra rule set was created → it's still the global default.
|
|
assert rs.scope.level == "global"
|
|
|
|
def test_missing_release_group_file_silently_ignored(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
rs = repo.load(release_group="DOES_NOT_EXIST")
|
|
assert rs.scope.level == "global"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# save_local #
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
class TestSaveLocal:
|
|
def test_creates_file(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
repo.save_local({"languages": ["spa"]})
|
|
path = tmp_path / ".alfred" / "rules.yaml"
|
|
assert path.is_file()
|
|
loaded = yaml.safe_load(path.read_text())
|
|
assert loaded == {"override": {"languages": ["spa"]}}
|
|
|
|
def test_merges_with_existing(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
repo.save_local({"languages": ["spa"]})
|
|
repo.save_local({"min_confidence": 0.8})
|
|
loaded = yaml.safe_load((tmp_path / ".alfred" / "rules.yaml").read_text())
|
|
assert loaded["override"]["languages"] == ["spa"]
|
|
assert loaded["override"]["min_confidence"] == 0.8
|
|
|
|
def test_overwrites_existing_key(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
repo.save_local({"languages": ["spa"]})
|
|
repo.save_local({"languages": ["jpn"]})
|
|
loaded = yaml.safe_load((tmp_path / ".alfred" / "rules.yaml").read_text())
|
|
assert loaded["override"]["languages"] == ["jpn"]
|
|
|
|
def test_temp_file_cleaned_up(self, tmp_path):
|
|
repo = RuleSetRepository(tmp_path)
|
|
repo.save_local({"languages": ["spa"]})
|
|
# No stale .tmp file
|
|
assert not (tmp_path / ".alfred" / "rules.yaml.tmp").exists()
|