Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
603 lines
22 KiB
Python
603 lines
22 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,
|
|
# IMP-55 (#93) u9 — mirror the live ``run_phase_z2_mvp1`` signature so
|
|
# the __main__ dispatch in src/phase_z2_pipeline.py:8332 does not raise
|
|
# TypeError on kwargs added by IMP-45 #74 (``override_slide_css``) and
|
|
# IMP-43 #72 (``reuse_from``). The u9 truth-table assertions only read
|
|
# the section-assignment axis; the new kwargs are captured here so any
|
|
# follow-up test can pin them without re-touching this harness.
|
|
override_slide_css=None,
|
|
reuse_from=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
|
|
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:
|
|
"""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"],
|
|
},
|
|
# IMP-55 (#93) u9 — seed the new ``manual_section_assignment``
|
|
# marker True so the per-axis / CLI-wins / partial-merge tests
|
|
# below continue to exercise the file→fallback path under the
|
|
# gate added in src/phase_z2_pipeline.py (``is True`` identity
|
|
# check). Truth-table coverage for False / absent / non-bool
|
|
# belongs to u10 and lives in its own test section.
|
|
"manual_section_assignment": True,
|
|
"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)
|
|
|
|
|
|
# -- 8. IMP-55 (#93) u10 — manual_section_assignment marker truth-table ----
|
|
#
|
|
# The backend file-fallback gate added in u9
|
|
# (``src/phase_z2_pipeline.py`` ``_persisted.get("manual_section_assignment")
|
|
# is True``) is exercised here with the full truth-table of marker values a
|
|
# stale or hand-edited overrides file could legally contain. The gate is
|
|
# fail-closed by ``is True`` identity, so only literal Python ``True``
|
|
# (JSON ``true``) propagates ``zone_sections`` into
|
|
# ``override_section_assignments``; everything else — absent, ``False``,
|
|
# truthy non-bool (``"true"``, ``1``, ``[]``, ``{}``), and ``None`` — must
|
|
# leave the axis as ``None`` (``or None`` collapses an empty dict on the
|
|
# call site). No hardcoding of section IDs in the assertion logic; the
|
|
# values ``03-1`` / ``03-2`` here are sample payload literals, not pinned
|
|
# behavior. ``_write_marker_payload`` reuses the IO loader by way of the
|
|
# ``__main__`` block (no direct import of the pipeline gate).
|
|
|
|
_MARKER_ABSENT = object()
|
|
|
|
|
|
def _write_marker_payload(
|
|
tmp_path: Path, marker: Any, stem: str = "03"
|
|
) -> Path:
|
|
"""Write a minimal overrides file with ``zone_sections`` + optional marker.
|
|
|
|
``marker is _MARKER_ABSENT`` → omit ``manual_section_assignment`` key.
|
|
Any other value (including ``None``) → write it verbatim as JSON.
|
|
"""
|
|
payload: dict[str, Any] = {
|
|
"zone_sections": {"top": ["03-1"], "bottom": ["03-2"]},
|
|
}
|
|
if marker is not _MARKER_ABSENT:
|
|
payload["manual_section_assignment"] = marker
|
|
path = tmp_path / f"{stem}.json"
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def test_marker_true_fills_section_assignments(tmp_path, monkeypatch):
|
|
"""marker=True + zone_sections in file + CLI empty → file value flows."""
|
|
_redirect_overrides_root(tmp_path, monkeypatch)
|
|
_write_marker_payload(tmp_path, True, "03")
|
|
|
|
captured: dict[str, Any] = {}
|
|
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
|
|
|
|
assert captured["override_section_assignments"] == {
|
|
"top": ["03-1"],
|
|
"bottom": ["03-2"],
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"marker",
|
|
[
|
|
_MARKER_ABSENT,
|
|
False,
|
|
"true", # JSON-string truthy must NOT pass the ``is True`` gate.
|
|
1, # int truthy must NOT pass.
|
|
[],
|
|
{},
|
|
None,
|
|
],
|
|
ids=["absent", "false", "string_true", "int_one", "empty_list", "empty_dict", "null"],
|
|
)
|
|
def test_marker_non_true_skips_section_assignments(
|
|
tmp_path, monkeypatch, marker
|
|
):
|
|
"""marker absent / False / non-bool → gate fail-closed → axis is None.
|
|
|
|
Even though ``zone_sections`` is present in the file, the gate refuses
|
|
to forward it because ``manual_section_assignment`` is not literal
|
|
``True``. ``or None`` on the call site collapses the empty dict back
|
|
to ``None``.
|
|
"""
|
|
_redirect_overrides_root(tmp_path, monkeypatch)
|
|
_write_marker_payload(tmp_path, marker, "03")
|
|
|
|
captured: dict[str, Any] = {}
|
|
_exec_main_block(captured, ["src.phase_z2_pipeline", "03.mdx"], monkeypatch)
|
|
|
|
assert captured["override_section_assignments"] is None
|
|
|
|
|
|
# -- 9. IMP-55 (#93) u11 — CLI ``--override-section-assignment`` wins over
|
|
# persisted manual-marker fallback ----------------------------------------
|
|
#
|
|
# The u9 gate only fires on the file→fallback branch
|
|
# (``not overrides_section_assignments and _persisted.get(...) is True``).
|
|
# When the CLI supplies ``--override-section-assignment`` the
|
|
# ``overrides_section_assignments`` dict is truthy before the gate is
|
|
# evaluated, so the persisted ``zone_sections`` axis (and the
|
|
# ``manual_section_assignment`` marker that would otherwise unlock it) is
|
|
# bypassed entirely — CLI is authoritative on the section-assignment
|
|
# axis. The three cases below pin this contract:
|
|
#
|
|
# 1. CLI + persisted marker True (file value present) → CLI wins.
|
|
# 2. CLI + persisted marker False (file value present) → CLI wins; the
|
|
# marker is irrelevant on the CLI-wins path (no truth-value coupling).
|
|
# 3. CLI with no overrides file at all → CLI value flows through; the
|
|
# marker is a gate on file→fallback only, never a precondition for
|
|
# any CLI ``cli_override`` to take effect.
|
|
#
|
|
# Cross-axis bystanders (layout / frames / geometries / images) are
|
|
# intentionally not seeded here; this section locks CLI-vs-marker
|
|
# semantics on the section axis only. Cross-axis CLI-wins behavior is
|
|
# already covered by the IMP-52 #80 tests in section 3 / 3b above.
|
|
|
|
|
|
def test_cli_section_assignment_wins_over_persisted_marker_true(
|
|
tmp_path, monkeypatch
|
|
):
|
|
"""marker=True + file zone_sections + CLI value → CLI wins per-axis."""
|
|
_redirect_overrides_root(tmp_path, monkeypatch)
|
|
_write_marker_payload(tmp_path, True, "03")
|
|
|
|
captured: dict[str, Any] = {}
|
|
_exec_main_block(
|
|
captured,
|
|
[
|
|
"src.phase_z2_pipeline",
|
|
"03.mdx",
|
|
"--override-section-assignment",
|
|
"top=cli-section",
|
|
],
|
|
monkeypatch,
|
|
)
|
|
|
|
# CLI value wholly replaces the file zone_sections (per-axis win); the
|
|
# gate's ``not overrides_section_assignments`` precondition is false.
|
|
assert captured["override_section_assignments"] == {"top": ["cli-section"]}
|
|
|
|
|
|
def test_cli_section_assignment_wins_with_persisted_marker_false(
|
|
tmp_path, monkeypatch
|
|
):
|
|
"""marker=False + file zone_sections + CLI value → CLI wins; marker unread."""
|
|
_redirect_overrides_root(tmp_path, monkeypatch)
|
|
_write_marker_payload(tmp_path, False, "03")
|
|
|
|
captured: dict[str, Any] = {}
|
|
_exec_main_block(
|
|
captured,
|
|
[
|
|
"src.phase_z2_pipeline",
|
|
"03.mdx",
|
|
"--override-section-assignment",
|
|
"bottom=cli-section",
|
|
],
|
|
monkeypatch,
|
|
)
|
|
|
|
assert captured["override_section_assignments"] == {"bottom": ["cli-section"]}
|
|
|
|
|
|
def test_cli_section_assignment_works_without_persisted_file(
|
|
tmp_path, monkeypatch
|
|
):
|
|
"""No overrides file → CLI value flows; marker is not a CLI precondition."""
|
|
_redirect_overrides_root(tmp_path, monkeypatch)
|
|
|
|
captured: dict[str, Any] = {}
|
|
_exec_main_block(
|
|
captured,
|
|
[
|
|
"src.phase_z2_pipeline",
|
|
"03.mdx",
|
|
"--override-section-assignment",
|
|
"top=cli-only",
|
|
],
|
|
monkeypatch,
|
|
)
|
|
|
|
assert captured["override_section_assignments"] == {"top": ["cli-only"]}
|