test: drift guard — frontend lossless lists must match backend LOSSLESS_FORMATS (#941)
the frontend keeps its own copy of the lossless set (settings.js RT_LOSSLESS_FORMATS + the index.html quality-profile dropdown) — runtime-fetching a yearly-changing list from the backend isn't worth the coupling. but that duplication IS the exact root cause of #941 (a format added to one place, not another). so instead of unifying, pin it: two tests parse the frontend lists and assert they match the backend LOSSLESS_FORMATS. adding a new lossless format now fails CI until it's added everywhere, instead of silently shipping a half-wired feature. verified the guard catches drift (not a tautology): a simulated backend-only 'ape' addition makes the equality fail. 18 lossless tests green, ruff clean.
This commit is contained in:
parent
4af7600fd5
commit
8aa8fcb94a
1 changed files with 42 additions and 0 deletions
|
|
@ -119,3 +119,45 @@ def test_regression_m4a_alac_to_aac_would_overwrite_and_is_blocked():
|
||||||
out = src # AAC target → .m4a
|
out = src # AAC target → .m4a
|
||||||
assert is_lossless_audio_path(src, probe_codec=lambda _p: 'alac') is True
|
assert is_lossless_audio_path(src, probe_codec=lambda _p: 'alac') is True
|
||||||
assert lossy_output_would_overwrite_source(src, out) is True # → callers skip
|
assert lossy_output_would_overwrite_source(src, out) is True # → callers skip
|
||||||
|
|
||||||
|
|
||||||
|
# ── cross-language drift guard: the frontend lossless lists must match the backend ──
|
||||||
|
# (#941's root cause: a format added to the quality profile but not the lossy-copy
|
||||||
|
# side. The lists are physically separate by choice — runtime-fetching a yearly-
|
||||||
|
# changing set isn't worth the coupling — so a test makes the drift impossible to
|
||||||
|
# ship silently instead.)
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
|
_SETTINGS_JS = _ROOT / "webui" / "static" / "settings.js"
|
||||||
|
_INDEX_HTML = _ROOT / "webui" / "index.html"
|
||||||
|
|
||||||
|
|
||||||
|
def _js_array(text, name):
|
||||||
|
m = re.search(rf"{name}\s*=\s*\[([^\]]*)\]", text)
|
||||||
|
assert m, f"{name} not found"
|
||||||
|
return set(re.findall(r"'([^']+)'", m.group(1)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_frontend_rt_lossless_formats_matches_backend():
|
||||||
|
js = _SETTINGS_JS.read_text(encoding="utf-8")
|
||||||
|
frontend = _js_array(js, "RT_LOSSLESS_FORMATS")
|
||||||
|
assert frontend == set(LOSSLESS_FORMATS), (
|
||||||
|
f"settings.js RT_LOSSLESS_FORMATS {frontend} != backend LOSSLESS_FORMATS "
|
||||||
|
f"{set(LOSSLESS_FORMATS)} — add the new format to BOTH"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_quality_profile_dropdown_offers_every_lossless_format():
|
||||||
|
html = _INDEX_HTML.read_text(encoding="utf-8")
|
||||||
|
m = re.search(r'<optgroup label="Lossless">(.*?)</optgroup>', html, re.DOTALL)
|
||||||
|
assert m, "Lossless optgroup not found in index.html"
|
||||||
|
# concrete per-format option values (skip the 'group:lossless' convenience entry)
|
||||||
|
option_values = {v for v in re.findall(r'value="([^"]+)"', m.group(1))
|
||||||
|
if not v.startswith("group:")}
|
||||||
|
assert set(LOSSLESS_FORMATS) <= option_values, (
|
||||||
|
f"index.html lossless dropdown {option_values} is missing "
|
||||||
|
f"{set(LOSSLESS_FORMATS) - option_values}"
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue