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:
@@ -86,6 +86,7 @@ def _clear_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Tests assert env-resolution behavior; default to a clean slate per test."""
|
||||
monkeypatch.delenv("WORLDTREE_API_KEY", raising=False)
|
||||
monkeypatch.delenv("WORLDTREE_API_URL", raising=False)
|
||||
monkeypatch.delenv("RATATOSKR_END_USER_ID", raising=False)
|
||||
|
||||
|
||||
class TestParseArgs:
|
||||
@@ -196,6 +197,42 @@ class TestParseArgs:
|
||||
with pytest.raises(UsageError):
|
||||
_parse_args(["--send", "", "--new", "--agent", "x", "--api-key", "k"])
|
||||
|
||||
def test_happy_new_with_end_user_id(self) -> None:
|
||||
"""happy_new_with_end_user_id [happy]: --end-user-id alice → end_user_id='alice'."""
|
||||
args = _parse_args(
|
||||
["--send", "hi", "--new", "--agent", "lofn", "--api-key", "k",
|
||||
"--end-user-id", "alice"]
|
||||
)
|
||||
assert args.end_user_id == "alice"
|
||||
assert args.agent_id == "lofn"
|
||||
|
||||
def test_end_user_id_default_none(self) -> None:
|
||||
"""end_user_id_default_none [trace]: omit --end-user-id → ParsedArgs.end_user_id is None."""
|
||||
args = _parse_args(["--send", "hi", "--new", "--agent", "m", "--api-key", "k"])
|
||||
assert args.end_user_id is None
|
||||
|
||||
def test_empty_end_user_id(self) -> None:
|
||||
"""empty_end_user_id [adversarial]: --end-user-id '' → UsageError (mirrors empty --send)."""
|
||||
with pytest.raises(UsageError, match="--end-user-id"):
|
||||
_parse_args(
|
||||
["--send", "hi", "--new", "--agent", "m", "--api-key", "k", "--end-user-id", ""]
|
||||
)
|
||||
|
||||
def test_end_user_id_from_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""end_user_id_from_env [trace]: $RATATOSKR_END_USER_ID fills when flag omitted."""
|
||||
monkeypatch.setenv("RATATOSKR_END_USER_ID", "ratatoskr-tui")
|
||||
args = _parse_args(["--send", "hi", "--new", "--agent", "m", "--api-key", "k"])
|
||||
assert args.end_user_id == "ratatoskr-tui"
|
||||
|
||||
def test_end_user_id_flag_beats_env(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""end_user_id_flag_beats_env [trace]: explicit --end-user-id wins over env."""
|
||||
monkeypatch.setenv("RATATOSKR_END_USER_ID", "from-env")
|
||||
args = _parse_args(
|
||||
["--send", "hi", "--new", "--agent", "m", "--api-key", "k",
|
||||
"--end-user-id", "from-flag"]
|
||||
)
|
||||
assert args.end_user_id == "from-flag"
|
||||
|
||||
|
||||
SID = SseId(42, 5)
|
||||
|
||||
@@ -817,6 +854,16 @@ _PARSED_NEW = ParsedArgs(
|
||||
server_url="https://w.example",
|
||||
raw=False,
|
||||
)
|
||||
_PARSED_NEW_WITH_END_USER = ParsedArgs(
|
||||
send_content="hi",
|
||||
session_id=None,
|
||||
new=True,
|
||||
agent_id="mimir",
|
||||
api_key="k",
|
||||
server_url="https://w.example",
|
||||
raw=False,
|
||||
end_user_id="alice",
|
||||
)
|
||||
_PARSED_EXISTING = ParsedArgs(
|
||||
send_content="hi",
|
||||
session_id="s-1",
|
||||
@@ -837,6 +884,27 @@ _CREATE_OK_RESP = {
|
||||
|
||||
|
||||
class TestAmain:
|
||||
@respx.mock
|
||||
async def test_user_agent_header_sent(self) -> None:
|
||||
"""user_agent_header_sent [trace]: outbound requests carry the ratatoskr User-Agent.
|
||||
|
||||
Worldtree-dev (althing 2026-05-23) requested consumers send User-Agent
|
||||
so server logs can distinguish ratatoskr traffic.
|
||||
"""
|
||||
sessions_route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(201, json=_CREATE_OK_RESP)
|
||||
)
|
||||
sse_body = _sse_chunk("42:1", {"type": "text", "content": "hi"}) + _sse_chunk(
|
||||
"42:2", _DONE_BODY
|
||||
)
|
||||
respx.post("https://w.example/sessions/s-new/messages").mock(
|
||||
return_value=_sse_resp(sse_body)
|
||||
)
|
||||
await _amain(_PARSED_NEW)
|
||||
ua = sessions_route.calls[0].request.headers["User-Agent"]
|
||||
assert ua.startswith("ratatoskr/")
|
||||
assert "vh@phasefinal.com" in ua
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_new_session_then_stream(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
"""happy_new_session_then_stream [happy,tracer]: …"""
|
||||
|
||||
Reference in New Issue
Block a user