"""Unit tests for the worldtree-sdk adapter (`ratatoskr.wt`) — slice-1 foundation. Covers the two foundation surfaces (issue #20 cutover contract, slice 1): * `build_client` — construction wiring + injected-transport ownership (INV-CUT-1: the SDK must never close ratatoskr's transport). * `translate_error` — the § Error map DEFAULT (`ApiError` → `SessionApiFailed`) plus discriminated-`WorldtreeError` passthrough (INV-CUT-2). No ratatoskr surface (CLI / web / TUI) is exercised here — that wiring lands in slice 2. These tests hit no network (WorldtreeClient does no I/O at construction). """ from __future__ import annotations import httpx from worldtree_sdk import AgentNotAvailable, ApiError, WorldtreeClient from ratatoskr.wt import SessionApiFailed, build_client, translate_error class TestBuildClient: async def test_constructs_worldtree_client(self) -> None: transport = httpx.AsyncClient() try: client = build_client( "https://wt.example:8081", api_key="ck-test", transport=transport ) assert isinstance(client, WorldtreeClient) assert client.base_url == "https://wt.example:8081" finally: await transport.aclose() async def test_injected_transport_is_ratatoskr_owned(self) -> None: # INV-CUT-1 [hard]: aclose() on the SDK client must NOT close ratatoskr's # transport — ratatoskr owns the lifecycle exactly as it does today. transport = httpx.AsyncClient() client = build_client("https://wt.example", api_key="ck", transport=transport) await client.aclose() assert client.closed is True assert transport.is_closed is False await transport.aclose() async def test_admin_key_optional(self) -> None: transport = httpx.AsyncClient() try: # Absent admin_key → admin_auth=None; still constructs. without_admin = build_client( "https://wt.example", api_key="ck", transport=transport ) assert isinstance(without_admin, WorldtreeClient) # Present admin_key → constructs (admin surface available in later slices). with_admin = build_client( "https://wt.example", api_key="ck", admin_key="ak", transport=transport ) assert isinstance(with_admin, WorldtreeClient) finally: await transport.aclose() class TestTranslateError: def test_apierror_maps_to_session_api_failed_default(self) -> None: exc = ApiError("some_code", "boom", status=500, body="raw-body") mapped = translate_error(exc) assert isinstance(mapped, SessionApiFailed) assert mapped.status == 500 assert mapped.error_code == "some_code" assert mapped.body == "raw-body" def test_apierror_with_no_body_maps_cleanly(self) -> None: exc = ApiError("nope", "no body", status=404) mapped = translate_error(exc) assert isinstance(mapped, SessionApiFailed) assert mapped.status == 404 assert mapped.error_code == "nope" assert mapped.body is None def test_discriminated_subclass_passes_through_unchanged(self) -> None: # Discriminated WorldtreeError subclasses are already the right semantic # type — the adapter passes them through by identity (no re-wrap). exc = AgentNotAvailable("agent_not_available", "gone", status=409) assert translate_error(exc) is exc def test_non_worldtree_error_passes_through_unchanged(self) -> None: exc = ValueError("unrelated") assert translate_error(exc) is exc