From 409c78b984fc1e6a3a47bdded7d8ff1191113d59 Mon Sep 17 00:00:00 2001 From: RMANOV <96174405+RMANOV@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:00:37 +0300 Subject: [PATCH] fix(testing): sanitize scenario exception reports --- python/tests/test_strix_scenario_contract.py | 23 ++++++++++++++++++ scripts/strix_scenario_contract.py | 25 +++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/python/tests/test_strix_scenario_contract.py b/python/tests/test_strix_scenario_contract.py index cc06131..67845bf 100644 --- a/python/tests/test_strix_scenario_contract.py +++ b/python/tests/test_strix_scenario_contract.py @@ -79,6 +79,29 @@ def test_validate_scenario_rejects_missing_seed_and_bad_bounds(tmp_path): assert "coverage: min must be <= max" in result["errors"] +def test_validate_scenario_parse_errors_are_public_safe(tmp_path): + module = _load_module() + scenario = tmp_path / "broken.yaml" + scenario.write_text("scenario_id: [unterminated\n", encoding="utf-8") + + result = module.validate_scenario(scenario) + + assert result["status"] == "failed" + assert str(tmp_path) not in result["path"] + assert str(tmp_path) not in result["errors"][0] + + +def test_validate_scenario_read_errors_are_public_safe(tmp_path): + module = _load_module() + scenario = tmp_path / "missing.yaml" + + result = module.validate_scenario(scenario) + + assert result["status"] == "failed" + assert str(tmp_path) not in result["path"] + assert str(tmp_path) not in result["errors"][0] + + def test_public_scenarios_satisfy_contract(): module = _load_module() scenario_dir = Path(__file__).resolve().parents[2] / "sim" / "scenarios" diff --git a/scripts/strix_scenario_contract.py b/scripts/strix_scenario_contract.py index d1b9d57..9a3ee3c 100644 --- a/scripts/strix_scenario_contract.py +++ b/scripts/strix_scenario_contract.py @@ -37,6 +37,24 @@ def public_path(path: Path) -> str: return str(path) +def public_exception_message(exc: Exception, path: Path) -> str: + """Return an exception message with local filesystem paths redacted.""" + + message = str(exc) or type(exc).__name__ + replacements = {str(path): public_path(path)} + filename = getattr(exc, "filename", None) + filename2 = getattr(exc, "filename2", None) + for candidate in (filename, filename2): + if candidate: + candidate_path = Path(candidate) + replacements[str(candidate_path)] = public_path(candidate_path) + + for raw, safe in replacements.items(): + if raw: + message = message.replace(raw, safe) + return message + + def load_yaml(path: Path) -> dict[str, Any]: data = yaml.safe_load(path.read_text(encoding="utf-8")) if not isinstance(data, dict): @@ -55,7 +73,12 @@ def validate_scenario(path: Path) -> dict[str, Any]: try: data = load_yaml(path) except Exception as exc: # noqa: BLE001 - report parse errors as validation failures - return {"path": str(path), "status": "failed", "errors": [str(exc)], "warnings": []} + return { + "path": public_path(path), + "status": "failed", + "errors": [public_exception_message(exc, path)], + "warnings": [], + } required = [ "scenario_id",