- u1: BuilderMissingError(FitError) — narrow exception aligned with pipeline catch - u2: load_frame_contracts catalog invariant + VP skip + CatalogInvariantError - u3a: audit CLI I1~I3 (partial existence / declared builder / registry membership) - u3b: audit CLI I4 (slot_payload refs vs declared/generated payload keys) - u4: lookup_v4_candidates VP filter (lookup_v4_all_judgments raw telemetry untouched) - u5: catalog invariant regression coverage + temp non-VP failure fixtures - u6: mdx04 VP routing fixture tests (sw_dependency_four_problems excluded from live) - u7: tests/conftest.py env isolation + mdx03/mdx04/mdx05 subprocess smoke Targeted 74 PASS (12.31s). Full regression 1063 PASS (87.70s). Audit CLI clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
"""IMP-#85 u1 — mapper missing-builder dispatch raises BuilderMissingError.
|
|
|
|
Scope (Stage 2 lock):
|
|
- `BuilderMissingError` exists and is a subclass of `FitError`.
|
|
- `map_with_contract` raises `BuilderMissingError` when
|
|
`contract.payload.builder` references an unknown registry entry, OR
|
|
when `payload.builder` is empty/missing.
|
|
- Because it subclasses `FitError`, the existing pipeline
|
|
`except FitError` route in `src/phase_z2_pipeline.py` continues to
|
|
catch the failure and emit an `adapter_needed` record instead of a
|
|
hard crash (mdx04 `sw_dependency_four_problems` / `cards_4_grid`
|
|
regression evidence).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from src.phase_z2_mapper import (
|
|
BuilderMissingError,
|
|
FitError,
|
|
PAYLOAD_BUILDERS,
|
|
map_with_contract,
|
|
)
|
|
|
|
|
|
def _make_section(raw_content: str = "- a\n- b\n- c"):
|
|
return SimpleNamespace(
|
|
section_id="test-sec",
|
|
raw_content=raw_content,
|
|
title="t",
|
|
order=1,
|
|
)
|
|
|
|
|
|
def test_builder_missing_error_is_fit_error_subclass():
|
|
assert issubclass(BuilderMissingError, FitError)
|
|
|
|
|
|
def test_unknown_builder_raises_builder_missing_error():
|
|
unknown = "definitely_not_a_registered_builder"
|
|
assert unknown not in PAYLOAD_BUILDERS
|
|
contract = {
|
|
"template_id": "fake_contract_unknown_builder",
|
|
"source_shape": "top_bullets",
|
|
"cardinality": {},
|
|
"payload": {"builder": unknown},
|
|
}
|
|
with pytest.raises(BuilderMissingError) as exc:
|
|
map_with_contract(_make_section(), contract)
|
|
assert unknown in str(exc.value)
|
|
assert "fake_contract_unknown_builder" in str(exc.value)
|
|
|
|
|
|
def test_missing_builder_field_raises_builder_missing_error():
|
|
contract = {
|
|
"template_id": "fake_contract_missing_builder_field",
|
|
"source_shape": "top_bullets",
|
|
"cardinality": {},
|
|
"payload": {},
|
|
}
|
|
with pytest.raises(BuilderMissingError) as exc:
|
|
map_with_contract(_make_section(), contract)
|
|
assert "missing payload.builder" in str(exc.value)
|
|
|
|
|
|
def test_builder_missing_error_caught_by_fit_error_handler():
|
|
"""Pipeline 의 `except FitError` 경로가 그대로 잡아주는지 검증.
|
|
|
|
실제 pipeline import 없이 동일 패턴을 재현하여 subclass 의 의도된
|
|
routing 효과(adapter_needed) 가 깨지지 않는지 확인.
|
|
"""
|
|
contract = {
|
|
"template_id": "fake_contract_routing_check",
|
|
"source_shape": "top_bullets",
|
|
"cardinality": {},
|
|
"payload": {"builder": "no_such_builder"},
|
|
}
|
|
caught = False
|
|
try:
|
|
map_with_contract(_make_section(), contract)
|
|
except FitError:
|
|
caught = True
|
|
assert caught, "BuilderMissingError must propagate through `except FitError`"
|