"""Tests for ratatoskr.first_message per docs/contracts/first_message.contract.md. Slice-3 (worldtree-sdk cutover): `seed_preset_first_message` routes through the `ratatoskr.wt` adapter over a `WorldtreeClient`, no longer the hand-rolled httpx wrapper. These tests drive it through a fake client whose `sessions.write_history` returns or raises the SDK's real types — exercising the adapter's error mapping AND first_message's best-effort swallow in one pass. The wire format itself is the SDK's to prove (the parity corpus); first_message's contract is behavioral: never block, never raise (except CancelledError), and exactly one write on a preset hit. """ import asyncio import hashlib from typing import Any, cast import pytest import worldtree_sdk as wtsdk from worldtree_sdk import ApiError, WorldtreeClient from ratatoskr.first_message import ( FIRST_MESSAGE_PRESETS, preset_for, seed_preset_first_message, ) class _FakeSessions: """Stand-in for `WorldtreeClient.sessions` — records each `write_history` call and returns a canned ack or raises a canned error (the SDK's real exceptions).""" def __init__(self, *, result: Any = None, error: BaseException | None = None) -> None: self._result = result if result is not None else {} self._error = error self.calls: list[tuple[str, Any]] = [] async def write_history(self, session_id: str, entry: Any) -> Any: self.calls.append((session_id, entry)) if self._error is not None: raise self._error return self._result class _FakeClient: def __init__(self, sessions: _FakeSessions) -> None: self.sessions = sessions def _wt(sessions: _FakeSessions) -> WorldtreeClient: """Cast the structural fake to the nominal client type (no network; the seed path only touches `client.sessions.write_history`, which the fake provides).""" return cast(WorldtreeClient, _FakeClient(sessions)) class TestPresetFor: """first_message contract — preset_for (dict lookup).""" def test_preset_hit(self) -> None: """preset_hit [happy,tracer]: sindra has a non-empty str preset.""" val = preset_for("ratatoskr:sindra") assert isinstance(val, str) and val def test_preset_miss(self) -> None: """preset_miss [happy]: an agent with no preset → None.""" assert preset_for("mimir") is None def test_empty_agent_id(self) -> None: """empty_agent_id [adversarial]: "" → AssertionError.""" with pytest.raises(AssertionError): preset_for("") class TestSeedPresetFirstMessage: """first_message contract — seed_preset_first_message (best-effort #347 seed).""" async def test_seeds_preset(self) -> None: """seeds_preset [happy,tracer]: preset agent → one write_history, correct entry.""" content = FIRST_MESSAGE_PRESETS["ratatoskr:sindra"] key = "ratatoskr-preset-" + hashlib.sha256(content.encode("utf-8")).hexdigest()[:12] fake = _FakeSessions(result={"seq": 0, "phase": "seeded"}) result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") assert result == content assert len(fake.calls) == 1 # POST-002: exactly one history write session_id, entry = fake.calls[0] assert session_id == "s1" assert entry == { "author": "assistant", "content": content, "idempotency_key": key, } async def test_no_preset_zero_write(self) -> None: """no_preset_zero_write [happy]: no-preset agent → None, ZERO write (INV-002).""" fake = _FakeSessions() result = await seed_preset_first_message(_wt(fake), "s1", "mimir") assert result is None assert fake.calls == [] async def test_feature_absent_swallowed(self) -> None: """feature_absent_swallowed [error]: 404 → AuthoredHistoryUnavailable → None (INV-001).""" fake = _FakeSessions(error=ApiError("session_not_found", "no", status=404)) result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") assert result is None assert len(fake.calls) == 1 # the write was attempted, then swallowed async def test_session_api_failed_swallowed(self) -> None: """session_api_failed_swallowed [error]: 409 → SessionApiFailed → None (INV-001).""" fake = _FakeSessions(error=ApiError("generation_active", "busy", status=409)) result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") assert result is None async def test_transport_error_swallowed(self) -> None: """transport_error_swallowed [error]: SDK ConnectFailed → None, no raise (INV-001).""" fake = _FakeSessions(error=wtsdk.ConnectFailed("connect_failed", "boom", status=0)) result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") assert result is None async def test_unexpected_exception_swallowed(self) -> None: """unexpected_exception [error]: write raises ValueError → None (broad never-raise).""" fake = _FakeSessions(error=ValueError("unexpected")) result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") assert result is None async def test_cancellation_propagates(self) -> None: """cancellation_propagates [error]: CancelledError from the write is RE-RAISED.""" fake = _FakeSessions(error=asyncio.CancelledError()) with pytest.raises(asyncio.CancelledError): await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra") async def test_malformed_agent_id_no_write(self) -> None: """malformed_agent_id [adversarial]: non-str or empty agent_id → None; no write; no raise.""" fake = _FakeSessions() assert await seed_preset_first_message(_wt(fake), "s1", 123) is None # type: ignore[arg-type] assert await seed_preset_first_message(_wt(fake), "s1", "") is None assert fake.calls == [] async def test_empty_session_id(self) -> None: """empty_session_id [adversarial]: "" → None (soft guard); no write; no raise.""" fake = _FakeSessions() result = await seed_preset_first_message(_wt(fake), "", "ratatoskr:sindra") assert result is None assert fake.calls == []