Issue #6 (TUI startup error visibility): restructure run_tui lifecycle so pre-App.run() failures land on real stderr instead of getting eaten by the alt-screen teardown. New _resolve_then_run async helper opens the AsyncClient via async-with, does pre-flight session resolution, routes AgentNotFound / SessionApiFailed / network errors to sys.stderr (verbatim same labels + exit codes as cli._amain), then constructs RatatoskrApp with pre-resolved state and awaits app.run_async(). RatatoskrApp.__init__ signature widens to (args, *, session_id, agent_id, client) — all three required. on_mount narrows to identity-widget population; on_unmount becomes a no-op (client lifetime owned by run_tui's async-with). Issue #5 (--end-user-id for per-end-user agents): sessions.create_session gains keyword-only end_user_id kwarg with PRE-003 non-empty assertion; ParsedArgs.end_user_id field added (default None); --end-user-id flag with non-empty validation; _amain + _resolve_then_run thread it to their create_session calls. RATATOSKR_END_USER_ID env-var fallback (flag > env > None) per the post-2026-05-23 amendment; env.sh (gitignored) ships "ratatoskr-tui" as project-stable partition default. Worldtree-dev consumer-API follow-up (althing 01KSBARG2B8M): User-Agent header added (ratatoskr/<version> (vh@phasefinal.com), version pulled via importlib.metadata) to both AsyncClient constructions so server logs can distinguish ratatoskr traffic from other consumers. Volva code-review (2 rounds on #6) found 8 test-precision gaps + 1 PRE assertion drift, all Category 1 fixed: missing PRE-001 at _resolve_then_run entry; Rule separator assertions on markdown render; RichLog-write spy on empty submit; input-cleared + no-new-worker on cancelling busy; worker.cancel observation on three force-exit paths; on_unmount-no-close focused test (the prior client-lifetime test patched run_async so on_unmount was never exercised); happy --new resolve test verifying POST count + identity propagation. Issues #2/#3/#4/#5 contracts amended in-place to reflect: - create_session widened (PRE-003, body construction step, body shape POST) - ParsedArgs description + _parse_args STEPS + _amain create_session call + new TESTS for end_user_id + env-var fallback - _resolve_then_run STEPS + new TEST entries; on_mount narrowed; INV-007 amended for new client ownership - Post-#6 adjustment note on issue #5 (_resolve_then_run replaces on_mount as the threading site since #6 moved session resolution out of the alt-screen) 188 tests GREEN; ruff clean. Bumps to v0.1.0 — first minor release, the load-bearing reason is RatatoskrApp.__init__'s breaking signature change (additive end_user_id alone wouldn't have triggered a minor pre-v1.x). Files Gitea issues #9 (spec-pin refresh v0.19.0 → v0.22.1), #10 (track Worldtree #196 subject:{type,id} migration), #11 (AdminEvents pane auth prerequisite admin.events.read). Infra-ops pinged via althing for agents.call:lofn scope add (broker pattern; they forwarded to worldtree-dev because personal Worldtree exposes no public scope-mutation endpoint).
This commit is contained in:
@@ -137,6 +137,68 @@ class TestCreateSession:
|
||||
await create_session(client, "")
|
||||
assert route.call_count == 0
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_create_with_end_user_id(self) -> None:
|
||||
"""happy_create_with_end_user_id [happy]: body carries both keys (issue #5)."""
|
||||
import json as _json
|
||||
|
||||
route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"session_id": "s-new",
|
||||
"agent_id": "lofn",
|
||||
"message_count": 0,
|
||||
"created_at": "2026-05-22T12:00:00+00:00",
|
||||
"last_active": "2026-05-22T12:00:00+00:00",
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
info = await create_session(client, "lofn", end_user_id="alice")
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
# INV: body MUST be exactly {"agent_id": ..., "end_user_id": ...} — byte-for-byte
|
||||
assert body == {"agent_id": "lofn", "end_user_id": "alice"}
|
||||
assert info.session_id == "s-new"
|
||||
assert info.agent_id == "lofn"
|
||||
|
||||
@respx.mock
|
||||
async def test_default_omits_end_user_id(self) -> None:
|
||||
"""default_omits_end_user_id [trace]: omit kwarg → body has no end_user_id (INV-002)."""
|
||||
import json as _json
|
||||
|
||||
route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"session_id": "s-new",
|
||||
"agent_id": "mimir",
|
||||
"message_count": 0,
|
||||
"created_at": "2026-05-22T12:00:00+00:00",
|
||||
"last_active": "2026-05-22T12:00:00+00:00",
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
await create_session(client, "mimir")
|
||||
body = _json.loads(route.calls[0].request.content)
|
||||
# Exact equality — no end_user_id key in the body when the kwarg is omitted
|
||||
assert body == {"agent_id": "mimir"}
|
||||
assert "end_user_id" not in body
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_end_user_id(self) -> None:
|
||||
"""empty_end_user_id [adversarial]: '' → AssertionError before HTTP (PRE-003)."""
|
||||
route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(201, content=b"{}")
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await create_session(client, "mimir", end_user_id="")
|
||||
assert route.call_count == 0
|
||||
|
||||
|
||||
def _list_item(
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user