"""IMP-38 U1 — v4_fallback_policy.yaml loader test. Verify: - load_v4_fallback_policy() returns dict with expected keys - yaml parsed correctly (usable_threshold, default_max_rank, extended_max_rank, policy_type) - graceful fallback when yaml missing → _V4_FALLBACK_POLICY_DEFAULT - _V4_FALLBACK_POLICY_CACHE pattern (lazy load, mirror of _CATALOG_CACHE) - load_frame_contracts() shape unchanged (separate yaml, catalog 오염 X) 4 round 합의 (#67): - Codex #1: separate yaml (not frame_contracts.yaml top-level) - Codex #3: load_frame_contracts() shape 변경 X """ from __future__ import annotations import importlib from pathlib import Path from unittest.mock import patch import pytest PROJECT_ROOT = Path(__file__).parent.parent V4_POLICY_PATH = PROJECT_ROOT / "templates" / "phase_z2" / "catalog" / "v4_fallback_policy.yaml" CATALOG_PATH = PROJECT_ROOT / "templates" / "phase_z2" / "catalog" / "frame_contracts.yaml" def _reset_caches(): """Reset module-level caches for test isolation.""" import src.phase_z2_mapper as mapper mapper._V4_FALLBACK_POLICY_CACHE = None mapper._CATALOG_CACHE = None @pytest.fixture(autouse=True) def clean_caches(): _reset_caches() yield _reset_caches() def test_v4_fallback_policy_yaml_exists(): """IMP-38 U1 — separate yaml file must exist.""" assert V4_POLICY_PATH.exists(), ( f"v4_fallback_policy.yaml not found at {V4_POLICY_PATH}. " "IMP-38 U1 expects separate yaml (Codex #1 corr — not frame_contracts.yaml top-level)." ) def test_load_v4_fallback_policy_returns_dict_with_expected_keys(): """load_v4_fallback_policy() must return dict with policy keys.""" from src.phase_z2_mapper import load_v4_fallback_policy policy = load_v4_fallback_policy() assert isinstance(policy, dict) expected_keys = {"policy_type", "usable_threshold", "default_max_rank", "extended_max_rank"} missing = expected_keys - set(policy.keys()) assert not missing, f"missing keys in v4_fallback_policy: {missing}" def test_load_v4_fallback_policy_values_match_yaml(): """Loaded policy values must match v4_fallback_policy.yaml (initial commit).""" from src.phase_z2_mapper import load_v4_fallback_policy policy = load_v4_fallback_policy() assert policy["policy_type"] == "dynamic_usable_count_based" assert policy["usable_threshold"] == 1 assert policy["default_max_rank"] == 3 assert policy["extended_max_rank"] == 32 def test_load_v4_fallback_policy_cache_pattern(): """_V4_FALLBACK_POLICY_CACHE pattern — second call returns same dict (lazy load).""" from src.phase_z2_mapper import load_v4_fallback_policy policy_a = load_v4_fallback_policy() policy_b = load_v4_fallback_policy() assert policy_a is policy_b, "cache pattern violated (should return same dict instance)" def test_load_v4_fallback_policy_graceful_when_yaml_missing(): """yaml 파일 없을 시 → _V4_FALLBACK_POLICY_DEFAULT (extended_max_rank=3, byte-identical pre-IMP-38).""" import src.phase_z2_mapper as mapper with patch.object(mapper, "V4_FALLBACK_POLICY_PATH", PROJECT_ROOT / "tests" / "__nonexistent_policy.yaml"): # reset cache to force reload via patched path mapper._V4_FALLBACK_POLICY_CACHE = None policy = mapper.load_v4_fallback_policy() assert policy["default_max_rank"] == 3 assert policy["extended_max_rank"] == 3, ( "graceful fallback must keep extended==default (byte-identical pre-IMP-38)" ) def test_load_frame_contracts_shape_unchanged(): """Codex #3 LOCK — load_frame_contracts() must still return template_id → entry dict.""" from src.phase_z2_mapper import load_frame_contracts, load_v4_fallback_policy catalog = load_frame_contracts() policy = load_v4_fallback_policy() # catalog 의 key 가 모두 frame entry (dict with template_id/frame_id) 여야 함 for key, entry in catalog.items(): assert isinstance(entry, dict), f"catalog entry {key} should be dict" assert "template_id" in entry, f"catalog entry {key} missing template_id (policy bleed?)" # policy keys 는 catalog 에 안 들어감 policy_keys = {"policy_type", "usable_threshold", "default_max_rank", "extended_max_rank"} catalog_top_keys = set(catalog.keys()) bleed = policy_keys & catalog_top_keys assert not bleed, ( f"policy keys leaked into frame_contracts.yaml: {bleed}. " "Codex #1 corr violated — policy must stay in separate v4_fallback_policy.yaml." )