From 8aa8fcb94ab022fd2b7b27841316c874d119224f Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sun, 28 Jun 2026 12:18:57 -0700 Subject: [PATCH] =?UTF-8?q?test:=20drift=20guard=20=E2=80=94=20frontend=20?= =?UTF-8?q?lossless=20lists=20must=20match=20backend=20LOSSLESS=5FFORMATS?= =?UTF-8?q?=20(#941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- tests/quality/test_lossless.py | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/quality/test_lossless.py b/tests/quality/test_lossless.py index acb274c9..0b311f15 100644 --- a/tests/quality/test_lossless.py +++ b/tests/quality/test_lossless.py @@ -119,3 +119,45 @@ def test_regression_m4a_alac_to_aac_would_overwrite_and_is_blocked(): out = src # AAC target → .m4a 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 + + +# ── 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'(.*?)', 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}" + )