The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").
Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.
Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).
One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
83 lines
3.0 KiB
Python
83 lines
3.0 KiB
Python
"""plex/paths — the Plex-side path → local mount translation (it was 23% covered, and a wrong answer
|
|
here reads as "media file not found" for a whole library at once, with nothing else failing).
|
|
|
|
Pure logic: the admin map is read through sysconfig, stubbed per test.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.plex import paths
|
|
|
|
|
|
@pytest.fixture
|
|
def path_map(monkeypatch):
|
|
def use(raw: str) -> None:
|
|
monkeypatch.setattr(paths.sysconfig, "get_str", lambda db, key: raw)
|
|
|
|
return use
|
|
|
|
|
|
def test_the_map_parses_both_separators_drops_junk_and_puts_the_longest_prefix_first():
|
|
raw = " /data=/mnt/a ,\n/data/movies = /mnt/m\nnot-a-pair,=/x,/y=, /data/tv=/mnt/tv "
|
|
assert paths.parse_map(raw) == [
|
|
("/data/movies", "/mnt/m"),
|
|
("/data/tv", "/mnt/tv"),
|
|
("/data", "/mnt/a"),
|
|
]
|
|
assert paths.parse_map("") == []
|
|
|
|
|
|
def test_the_longest_matching_prefix_wins(path_map):
|
|
path_map("/data=/mnt/a,/data/movies=/mnt/m")
|
|
assert paths.map_path(None, "/data/movies/Foo.mkv") == str(Path("/mnt/m") / "Foo.mkv")
|
|
assert paths.map_path(None, "/data/tv/Bar.mkv") == str(Path("/mnt/a") / "tv/Bar.mkv")
|
|
|
|
|
|
def test_a_prefix_matches_only_at_a_path_boundary(path_map):
|
|
# "/data/movies2" shares the characters of "/data/movies" but is another folder.
|
|
path_map("/data/movies=/mnt/m")
|
|
assert paths.map_path(None, "/data/movies2/Foo.mkv") == "/data/movies2/Foo.mkv"
|
|
|
|
|
|
def test_the_prefix_itself_and_a_trailing_slash_in_the_map(path_map):
|
|
path_map("/data/movies/=/mnt/m")
|
|
assert paths.map_path(None, "/data/movies/Foo.mkv") == str(Path("/mnt/m") / "Foo.mkv")
|
|
path_map("/data/movies=/mnt/m")
|
|
assert paths.map_path(None, "/data/movies") == "/mnt/m"
|
|
|
|
|
|
def test_no_map_leaves_the_path_as_plex_sees_it(path_map):
|
|
path_map("")
|
|
assert paths.map_path(None, "/data/movies/Foo.mkv") == "/data/movies/Foo.mkv"
|
|
|
|
|
|
def test_the_local_file_must_exist(path_map, tmp_path):
|
|
(tmp_path / "movies").mkdir()
|
|
(tmp_path / "movies" / "Foo.mkv").write_bytes(b"x")
|
|
path_map(f"/data={tmp_path}")
|
|
assert (
|
|
paths.local_media_path(None, "/data/movies/Foo.mkv")
|
|
== (tmp_path / "movies" / "Foo.mkv").resolve()
|
|
)
|
|
assert paths.local_media_path(None, "/data/movies/Missing.mkv") is None
|
|
assert paths.local_media_path(None, "/data/movies") is None # a folder is not a file
|
|
assert paths.local_media_path(None, None) is None
|
|
assert paths.local_media_path(None, "") is None
|
|
|
|
|
|
def test_a_path_that_cannot_be_resolved_is_logged_and_answered_none(path_map, monkeypatch, caplog):
|
|
path_map("")
|
|
|
|
def broken(self, strict=False):
|
|
raise OSError("dead mount")
|
|
|
|
monkeypatch.setattr(paths.Path, "resolve", broken)
|
|
assert paths.local_media_path(None, "/data/movies/Foo.mkv") is None
|
|
rec = next(
|
|
r for r in caplog.records if getattr(r, "event", None) == "plex.paths.resolve_failed"
|
|
)
|
|
# The title stays out of the log line: the path carries it.
|
|
assert "Foo" not in rec.getMessage() and rec.attrs == {"reason": "OSError"}
|