ca9a339050
Slice-3 of the worldtree-sdk cutover: migrate the session persona-state write, the #347 authored-history write, and get_session_messages onto ratatoskr.wt, and route the first-message preset seed through the adapter. Retire the last hand-rolled sessions.py paths the --seed-first-message probe kept alive (create_session + SessionInfo, set_persona_state, write_authored_history, get_session_messages, _bifrost_error_from). - wt.set_persona_state (SDK PadState) — the CLI passes three finite PAD axes; the SDK owns the {"pad": {...}} wire (#317). No route-specific error row → the SessionApiFailed default. - wt.write_authored_history (SDK write_history) — v1 author=assistant; 404 → AuthoredHistoryUnavailable (hide-existence; the route is the discriminator, never the body); every other ApiError → the default. Drops the unused author/effects/claimed_original_at params (no caller uses them). - first_message.seed_preset_first_message now takes a WorldtreeClient and routes through wt.write_authored_history; the best-effort invariants (INV-001..004, never-raise/never-block/one-write/zero-worldtree-source-import) are unchanged. Tests drive a fake WorldtreeClient — the wire is the SDK's to prove. - CLI --set-persona-pad / --seed-first-message + the _amain and web create-path first-message seeds rewired onto the adapter. --set-persona-pad pre-validates PAD finiteness (clean usage_error, never a crash on the SDK ConfigurationError). LIVE-SMOKE on personal :8081 (b128, INV-CUT-5): --seed-first-message → 201 (seq=0, phase=seeded) → read-back verbatim; --set-persona-pad → 204; the --new create-path preset seed observed routing through the adapter. All slice-3 route families proven end-to-end through the ratatoskr surface. docs/coverage-map.md + first_message.contract.md re-anchored onto the adapter; the slice-2 create/stream/cancel rows re-anchored too (they still named the deleted sse_client/sessions symbols). Suite 466 green; mypy no new errors (baseline 22 → 20 in the touched modules); ruff clean. INV-CUT-1..5 held. Bifrost provider planes untouched.
142 lines
6.2 KiB
Python
142 lines
6.2 KiB
Python
"""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 == []
|