"""IMP-06 zone-section assignment override — helper unit tests (synthetic). Lock per Claude #6 §4 L13 + Codex #2 R3 6 cases + 자체 catch 7-10 : 9 cases covering parse / helper assignment / collision / template ladder. Fully synthetic per Codex #7 generalization guardrail (MOCK_ prefix). NO real catalog template_id / frame_id, NO `v4_full32_result.yaml` dependency. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Optional import pytest from src.phase_z2_pipeline import _build_position_assignment_plan # ─── Synthetic fixtures ────────────────────────────────────────── @dataclass class _FakeUnit: """Synthetic CompositionUnit stand-in. Only fields the helper reads.""" source_section_ids: list[str] template_id: Optional[str] = None frame_template_id: Optional[str] = None @dataclass class _FakeSection: """Synthetic MdxSection stand-in. Only fields the helper reads.""" section_id: str raw_content: str = "- item A\n- item B\n" # ─── Case 1 : single override + non-conflicting auto retain ───────────── def test_single_zone_override_retains_non_conflicting_auto(): """Claude #6 L13 case 4 — single override on `top`, auto units do not overlap. Expected: top = override unit; bottom = auto unit retained. """ units = [ _FakeUnit(source_section_ids=["MOCK_S1"], frame_template_id="MOCK_T_auto_top"), _FakeUnit(source_section_ids=["MOCK_S2"], frame_template_id="MOCK_T_auto_bottom"), ] positions = ["top", "bottom"] overrides = {"top": ["MOCK_S3"]} sections_by_id = {"MOCK_S3": _FakeSection("MOCK_S3")} override_frames = {"MOCK_S3": "MOCK_T_for_S3"} # ladder step 1 plan, summary = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) by_pos = {p["position"]: p for p in plan} assert by_pos["top"]["assignment_source"] == "cli_override" assert by_pos["top"]["source_section_ids"] == ["MOCK_S3"] assert by_pos["top"]["template_id"] == "MOCK_T_for_S3" assert by_pos["bottom"]["assignment_source"] == "auto" assert by_pos["bottom"]["source_section_ids"] == ["MOCK_S2"] assert summary["applied_count"] == 1 assert summary["skipped_count"] == 0 # ─── Case 2 : collision — override wins, auto whole-skipped ────────────── def test_override_collision_whole_skip_no_split_uncovered_traced(): """Codex #2 R1 example : override section overlaps an auto merged unit. Expected: override wins; auto [MOCK_S1, MOCK_S2] skipped whole (no split); MOCK_S2 reported as uncovered. """ auto_merged = _FakeUnit( source_section_ids=["MOCK_S1", "MOCK_S2"], frame_template_id="MOCK_T_merged", ) auto_solo = _FakeUnit(source_section_ids=["MOCK_S3"], frame_template_id="MOCK_T_solo") units = [auto_merged, auto_solo] positions = ["top", "bottom"] overrides = {"top": ["MOCK_S1"]} sections_by_id = {sid: _FakeSection(sid) for sid in ["MOCK_S1", "MOCK_S2", "MOCK_S3"]} override_frames = {"MOCK_S1": "MOCK_T_override_S1"} plan, summary = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) by_pos = {p["position"]: p for p in plan} # top : override wins; auto_merged that sat at top is replaced. assert by_pos["top"]["assignment_source"] == "cli_override" assert by_pos["top"]["source_section_ids"] == ["MOCK_S1"] # No split : MOCK_S2 (the other half of the auto merged unit) is NOT # re-extracted into the replaced position; instead it surfaces as uncovered. assert by_pos["top"]["previous_source_section_ids"] == ["MOCK_S1", "MOCK_S2"] assert by_pos["top"]["uncovered_section_ids"] == ["MOCK_S2"] # bottom : non-overlapping auto_solo is retained (no collision). assert by_pos["bottom"]["assignment_source"] == "auto" assert by_pos["bottom"]["source_section_ids"] == ["MOCK_S3"] # Summary aggregates MOCK_S2 as the global uncovered section. assert summary["uncovered_section_ids"] == ["MOCK_S2"] # ─── Case 3 : template ladder step 1 (override_frames wins) ───────────── def test_template_resolution_ladder_step1_override_frame_wins(): """Codex #4 T1 ladder step 1 : --override-frame exact unit_id wins.""" units = [_FakeUnit(source_section_ids=["MOCK_S1"], frame_template_id="MOCK_T_auto")] positions = ["top"] overrides = {"top": ["MOCK_S1"]} sections_by_id = {"MOCK_S1": _FakeSection("MOCK_S1")} override_frames = {"MOCK_S1": "MOCK_T_explicit_override"} plan, _ = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) assert plan[0]["template_id"] == "MOCK_T_explicit_override" assert plan[0]["skipped_reason"] is None # ─── Case 4 : template ladder step 2 (exact auto unit reuse) ──────────── def test_template_resolution_ladder_step2_exact_auto_reuse(): """Codex #4 T1 ladder step 2 : no override_frame; exact existing auto unit -> reuse.""" units = [_FakeUnit(source_section_ids=["MOCK_S1", "MOCK_S2"], frame_template_id="MOCK_T_auto_merged")] positions = ["top"] overrides = {"top": ["MOCK_S1", "MOCK_S2"]} sections_by_id = {sid: _FakeSection(sid) for sid in ["MOCK_S1", "MOCK_S2"]} plan, _ = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=None, # no explicit frame override ) # ladder step 2 hits : exact auto unit [MOCK_S1, MOCK_S2] reuse assert plan[0]["template_id"] == "MOCK_T_auto_merged" assert plan[0]["skipped_reason"] is None # ─── Case 5 : template ladder step 4 (ad-hoc multi-section fail) ───────── def test_template_resolution_ladder_step4_ad_hoc_multi_section_fail(): """Codex #4 Additional lock : ad-hoc multi-section override without exact auto + without explicit --override-frame -> skipped_reason = 'ad_hoc_merged_no_template'. """ units = [ _FakeUnit(source_section_ids=["MOCK_S1"], frame_template_id="MOCK_T_a"), _FakeUnit(source_section_ids=["MOCK_S2"], frame_template_id="MOCK_T_b"), ] positions = ["top"] overrides = {"top": ["MOCK_S1", "MOCK_S2"]} # ad-hoc merge, no exact auto sections_by_id = {sid: _FakeSection(sid) for sid in ["MOCK_S1", "MOCK_S2"]} plan, _ = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=None, ) assert plan[0]["template_id"] is None assert plan[0]["skipped_reason"] == "ad_hoc_merged_no_template" # ─── Case 6 : unit_id naming convention ───────────────────────────────── def test_unit_id_naming_convention_consistent_for_auto_and_override(): """Codex T2 + Claude #4 catch 8 : unit_id = '+'.join(source_section_ids).""" units = [ _FakeUnit(source_section_ids=["MOCK_S1", "MOCK_S2"], frame_template_id="MOCK_T_merged"), ] positions = ["top", "bottom"] overrides = {"bottom": ["MOCK_S3"]} sections_by_id = {"MOCK_S3": _FakeSection("MOCK_S3")} override_frames = {"MOCK_S3": "MOCK_T_S3"} plan, _ = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) by_pos = {p["position"]: p for p in plan} # auto merged unit assert by_pos["top"]["unit_id"] == "MOCK_S1+MOCK_S2" # single-section override assert by_pos["bottom"]["unit_id"] == "MOCK_S3" # ─── Case 7 : previous_source_section_ids semantics (position history) ── def test_previous_source_section_ids_records_same_position_auto_history(): """Codex T3 + Claude #4 catch 9 : previous_source_section_ids = the auto assignment that occupied the SAME position before override applied. """ units = [ _FakeUnit(source_section_ids=["MOCK_S1"], frame_template_id="MOCK_T_a"), _FakeUnit(source_section_ids=["MOCK_S2"], frame_template_id="MOCK_T_b"), ] positions = ["top", "bottom"] overrides = {"top": ["MOCK_S3"]} sections_by_id = {"MOCK_S3": _FakeSection("MOCK_S3")} override_frames = {"MOCK_S3": "MOCK_T_S3"} plan, _ = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) by_pos = {p["position"]: p for p in plan} # top : auto WAS MOCK_S1 before override assert by_pos["top"]["previous_source_section_ids"] == ["MOCK_S1"] # ─── Case 8 : empty position when no auto unit available ─────────────── def test_position_with_no_auto_unit_marked_empty(): """When `units` has fewer entries than `positions`, extra positions = empty.""" units = [_FakeUnit(source_section_ids=["MOCK_S1"], frame_template_id="MOCK_T_a")] positions = ["top", "bottom"] # bottom has no auto unit sections_by_id = {"MOCK_S1": _FakeSection("MOCK_S1")} plan, summary = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=None, sections_by_id=sections_by_id, override_frames=None, ) by_pos = {p["position"]: p for p in plan} assert by_pos["bottom"]["assignment_source"] == "empty" assert by_pos["bottom"]["skipped_reason"] == "no_auto_unit_available" assert summary["applied_count"] == 0 # ─── Case 9 : summary aggregation invariants ─────────────────────────── def test_summary_aggregation_counts_applied_skipped_uncovered(): """Claude #6 L12 single source of truth : summary derives from plan.""" auto_merged = _FakeUnit( source_section_ids=["MOCK_S1", "MOCK_S2"], frame_template_id="MOCK_T_merged", ) units = [auto_merged] positions = ["top", "bottom"] # Two overrides : top=MOCK_S1 (causes collision with auto_merged at bottom) overrides = {"top": ["MOCK_S1"]} sections_by_id = {sid: _FakeSection(sid) for sid in ["MOCK_S1", "MOCK_S2"]} override_frames = {"MOCK_S1": "MOCK_T_override_S1"} plan, summary = _build_position_assignment_plan( units=units, positions=positions, override_section_assignments=overrides, sections_by_id=sections_by_id, override_frames=override_frames, ) # Summary derives from plan : 1 applied, 1 collision skip, 1 uncovered. assert summary["applied_count"] == 1 assert summary["skipped_count"] >= 1 # collision skip assert summary["uncovered_section_ids"] == ["MOCK_S2"] assert summary["section_assignment_overrides_applied"][0]["position"] == "top"