b4c9efd13b
Wire the scoring foundations into the parser entry point. parse_release now returns a tuple — the structural ParsedRelease and a diagnostic ParseReport carrying confidence (0-100), road (EASY / SHITTY / PATH_OF_PAIN), the residual UNKNOWN tokens, and the list of critical fields that couldn't be filled. EASY is decided structurally (a group schema matched), independently of the score. SHITTY vs PATH_OF_PAIN is decided by score against the 60 cutoff from scoring.yaml. Malformed names (forbidden chars) emit a zero-confidence PoP report and short-circuit to parse_path=AI as before. ParsePath stays as-is (DIRECT / SANITIZED / AI) — it records *how* we tokenized, not how confident we are. The two dimensions are now properly separated. Call sites propagated: - alfred/application/filesystem/resolve_destination.py (4 occurrences) - alfred/agent/tools/filesystem.py - tests/domain/test_release.py - tests/domain/test_release_fixtures.py - tests/application/test_detect_media_type.py New tests/domain/release/test_parser_v2_scoring.py (22 cases) locks ParseReport validation, compute_score arithmetic, decide_road thresholding, the collector helpers, and the end-to-end tuple contract.
66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
"""Real-world release fixtures — anti-regression baseline for parse_release.
|
|
|
|
Each fixture under ``tests/fixtures/releases/<bucket>/<case>/expected.yaml``
|
|
declares a release name and the ``ParsedRelease`` fields it should produce.
|
|
Fields absent from the fixture's ``parsed`` block are not checked, so adding
|
|
new attributes to ``ParsedRelease`` never breaks existing fixtures.
|
|
|
|
The fixture's ``tree`` is materialized into a temp dir to prove the layout is
|
|
self-consistent, even though no filesystem assertions are made yet. The
|
|
``routing`` block (library / torrents / seed_hardlinks) is captured ahead of
|
|
the ``organize_media`` refactor — it will become verifiable once the planner
|
|
exists.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict
|
|
|
|
import pytest
|
|
|
|
from alfred.domain.release.services import parse_release
|
|
from alfred.infrastructure.knowledge.release_kb import YamlReleaseKnowledge
|
|
from tests.fixtures.releases.conftest import ReleaseFixture, discover_fixtures
|
|
|
|
_KB = YamlReleaseKnowledge()
|
|
FIXTURES = discover_fixtures()
|
|
|
|
|
|
def _fixture_param(f: ReleaseFixture) -> pytest.param:
|
|
marks = []
|
|
if f.xfail_reason:
|
|
marks.append(pytest.mark.xfail(reason=f.xfail_reason, strict=False))
|
|
return pytest.param(f, id=f.name, marks=marks)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"fixture",
|
|
[_fixture_param(f) for f in FIXTURES],
|
|
)
|
|
def test_parse_matches_fixture(fixture: ReleaseFixture, tmp_path) -> None:
|
|
# Materialize the tree to assert it is at least well-formed YAML +
|
|
# plausible filesystem paths. Catches typos / missing leading dirs early.
|
|
fixture.materialize(tmp_path)
|
|
|
|
parsed, _report = parse_release(fixture.release_name, _KB)
|
|
result = asdict(parsed)
|
|
# ``is_season_pack`` is a @property — asdict() does not include it.
|
|
result["is_season_pack"] = parsed.is_season_pack
|
|
|
|
for field, expected in fixture.expected_parsed.items():
|
|
assert field in result, (
|
|
f"{fixture.name}: unknown field '{field}' in expected.parsed"
|
|
)
|
|
assert result[field] == expected, (
|
|
f"{fixture.name}: parsed.{field} — "
|
|
f"expected {expected!r}, got {result[field]!r}"
|
|
)
|
|
|
|
|
|
def test_at_least_one_fixture_per_bucket() -> None:
|
|
"""Each bucket should hold at least one case once populated."""
|
|
buckets = {f.name.split("/")[0] for f in FIXTURES}
|
|
assert "easy" in buckets, "EASY bucket must have at least one fixture"
|
|
assert "shitty" in buckets, "SHITTY bucket must have at least one fixture"
|
|
assert "path_of_pain" in buckets, "PATH_OF_PAIN bucket must have at least one fixture"
|