Files
camoufox/pythonlib/tests/test_config_schema.py
T
Jake Writer 0169975638 fix(config): declare media:spoof_codecs, and guard the whole class
PR #562 added a `media:spoof_codecs` read on the C++ side --
MaskConfig::GetBool("media:spoof_codecs") in MP4Decoder and
MatroskaDecoder -- but never declared the key in settings/. Since
validate_config() drops any key it does not recognise, the documented
usage was inert:

    AsyncCamoufox(config={"media:spoof_codecs": True})
    -> "Skipping unknown patch media:spoof_codecs : True"

The key never reached the browser, so the feature could not be turned on
through the supported path at all. Declared in both properties.json and
camoucfg.jvv (bool, beside mediaDevices:enabled).

The new test is the general form rather than a check for this one key:
it scans patches/ and additions/ for MaskConfig::Get*/Has*("key") reads
and fails when a key is not declared in settings/properties.json. A
patch and its schema entry are two halves of one change, and shipping
only one half is a mistake this project has now made in both directions
-- canvas:seed (#721) and navigator.maxTouchPoints (#696) were declared
but unconsumed; this one was consumed but undeclared. Across the tree
the scan finds 63 reads against 109 declared keys, and media:spoof_codecs
was the only gap.

Note the runtime reads properties.json from the *installed browser
bundle*, not the repo, so this fix only takes effect for a build
packaged after it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQgHHGRXNp29jr4xQjK7iv
(cherry picked from commit 375b0fca4529a722220022c7993c030b83439db1)
2026-09-05 14:26:37 -06:00

108 lines
3.7 KiB
Python

"""
Guard: every config key the browser reads must be declared in the schema.
Regression guard for the `media:spoof_codecs` gap in PR #562. The C++ side read
the key via MaskConfig::GetBool("media:spoof_codecs"), but nothing ever added it
to settings/properties.json, and validate_config() drops any key it does not
recognise -- printing "Skipping unknown patch media:spoof_codecs" and moving on.
The documented usage,
AsyncCamoufox(config={"media:spoof_codecs": True})
therefore did nothing at all: the key never reached the browser, so the feature
could not be switched on through the supported path.
This is the read-but-undeclared direction of a mistake the project has made
before in the other direction -- canvas:seed (#721) and navigator.maxTouchPoints
(#696) were both declared in the schema while nothing consumed them. A patch and
a schema entry are two halves of one change; this test fails the build when only
one half lands.
Run with:
cd pythonlib && python -m pytest tests/test_config_schema.py -v
"""
import json
import re
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[2]
PROPERTIES = REPO / "settings" / "properties.json"
# MaskConfig::GetBool("k") / GetString("k") / GetUint32("k") / HasKey("k") ...
MASKCONFIG_READ = re.compile(r'MaskConfig::(?:Get|Has)\w*\(\s*"([^"]+)"')
# Keys read through a variable or built at runtime rather than a string literal.
# Add here (with a reason) only when the read genuinely cannot name its key.
ALLOWED_UNDECLARED: set = set()
def _sources():
for pattern in ("patches/**/*.patch", "additions/**/*"):
for path in REPO.glob(pattern):
if path.is_file():
yield path
def _declared_keys() -> set:
return {entry["property"] for entry in json.loads(PROPERTIES.read_text())}
def _keys_read() -> dict:
"""Map config key -> sorted list of files that read it."""
found: dict = {}
for path in _sources():
try:
text = path.read_text(errors="ignore")
except OSError:
continue
for match in MASKCONFIG_READ.finditer(text):
found.setdefault(match.group(1), set()).add(
str(path.relative_to(REPO))
)
return {k: sorted(v) for k, v in found.items()}
def test_properties_json_is_wellformed():
entries = json.loads(PROPERTIES.read_text())
assert entries, "settings/properties.json is empty"
for entry in entries:
assert "property" in entry and "type" in entry, f"malformed entry: {entry}"
def test_every_key_the_browser_reads_is_declared():
declared = _declared_keys()
read = _keys_read()
assert read, "found no MaskConfig reads -- the scanner regexp has gone stale"
undeclared = {
key: files
for key, files in read.items()
if key not in declared and key not in ALLOWED_UNDECLARED
}
if undeclared:
lines = [
"config keys are read by the browser but missing from "
"settings/properties.json,",
"so validate_config() silently drops them and the feature cannot be "
"enabled through the Python API:",
"",
]
for key, files in sorted(undeclared.items()):
lines.append(f" {key}")
for f in files:
lines.append(f" read in {f}")
pytest.fail("\n".join(lines))
@pytest.mark.parametrize("key", ["media:spoof_codecs"])
def test_known_previously_missing_keys_stay_declared(key):
"""Pin the specific keys this guard was written for, so a schema edit that
drops one fails loudly here rather than only in the general scan above."""
assert key in _declared_keys(), (
f"{key} is read by the browser but is not declared in "
f"settings/properties.json -- see PR #562"
)