"""Tests for ratatoskr.sessions per docs/contracts/issues/2.contract.md.""" import httpx import pytest import respx from ratatoskr.sessions import ( SessionApiFailed, endpoint_for_plane, get_session_bifrost, ) class TestEndpointForPlane: """Issue #17 — endpoint_for_plane: plane name → Worldtree-visible base URL.""" def test_memory_plane_maps_to_8391(self) -> None: """memory [tracer]: 'memory' → http://:8391 (POST-001).""" assert endpoint_for_plane("memory", "10.100.10.50") == "http://10.100.10.50:8391" def test_affect_plane_maps_to_8390(self) -> None: """affect: 'affect' → http://:8390 (POST-001).""" assert endpoint_for_plane("affect", "10.100.10.50") == "http://10.100.10.50:8390" def test_combined_plane_maps_to_8392(self) -> None: """combined [#18 composite]: 'combined' → http://:8392 (POST-001).""" assert endpoint_for_plane("combined", "10.100.10.50") == "http://10.100.10.50:8392" def test_unknown_plane_raises_value_error(self) -> None: """unknown_plane [adversarial]: any other plane → ValueError (PRE-001).""" with pytest.raises(ValueError): endpoint_for_plane("persona", "10.100.10.50") class TestGetSessionBifrost: """#2 contract — get_session_bifrost (GET /admin/sessions/{id}/bifrost, #176).""" @respx.mock async def test_happy_uses_admin_bearer(self) -> None: """happy [happy,tracer]: 200 → binding dict; request carries the ADMIN bearer (override).""" route = respx.get("https://w.example/admin/sessions/s1/bifrost").mock( return_value=httpx.Response( 200, json={ "endpoint_url": "https://bifrost.example/mcp", "consumer_id": "alice", "connected": True, "capabilities_granted": ["tools:call", "tools:read"], "tools": [{"name": "bifrost.alice.echo", "description": "echo"}], }, ) ) async with httpx.AsyncClient( base_url="https://w.example", headers={"Authorization": "Bearer consumer-key"}, ) as client: state = await get_session_bifrost(client, "s1", admin_key="admin-xyz") assert state["connected"] is True assert state["tools"][0]["name"] == "bifrost.alice.echo" # the request overrode the client's default consumer bearer with the admin key assert route.calls[0].request.headers["Authorization"] == "Bearer admin-xyz" @respx.mock async def test_403_scope_denied(self) -> None: """403 [error]: admin key lacks admin.sessions.read → SessionApiFailed(403).""" respx.get("https://w.example/admin/sessions/s1/bifrost").mock( return_value=httpx.Response(403, json={"error_code": "auth_scope_denied"}) ) async with httpx.AsyncClient(base_url="https://w.example") as client: with pytest.raises(SessionApiFailed) as exc: await get_session_bifrost(client, "s1", admin_key="k") assert exc.value.status == 403 @respx.mock async def test_404_not_bound(self) -> None: """404 [error]: session_not_bifrost_bound → SessionApiFailed(404).""" respx.get("https://w.example/admin/sessions/s1/bifrost").mock( return_value=httpx.Response(404, json={"error_code": "session_not_bifrost_bound"}) ) async with httpx.AsyncClient(base_url="https://w.example") as client: with pytest.raises(SessionApiFailed) as exc: await get_session_bifrost(client, "s1", admin_key="k") assert exc.value.status == 404 @respx.mock async def test_empty_admin_key_asserts(self) -> None: """empty_admin_key [adversarial]: '' → AssertionError; no HTTP issued.""" route = respx.get("https://w.example/admin/sessions/s1/bifrost").mock( return_value=httpx.Response(200, json={}) ) async with httpx.AsyncClient(base_url="https://w.example") as client: with pytest.raises(AssertionError): await get_session_bifrost(client, "s1", admin_key="") assert route.call_count == 0