Files
C.E.L_Slide_test2/tests/test_user_overrides_pipeline_fallback.py

411 lines
14 KiB
Python

"""IMP-52 (#80) u9 — backend tests for the argparse persistence fallback.
Stage 2 u9 scope (per the Exit Report):
1. Per-axis fill — file value flows through when CLI omits the axis.
2. CLI-wins — CLI value beats file value on the same axis.
3. No-file noop — missing file → ``run_phase_z2_mvp1`` gets all-None.
4. Corrupt-file warn — invalid JSON / non-object → stderr warning + skip.
5. Invalid stem warn — ``Path(args.mdx_path).stem`` rejected by validator
→ warning + fallback skipped wholesale.
We exec the ``if __name__ == "__main__"`` block of
``src.phase_z2_pipeline`` directly inside the module's namespace, after
(a) monkeypatching ``src.user_overrides_io.DEFAULT_OVERRIDES_ROOT`` to a
tmp directory and (b) replacing ``run_phase_z2_mvp1`` with a recording
stub. This exercises the production fallback verbatim without the cost
of a real pipeline invocation.
"""
from __future__ import annotations
import ast
import json
import sys
from pathlib import Path
from typing import Any
import pytest
import src.phase_z2_pipeline as _pz2
import src.user_overrides_io as _io
# -- harness ---------------------------------------------------------------
def _exec_main_block(
captured: dict[str, Any], argv: list[str], monkeypatch
) -> None:
"""Run the ``__main__`` body of phase_z2_pipeline.py with a fake
``run_phase_z2_mvp1`` so its kwargs are observable."""
def _fake_run(
mdx_path,
run_id,
*,
override_layout=None,
override_frames=None,
override_zone_geometries=None,
override_section_assignments=None,
override_image_overrides=None,
):
captured["mdx_path"] = mdx_path
captured["run_id"] = run_id
captured["override_layout"] = override_layout
captured["override_frames"] = override_frames
captured["override_zone_geometries"] = override_zone_geometries
captured["override_section_assignments"] = override_section_assignments
captured["override_image_overrides"] = override_image_overrides
monkeypatch.setattr(_pz2, "run_phase_z2_mvp1", _fake_run)
monkeypatch.setattr(sys, "argv", argv)
src_path = Path(_pz2.__file__)
source = src_path.read_text(encoding="utf-8")
tree = ast.parse(source)
for node in tree.body:
if (
isinstance(node, ast.If)
and isinstance(node.test, ast.Compare)
and isinstance(node.test.left, ast.Name)
and node.test.left.id == "__name__"
):
block = ast.Module(body=node.body, type_ignores=[])
exec(compile(block, str(src_path), "exec"), _pz2.__dict__)
return
raise AssertionError("no `if __name__ == '__main__'` block found")
def _redirect_overrides_root(tmp_path: Path, monkeypatch) -> None:
"""Redirect the on-disk persistence root so tests never touch
``data/user_overrides/``."""
monkeypatch.setattr(_io, "DEFAULT_OVERRIDES_ROOT", tmp_path)
def _write_full_payload(tmp_path: Path, stem: str = "03") -> Path:
path = tmp_path / f"{stem}.json"
path.write_text(
json.dumps(
{
"layout": "sidebar-right",
"frames": {"03-1": "frame_file_a", "03-1+03-2": "frame_file_b"},
"zone_geometries": {
"top": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 0.3},
"bottom": {"x": 0.0, "y": 0.3, "w": 1.0, "h": 0.7},
},
"zone_sections": {
"top": ["03-1"],
"bottom": ["03-2", "03-3"],
},
"image_overrides": {
"img-file-a": {"x": 10.0, "y": 15.0, "w": 30.0, "h": 25.0},
"img-file-b": {"x": 50.0, "y": 50.0, "w": 40.0, "h": 40.0},
},
}
),
encoding="utf-8",
)
return path
# -- 1. no-file noop -------------------------------------------------------
def test_no_overrides_file_passes_none_overrides(tmp_path, monkeypatch):
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
assert captured["override_layout"] is None
assert captured["override_frames"] is None
assert captured["override_zone_geometries"] is None
assert captured["override_section_assignments"] is None
assert captured["override_image_overrides"] is None
# MDX path / run_id propagate untouched.
assert captured["mdx_path"] == Path("03.mdx")
assert captured["run_id"] is None
# -- 2. file fills every axis when CLI is empty ----------------------------
def test_file_only_fills_all_five_axes_when_cli_empty(tmp_path, monkeypatch):
_redirect_overrides_root(tmp_path, monkeypatch)
_write_full_payload(tmp_path, "03")
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
assert captured["override_layout"] == "sidebar-right"
assert captured["override_frames"] == {
"03-1": "frame_file_a",
"03-1+03-2": "frame_file_b",
}
assert captured["override_zone_geometries"] == {
"top": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 0.3},
"bottom": {"x": 0.0, "y": 0.3, "w": 1.0, "h": 0.7},
}
assert captured["override_section_assignments"] == {
"top": ["03-1"],
"bottom": ["03-2", "03-3"],
}
assert captured["override_image_overrides"] == {
"img-file-a": {"x": 10.0, "y": 15.0, "w": 30.0, "h": 25.0},
"img-file-b": {"x": 50.0, "y": 50.0, "w": 40.0, "h": 40.0},
}
# -- 3. CLI beats file on the same axis -----------------------------------
def test_cli_layout_overrides_file_layout(tmp_path, monkeypatch):
_redirect_overrides_root(tmp_path, monkeypatch)
_write_full_payload(tmp_path, "03")
captured: dict[str, Any] = {}
_exec_main_block(
captured,
["src.phase_z2_pipeline", "03.mdx", "--override-layout", "two-column"],
monkeypatch,
)
# layout from CLI; remaining axes still filled from file.
assert captured["override_layout"] == "two-column"
assert captured["override_frames"] == {
"03-1": "frame_file_a",
"03-1+03-2": "frame_file_b",
}
assert captured["override_zone_geometries"] is not None
assert captured["override_section_assignments"] is not None
def test_cli_frames_overrides_file_frames(tmp_path, monkeypatch):
_redirect_overrides_root(tmp_path, monkeypatch)
_write_full_payload(tmp_path, "03")
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--override-frame",
"03-1=cli_frame_x",
],
monkeypatch,
)
# CLI ``frames`` payload wholly replaces file ``frames`` (per-axis win).
assert captured["override_frames"] == {"03-1": "cli_frame_x"}
# Other axes still come from the file.
assert captured["override_layout"] == "sidebar-right"
assert captured["override_zone_geometries"] is not None
assert captured["override_section_assignments"] is not None
assert captured["override_image_overrides"] is not None
# -- 3b. CLI image override beats file image override (IMP-51 #79 u6) -----
def test_cli_image_override_overrides_file_image_overrides(tmp_path, monkeypatch):
_redirect_overrides_root(tmp_path, monkeypatch)
_write_full_payload(tmp_path, "03")
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--override-image",
"img-cli=70,80,20,15",
],
monkeypatch,
)
# CLI ``image_overrides`` payload wholly replaces file payload (per-axis).
assert captured["override_image_overrides"] == {
"img-cli": {"x": 70.0, "y": 80.0, "w": 20.0, "h": 15.0},
}
# Other axes still come from the file.
assert captured["override_layout"] == "sidebar-right"
assert captured["override_frames"] is not None
assert captured["override_zone_geometries"] is not None
assert captured["override_section_assignments"] is not None
# -- 4. corrupt / non-object file warns and skips fallback ----------------
def test_corrupt_json_warns_and_skips_fallback(tmp_path, monkeypatch, capsys):
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text("{ not valid json", encoding="utf-8")
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
err = capsys.readouterr().err
assert "failed to read" in err
# ``or None`` collapses empty dicts back to None on the call site.
assert captured["override_layout"] is None
assert captured["override_frames"] is None
assert captured["override_zone_geometries"] is None
assert captured["override_section_assignments"] is None
assert captured["override_image_overrides"] is None
def test_non_object_top_level_warns_and_skips_fallback(
tmp_path, monkeypatch, capsys
):
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text("[1, 2, 3]", encoding="utf-8")
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
err = capsys.readouterr().err
assert "not a JSON object" in err
assert captured["override_layout"] is None
assert captured["override_frames"] is None
assert captured["override_zone_geometries"] is None
assert captured["override_section_assignments"] is None
assert captured["override_image_overrides"] is None
# -- 5. invalid MDX stem warns and skips fallback wholesale ---------------
def test_invalid_mdx_stem_warns_and_skips_fallback(
tmp_path, monkeypatch, capsys
):
_redirect_overrides_root(tmp_path, monkeypatch)
# Seed a file the loader would otherwise consume; the invalid stem must
# short-circuit before any read happens.
_write_full_payload(tmp_path, "03")
captured: dict[str, Any] = {}
# ``Path(".hidden.mdx").stem`` == ".hidden" → leading dot → InvalidOverrideKey.
_exec_main_block(
captured, ["src.phase_z2_pipeline", ".hidden.mdx"], monkeypatch
)
err = capsys.readouterr().err
assert "cannot derive persistence key" in err
assert captured["override_layout"] is None
assert captured["override_frames"] is None
assert captured["override_zone_geometries"] is None
assert captured["override_section_assignments"] is None
assert captured["override_image_overrides"] is None
# -- 6. per-axis partial fill (file fills only what CLI omits) ------------
def test_per_axis_partial_fill_mixes_cli_and_file(tmp_path, monkeypatch):
"""File carries frames + zone_geometries; CLI supplies layout only.
Expected: ``override_layout`` = CLI value, ``override_frames`` and
``override_zone_geometries`` = file values, ``override_section_assignments``
= None (neither side provided it).
"""
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text(
json.dumps(
{
"frames": {"03-1": "frame_only_file"},
"zone_geometries": {
"top": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 0.5},
},
}
),
encoding="utf-8",
)
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--override-layout",
"sidebar-right",
],
monkeypatch,
)
assert captured["override_layout"] == "sidebar-right"
assert captured["override_frames"] == {"03-1": "frame_only_file"}
assert captured["override_zone_geometries"] == {
"top": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 0.5},
}
assert captured["override_section_assignments"] is None
assert captured["override_image_overrides"] is None
# -- 7. image_overrides fallback edge cases (IMP-51 #79 u6) ---------------
def test_image_overrides_fallback_drops_malformed_entries(tmp_path, monkeypatch):
"""File carries a mix of valid + malformed image_overrides entries.
Expected: valid entry survives; malformed entries (non-string id,
empty id, non-dict value, missing key, non-numeric value) are silently
dropped — no exception propagates.
"""
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text(
json.dumps(
{
"image_overrides": {
"img-valid": {"x": 1.0, "y": 2.0, "w": 3.0, "h": 4.0},
"": {"x": 1.0, "y": 2.0, "w": 3.0, "h": 4.0},
"img-not-dict": "oops",
"img-missing-h": {"x": 1.0, "y": 2.0, "w": 3.0},
"img-bad-value": {"x": "abc", "y": 2.0, "w": 3.0, "h": 4.0},
}
}
),
encoding="utf-8",
)
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
assert captured["override_image_overrides"] == {
"img-valid": {"x": 1.0, "y": 2.0, "w": 3.0, "h": 4.0},
}
def test_image_overrides_fallback_non_dict_axis_is_ignored(tmp_path, monkeypatch):
"""File ``image_overrides`` is a non-dict (list); fallback silently skips."""
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text(
json.dumps({"image_overrides": ["not", "a", "dict"]}),
encoding="utf-8",
)
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
# ``overrides_images`` stays empty; ``or None`` collapses on call site.
assert captured["override_image_overrides"] is None
def test_image_overrides_fallback_coerces_int_values_to_float(tmp_path, monkeypatch):
"""JSON-loaded ints (e.g. ``10`` not ``10.0``) must coerce to float."""
_redirect_overrides_root(tmp_path, monkeypatch)
(tmp_path / "03.json").write_text(
json.dumps(
{"image_overrides": {"img-int": {"x": 10, "y": 20, "w": 30, "h": 40}}}
),
encoding="utf-8",
)
captured: dict[str, Any] = {}
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
coerced = captured["override_image_overrides"]
assert coerced == {"img-int": {"x": 10.0, "y": 20.0, "w": 30.0, "h": 40.0}}
for axis_value in coerced["img-int"].values():
assert isinstance(axis_value, float)