"""IMP-43 (#72) u1 + u5 — focused tests for the ``--reuse-from`` CLI surface. u1 scope (per the Stage 2 Exit Report): - argparse flag ``--reuse-from PREV_RUN_ID`` parses without error. - Fail-closed precondition guard runs AFTER the ``user_overrides.json`` merge and BEFORE dispatch. With ``--reuse-from`` set, the guard must: * accept frame-only overrides (or no overrides at all); * reject layout / zone-geometry / zone-section / image overrides with ``sys.exit(2)`` whose stderr names every rejected axis. u5 scope (added 2026-05-24): - ``reuse_from`` is keyword-only on ``run_phase_z2_mvp1`` and defaults to ``None`` so the absent-flag path preserves pre-u5 behaviour. - The CLI dispatch forwards ``args.reuse_from`` verbatim — both ``None`` (flag absent) and ``"PREV_RUN_ID"`` (flag present) reach the kwarg unchanged. - The fake ``run_phase_z2_mvp1`` stub below mirrors the production signature so the forwarding lock would fail loudly on any forwarding regression. The harness mirrors ``tests/test_phase_z2_cli_overrides.py`` — the ``if __name__ == "__main__"`` block of ``src.phase_z2_pipeline`` is exec'd inside the module's namespace after monkeypatching ``run_phase_z2_mvp1`` with a recording stub. The persistence fallback is silenced by redirecting ``src.user_overrides_io.DEFAULT_OVERRIDES_ROOT`` to a clean tmp directory so persisted state from prior runs cannot bleed into the parser-only assertions here. """ from __future__ import annotations import ast 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. Captures the presence of the call (``called=True``) so guard-driven early exits can be distinguished from a successful parse + dispatch.""" 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, override_slide_css=None, reuse_from=None, ): captured["called"] = True 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 captured["override_slide_css"] = override_slide_css captured["reuse_from"] = reuse_from 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: """Isolate the persistence fallback so file state never leaks in.""" monkeypatch.setattr(_io, "DEFAULT_OVERRIDES_ROOT", tmp_path) # -- success paths -------------------------------------------------------- def test_reuse_from_alone_parses_and_dispatches(tmp_path, monkeypatch): """``--reuse-from`` with no other overrides must parse cleanly and fall through to dispatch (frame-only / empty override is allowed). u5 (2026-05-24): also asserts the CLI threads ``args.reuse_from`` verbatim into the ``reuse_from`` kwarg.""" _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", ], monkeypatch, ) assert captured.get("called") is True # u5 — verbatim threading. assert captured["reuse_from"] == "03__DX_20260508025134" def test_reuse_from_with_frame_override_dispatches(tmp_path, monkeypatch): """Frame overrides ARE preserved across Step 0/1/2/5/6 reuse, so ``--reuse-from`` + ``--override-frame`` must reach dispatch. u5: forwards both ``reuse_from`` and ``override_frames`` in the same call.""" _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-frame", "03-1=frame_foo", ], monkeypatch, ) assert captured.get("called") is True assert captured["override_frames"] == {"03-1": "frame_foo"} # u5 — frame override + reuse_from reach the kwarg simultaneously. assert captured["reuse_from"] == "03__DX_20260508025134" # -- u5 — flag-absent default + signature surface ------------------------ def test_no_reuse_from_threads_none_kwarg(tmp_path, monkeypatch): """u5 — when ``--reuse-from`` is absent, the kwarg must reach ``run_phase_z2_mvp1`` as ``None`` (not omitted, not ``""``). This locks the "default None preserves current behavior" requirement from the Stage 2 plan §u5.""" _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} _exec_main_block( captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch, ) assert captured.get("called") is True assert captured["reuse_from"] is None def test_run_phase_z2_mvp1_signature_includes_reuse_from(): """Production signature lock — ``reuse_from`` must be a keyword-only parameter with default ``None``. Mirror of the entry-tests invariant; kept here so the CLI-surface test file fails loudly if the production signature drifts away from the dispatch contract.""" import inspect sig = inspect.signature(_pz2.run_phase_z2_mvp1) assert "reuse_from" in sig.parameters, list(sig.parameters) param = sig.parameters["reuse_from"] assert param.kind is inspect.Parameter.KEYWORD_ONLY, param.kind assert param.default is None, param.default # -- fail-closed (single-axis rejection) ---------------------------------- def test_reuse_from_with_layout_override_exits(tmp_path, monkeypatch, capsys): _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-layout", "horizontal-2", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "--reuse-from incompatible with override axes" in err assert "layout" in err assert captured.get("called") is not True def test_reuse_from_with_zone_geometry_override_exits( tmp_path, monkeypatch, capsys ): _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-zone-geometry", "top=0,0,1,0.3", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "--reuse-from incompatible with override axes" in err assert "zone_geometry" in err assert captured.get("called") is not True def test_reuse_from_with_zone_section_override_exits( tmp_path, monkeypatch, capsys ): _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-section-assignment", "top=03-1", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "--reuse-from incompatible with override axes" in err assert "zone_section" in err assert captured.get("called") is not True def test_reuse_from_with_image_override_exits(tmp_path, monkeypatch, capsys): _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-image", "img-abc=10,15,30,25", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "--reuse-from incompatible with override axes" in err assert "image" in err assert captured.get("called") is not True # -- fail-closed (multi-axis aggregation) --------------------------------- def test_reuse_from_with_multiple_rejected_axes_lists_all( tmp_path, monkeypatch, capsys ): """Stderr must enumerate every rejected axis (not stop at first).""" _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", "--override-layout", "horizontal-2", "--override-zone-geometry", "top=0,0,1,0.3", "--override-image", "img-abc=10,15,30,25", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "layout" in err assert "zone_geometry" in err assert "image" in err assert captured.get("called") is not True # -- guard inactive when --reuse-from absent ------------------------------ def test_no_reuse_from_layout_override_still_dispatches( tmp_path, monkeypatch ): """Without ``--reuse-from``, the guard must be silent — existing override behaviour is preserved end-to-end.""" _redirect_overrides_root(tmp_path, monkeypatch) captured: dict[str, Any] = {} _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--override-layout", "horizontal-2", ], monkeypatch, ) assert captured.get("called") is True assert captured["override_layout"] == "horizontal-2" # -- fail-closed honours persisted overrides ------------------------------ def test_reuse_from_with_persisted_layout_override_exits( tmp_path, monkeypatch, capsys ): """The guard runs AFTER the user_overrides.json merge, so a layout persisted on disk (not on the CLI) must still reject when ``--reuse-from`` is set. This locks the Stage 2 placement rule.""" _redirect_overrides_root(tmp_path, monkeypatch) # Persist a layout override keyed by the MDX stem ``03``. overrides_dir = tmp_path overrides_dir.mkdir(parents=True, exist_ok=True) (overrides_dir / "03.json").write_text( '{"layout": "vertical-2"}', encoding="utf-8" ) captured: dict[str, Any] = {} with pytest.raises(SystemExit) as excinfo: _exec_main_block( captured, [ "src.phase_z2_pipeline", "03.mdx", "--reuse-from", "03__DX_20260508025134", ], monkeypatch, ) assert excinfo.value.code == 2 err = capsys.readouterr().err assert "--reuse-from incompatible with override axes" in err assert "layout" in err assert captured.get("called") is not True