"""CLI integration tests for `python -m ratatoskr.tier3`. The define/patch/delete wire calls route through the worldtree-sdk adapter (`ratatoskr.wt`); the adapter's body-building + error-mapping are unit-tested in `test_wt.py` (against a fake `client.agents`). These tests exercise the CLI end-to-end — argv → the real SDK over a respx-mocked HTTP layer → exit code + stdout + the local tier3 index side effects. The define/patch response echoes `role` (Worldtree spec 1.2 / b128), read off the SDK's open-world dict; `LocalAgentEntry.role` is the v2-schema field. """ from pathlib import Path import httpx import pytest import respx from ratatoskr.tier3 import main # The consumer-agent response echoes `role` (b128), not the former `model`. _FULL_AGENT_RESP = { "agent_id": "ratatoskr:wizard", "user_id": "ratatoskr", "agent_name": "wizard", "system_prompt": "You are a wizard.", "role": "qwen3.6-35-a3b", "created_at": "2026-05-25T03:20:09.703601+00:00", "updated_at": "2026-05-25T03:20:09.703601+00:00", } @pytest.fixture def _isolated_local_agents( tmp_path: "Path", monkeypatch: pytest.MonkeyPatch ) -> "Path": """Isolate the v0.8.0 local-tier-3 index from the operator's real file.""" path = tmp_path / "local_agents.json" monkeypatch.setenv("RATATOSKR_LOCAL_AGENTS", str(path)) return path class TestCli: @respx.mock def test_cli_define_happy( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, _isolated_local_agents: "Path", ) -> None: """cli_define_happy [happy,tracer]: argv → 201 mock → stdout confirmation; local index updated with the new entry (role echoed). """ from ratatoskr.local_agents import load_local_agents monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( return_value=httpx.Response(201, json=_FULL_AGENT_RESP) ) rc = main([ "define", "--name", "wizard", "--system-prompt", "You are a wizard.", "--role", "qwen3.6-35-a3b", ]) out = capsys.readouterr() assert rc == 0 assert out.out.strip() == "defined ratatoskr:wizard (qwen3.6-35-a3b)" entries = load_local_agents() assert len(entries) == 1 assert entries[0].agent_id == "ratatoskr:wizard" assert entries[0].role == "qwen3.6-35-a3b" @respx.mock def test_cli_define_body_shape( self, monkeypatch: pytest.MonkeyPatch, _isolated_local_agents: "Path" ) -> None: """cli_define_body_shape [trace]: outbound JSON is exactly the three keys (the adapter sends AgentDefineInput, no layer fields).""" import json as _json monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") route = respx.post("https://w.example/agents/define").mock( return_value=httpx.Response(201, json=_FULL_AGENT_RESP) ) main([ "define", "--name", "wizard", "--system-prompt", "You are a wizard.", "--role", "qwen3.6-35-a3b", ]) body = _json.loads(route.calls[0].request.content) assert body == { "agent_name": "wizard", "role": "qwen3.6-35-a3b", "system_prompt": "You are a wizard.", } @respx.mock def test_cli_patch_happy( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, _isolated_local_agents: "Path", ) -> None: """cli_patch_happy [happy]: argv → 200 mock → stdout confirmation; local index refreshed with the post-patch state. """ from ratatoskr.local_agents import load_local_agents monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.patch("https://w.example/agents/ratatoskr:wizard").mock( return_value=httpx.Response(200, json=_FULL_AGENT_RESP) ) rc = main(["patch", "ratatoskr:wizard", "--system-prompt", "new"]) out = capsys.readouterr() assert rc == 0 assert out.out.strip() == "patched ratatoskr:wizard" entries = load_local_agents() assert len(entries) == 1 assert entries[0].agent_id == "ratatoskr:wizard" assert entries[0].role == "qwen3.6-35-a3b" @respx.mock def test_cli_delete_happy( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, _isolated_local_agents: "Path", ) -> None: """cli_delete_happy [happy]: argv → 204 mock → stdout confirmation; local index entry removed (v0.8.0 hook). """ from ratatoskr.local_agents import ( LocalAgentEntry, add_local_agent, load_local_agents, ) # Pre-populate so we can verify removal (v2 schema: role, not model). add_local_agent(LocalAgentEntry( agent_id="ratatoskr:wizard", agent_name="wizard", role="m", description="d", defined_at="t", )) monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.delete("https://w.example/agents/ratatoskr:wizard").mock( return_value=httpx.Response(204) ) rc = main(["delete", "ratatoskr:wizard"]) out = capsys.readouterr() assert rc == 0 assert out.out.strip() == "deleted ratatoskr:wizard" assert load_local_agents() == [] def test_cli_missing_auth( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_missing_auth [error]: no api-key → stderr [auth_error] + exit 11.""" monkeypatch.delenv("WORLDTREE_API_KEY", raising=False) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) err = capsys.readouterr().err assert rc == 11 assert "[auth_error]" in err @respx.mock def test_cli_api_failed( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_api_failed [error]: 500 → stderr [api_failed] + exit 20.""" monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( return_value=httpx.Response(500, content=b"upstream out") ) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) err = capsys.readouterr().err assert rc == 20 assert "[api_failed]" in err @respx.mock def test_cli_quota_exceeded( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_quota_exceeded [error]: 429 → stderr [quota_exceeded] + exit 20.""" monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( return_value=httpx.Response( 429, headers={"Retry-After": "0"}, json={"detail": {"error_code": "agent_quota_exceeded"}}, ) ) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) err = capsys.readouterr().err assert rc == 20 assert "[quota_exceeded]" in err @respx.mock def test_cli_network_error( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_network_error [error]: a transport failure surfaces as the SDK's ConnectFailed (not a raw httpx error) → stderr [network_error] + exit 21.""" monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( side_effect=httpx.ConnectError("refused") ) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) err = capsys.readouterr().err assert rc == 21 assert "[network_error]" in err def test_cli_patch_no_fields( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_patch_no_fields [error]: patch with no flags → [usage_error] + exit 10.""" monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") rc = main(["patch", "ratatoskr:wizard"]) err = capsys.readouterr().err assert rc == 10 assert "[usage_error]" in err @respx.mock def test_cli_define_partial_response_degrades( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, _isolated_local_agents: "Path", ) -> None: """cli_define_partial [error]: a 201 missing `role` + a NULL system_prompt degrades (role '?', description-safe) and exits 0 — never a KeyError/ AttributeError traceback (heid-bug-hunt open-world invariant).""" from ratatoskr.local_agents import load_local_agents monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( return_value=httpx.Response(201, json={ "agent_id": "ratatoskr:wizard", "agent_name": "wizard", "system_prompt": None, # present-but-null: .get(...,'') would NOT default }) ) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) out = capsys.readouterr() assert rc == 0 assert out.out.strip() == "defined ratatoskr:wizard (?)" # Well-formed identity → still indexed (role degraded to '?'). entries = load_local_agents() assert len(entries) == 1 assert entries[0].role == "?" @respx.mock def test_cli_define_no_agent_id_is_api_failure( self, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch ) -> None: """cli_define_no_agent_id [error]: a 2xx with no usable agent_id → [api_failed] + exit 20 (controlled), not an uncaught traceback.""" monkeypatch.setenv("WORLDTREE_API_URL", "https://w.example") monkeypatch.setenv("WORLDTREE_API_KEY", "k") respx.post("https://w.example/agents/define").mock( return_value=httpx.Response(201, json={"role": "m"}) # no agent_id ) rc = main([ "define", "--name", "wizard", "--system-prompt", "x", "--role", "m", ]) err = capsys.readouterr().err assert rc == 20 assert "[api_failed]" in err