Repository navigation
test(terminal): prove the WS ownership refusal through the real route (#14960) - #15095
Merged
Merged
Conversation
…#14960) The non-owner rejection had handler-level coverage only: terminal_websocket_auth_test.py awaits terminal_websocket() directly, which cannot observe a routing or dependency-resolution failure - the lesson of #14998, where those same tests stayed green while every handshake 500'd. Adds a TestClient handshake driving /ws/{session_id} as mallory against alice's session, asserting close code 1008 on the wire, that accept() never ran, and via caplog that the ownership branch fired rather than #14961's unknown-session branch (both close 1008 from the same line). WebSocketDisconnect hoisted to module scope, reused by the two existing local imports, so the file stays under the 600-line cap at 599.
5 tasks
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml.
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
Closure audit of #14960 found four of its five acceptance criteria discharged by merged code
with route-level evidence, and one — "an authenticated user connecting to another user's
session id is rejected" — covered only at handler level.
api/terminal_websocket_auth_test.py::TestTerminalWebsocketOwnership::test_non_owner_is_rejectedawaits
terminal_websocket(ws, session_id)directly. That style of test cannot observe arouting or dependency-resolution failure — which is precisely how #14998 hid: the same suite
stayed green for the entire period every real handshake 500'd inside
solve_dependencies,because a router-level
Request-typedDependscannot resolve in a WebSocket scope.A rejection criterion is about what a client experiences, so it needs a test that reaches
the guard the way a client does. #15085 added exactly that shape for the authenticated and the
unauthenticated cases; the non-owner case was the one left behind. This adds it, and nothing
else — no production code changes, no change to authentication or authorization semantics.
What Changed
autobot-backend/api/terminal_websocket_route_test.pyonly.TestTerminalWebsocketRouteHandshake::test_authenticated_non_owner_is_refused: a realTestClienthandshake on/ws/{session_id}asmalloryagainst a session owned byalice.Asserts close code
1008observed on the wire as aWebSocketDisconnect, thataccept()never ran (so no terminal handler is constructed and no shell starts), and — via
caplog—that the ownership branch fired, not the unknown-session branch of Unknown terminal session_id defaults to a live STANDARD-security terminal #14961. Both branches
close
1008from the same line, so without the log assertion the test would pass for thewrong reason.
WebSocketDisconnecthoisted to module scope and the two existing function-local importsdropped in favour of it. This is what keeps the file inside the 600-line cap: 599 lines,
up from 584 minus the 4 lines the hoist recovers.
api/terminal.pyis untouched and unchanged in size at its grandfathered 1299 lines.Verification
Targeted run, this file only:
Contrast mutation — the point of the change. Replacing the ownership comparison in
_lookup_terminal_sessionwith a never-taken branch:Exactly one test fails, the new one, and it fails for the right reason: with the check removed
the non-owner's handshake is accepted and a real PTY is allocated for them — the captured
simple_ptyerror is that shell being torn down. The other 18 route tests, including bothpre-existing handshake tests, pass throughout: they could not see this.
Mutation reverted before commit;
git diff --staton the branch shows one file, +19/-4.Local
fastapiis 0.135.2 against CI's 0.141.1 (#15091). The assertion here is behavioural —a real handshake — so it is unaffected by the deferred-
include_routerdifferences that madeintrospection unusable in #14998; the file's structural checks already run through a
clean-interpreter subprocess dump and are untouched.
Model Used
Claude Opus 5 (1M context)
Refs #14960.