feat(web): address Heid code-review findings — issue #16 (v0.16.0)
Heid panel review (Gróa + Hulda, thread 01KSP5P6CSJH) on v0.15.0/
v0.15.1 surfaced one load-bearing bug + several precision items. This
pass closes them.
Load-bearing fix — cancel paths targeted the wrong turn_id:
- `_TURN_COUNTER` allocates browser-local ids (1, 2, 3…); the real
upstream Worldtree turn_id (e.g. 799) only arrives in the first SSE
event. The v0.15.x cancel/disconnect/shutdown paths posted to
/sessions/{sid}/turns/{LOCAL_ID}/cancel — wrong URL upstream.
- TurnHandle.upstream_response (dead field) → upstream_turn_id: int|None.
Captured from the first event's sse_id.turn_id in the stream
generator. All cancel paths now target it. Cancel before the upstream
stream starts (upstream_turn_id None) is a no-op
({"cancelled": false, "reason": "not_started"}).
- The old cancel tests mocked the local-id URL, so they encoded the bug;
rewritten to assert the UPSTREAM id is targeted.
Behavior change (minor-bump driver) — server-side end_user_id:
- create_app gains end_user_id kwarg; entrypoint reads
RATATOSKR_END_USER_ID and threads it in. POST /api/sessions uses
app.state.end_user_id, IGNORING any browser-supplied value (a client
can't impersonate an arbitrary end-user partition). JS no longer
sends end_user_id.
Precision fixes:
- Entrypoint missing-extras ImportError catch scoped to starlette/
uvicorn ONLY; baseline-dep / first-party import failures now
propagate as real tracebacks instead of masking as exit-12.
- Lifespan shutdown logs per-pending session_id + upstream_turn_id
(was a single aggregate count).
Tests (+18; 376 total):
- disconnect_triggers_upstream_cancel (INV-005 load-bearing — drives
the stream generator directly + cancels the consuming task; would
have caught the turn_id bug)
- cancel_targets_upstream_turn_id, cancel_before_started_is_noop,
cancel_failed_500
- server-side end_user_id: uses / ignores-body / omits-when-unset
- create_app: routes_registered / state_attached / factory_stored
- entrypoint: default_host / port_zero / happy_argv / open / no-open
- real_import_bug_propagates (precision guard)
- full_event_vocab at the stream-endpoint layer
Contract #16 amended: v0.16.0 amendment banner + INV-005/006 reworded
for upstream_turn_id + FN sketches corrected (server-side end_user_id,
upstream_response→upstream_turn_id, manual client lifecycle vs the
non-executable async-with sketch, not-started cancel branch).
This commit is contained in:
@@ -62,17 +62,93 @@ def test_entrypoint_missing_api_key_returns_11(monkeypatch) -> None:
|
||||
def test_entrypoint_missing_extras_returns_12(monkeypatch) -> None:
|
||||
"""missing_extras [error]: starlette unimportable → exit 12.
|
||||
|
||||
Simulated by shadowing the import within main()'s try-block via
|
||||
sys.modules tampering — pre-set the offending module to None so the
|
||||
import raises ImportError.
|
||||
v0.16.0: the missing-extras probe is scoped to the OPTIONAL extras
|
||||
(starlette / uvicorn) only. Simulated by shadowing `starlette` to
|
||||
None in sys.modules so its import raises ImportError inside the
|
||||
narrow try-block.
|
||||
"""
|
||||
import sys
|
||||
|
||||
monkeypatch.setenv("WORLDTREE_API_KEY", "k")
|
||||
monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
|
||||
monkeypatch.setitem(sys.modules, "starlette", None)
|
||||
|
||||
from ratatoskr.web.entrypoint import main
|
||||
|
||||
rc = main(["--port", "0"])
|
||||
assert rc == 12
|
||||
|
||||
|
||||
def test_entrypoint_real_import_bug_propagates(monkeypatch) -> None:
|
||||
"""v0.16.0: an ImportError from a BASELINE module (not an extra) MUST
|
||||
propagate as a real traceback, not be masked as exit-12 missing-extras.
|
||||
Shadowing ratatoskr.web.server (a first-party module, present with or
|
||||
without the [web] extras) should raise, not return 12.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import pytest as _pytest
|
||||
|
||||
monkeypatch.setenv("WORLDTREE_API_KEY", "k")
|
||||
monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
|
||||
monkeypatch.setitem(sys.modules, "ratatoskr.web.server", None)
|
||||
|
||||
from ratatoskr.web.entrypoint import main
|
||||
|
||||
rc = main(["--port", "0"])
|
||||
assert rc == 12
|
||||
with _pytest.raises(ImportError):
|
||||
main(["--port", "0"])
|
||||
|
||||
|
||||
class TestEntrypointArgs:
|
||||
"""FN main argparse + serve-loop traces (contract TESTS)."""
|
||||
|
||||
def test_default_host_is_zero(self) -> None:
|
||||
"""default_host_is_zero [trace]: argv=[] → host == '0.0.0.0'."""
|
||||
from ratatoskr.web.entrypoint import _build_arg_parser
|
||||
args = _build_arg_parser().parse_args([])
|
||||
assert args.host == "0.0.0.0"
|
||||
assert args.port == 8765
|
||||
assert args.open is False
|
||||
|
||||
def test_port_zero_supported(self) -> None:
|
||||
"""port_zero_supported [trace]: argv=['--port','0'] → port == 0."""
|
||||
from ratatoskr.web.entrypoint import _build_arg_parser
|
||||
args = _build_arg_parser().parse_args(["--port", "0"])
|
||||
assert args.port == 0
|
||||
|
||||
def test_happy_argv_serves_and_returns_zero(self, monkeypatch) -> None:
|
||||
"""happy_argv [tracer]: env set + uvicorn.run mocked → main returns 0."""
|
||||
import uvicorn
|
||||
monkeypatch.setenv("WORLDTREE_API_KEY", "k")
|
||||
monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com")
|
||||
calls = {}
|
||||
monkeypatch.setattr(uvicorn, "run", lambda app, **kw: calls.update(kw))
|
||||
from ratatoskr.web.entrypoint import main
|
||||
rc = main(["--port", "0"])
|
||||
assert rc == 0
|
||||
assert calls["host"] == "0.0.0.0"
|
||||
assert calls["port"] == 0
|
||||
|
||||
def test_open_flag_calls_webbrowser(self, monkeypatch) -> None:
|
||||
"""open_flag_calls_webbrowser [trace]: --open → webbrowser.open called."""
|
||||
import uvicorn
|
||||
import webbrowser
|
||||
monkeypatch.setenv("WORLDTREE_API_KEY", "k")
|
||||
monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None)
|
||||
opened = []
|
||||
monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url))
|
||||
from ratatoskr.web.entrypoint import main
|
||||
main(["--port", "0", "--open"])
|
||||
assert len(opened) == 1
|
||||
|
||||
def test_no_open_default(self, monkeypatch) -> None:
|
||||
"""no_open_default [trace]: argv without --open → webbrowser.open NOT called."""
|
||||
import uvicorn
|
||||
import webbrowser
|
||||
monkeypatch.setenv("WORLDTREE_API_KEY", "k")
|
||||
monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None)
|
||||
opened = []
|
||||
monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url))
|
||||
from ratatoskr.web.entrypoint import main
|
||||
main(["--port", "0"])
|
||||
assert opened == []
|
||||
|
||||
Reference in New Issue
Block a user