"""Phase 12 invariant tests (GREEN). 6 RETHROW sites addressed: - Site 1 (L276 _load_credentials): added 'from e' (Pattern 1) - Sites 2+3 (L878+L879 _default_send nested in run_with_tool_loop): added 'from None' - Site 4 (L1336 _list_anthropic_models): migrated to Result[T] (the broken 'raise ErrorInfo from exc' bug) - Site 5 (L2078 _send inside _send_gemini_cli): added 'from None' - Site 6 (L2759 _dashscope_call): added 'from None' KNOWN LIMITATION: the audit does not recognize 'raise X from e' / 'from None' as Pattern 1 (compliant). The 5 remaining RETHROW sites are classified as 'suspicious' (INTERNAL_RETHROW) but NOT 'violation' (strict mode accepts). Adding a Pattern 1 heuristic requires Tier 1 approval. """ import sys sys.path.insert(0, ".") def test_phase12_ai_client_rethrow_count_at_most_5(): """After Phase 12: ai_client RETHROW count is <= 5 (was 7 at baseline).""" import json import subprocess r = subprocess.run( ["uv", "run", "python", "scripts/audit_exception_handling.py", "--include-baseline", "--json"], capture_output=True, text=True ) data = json.loads(r.stdout) files = {f["filename"]: f for f in data["files"]} ai = files["src\\ai_client.py"] rethrow = sum(1 for x in ai["findings"] if x["category"] == "INTERNAL_RETHROW") # Phase 9 redo: -1 site (L1594 _list_gemini_models migrated to Result) # Phase 10: -1 site (BC site 1 migrated) # Phase 12: -1 site (site 4 migrated to Result) # Baseline was 7; expected <= 5 (7 - 1 - 1 - 1 = 4 actually, but Pattern 1 sites stay as RETHROW) assert rethrow <= 5, f"expected ai_client RETHROW <= 5 after Phase 12, got {rethrow}" def test_phase12_list_anthropic_models_result_exists(): """Site 4 migration: _list_anthropic_models_result helper exists.""" import src.ai_client assert hasattr(src.ai_client, "_list_anthropic_models_result") def test_phase12_legacy_functions_preserved(): """Legacy functions preserved EXCEPT _list_anthropic_models OBLITERATED by cruft-removal Phase 4.""" import src.ai_client # _default_send is nested inside run_with_tool_loop; check via getattr with fallback preserved = ("_load_credentials", "_dashscope_call") obliterated = ("_list_anthropic_models",) for name in preserved: assert hasattr(src.ai_client, name), f"{name} legacy function missing" # The nested _default_send is part of run_with_tool_loop (not a top-level attr) assert callable(getattr(src.ai_client, "run_with_tool_loop", None)), \ "run_with_tool_loop must exist (contains nested _default_send)" for name in obliterated: assert not hasattr(src.ai_client, name), ( f"{name} wrapper must be OBLITERATED (cruft-removal Phase 4); " f"callers must use {name}_result directly" )