test(websocket): connection-local overlay verification for browser-registered ops

Ported the alknet-http overlay verification to the channels-over-WS
session (tests/ws_overlay_ops.rs, test-support feature, 8 tests):

- overlay mechanism: browser-registered ops land in the connection's
  Layer 2 overlay (register_imported), exposed via overlay_env() —
  no PeerIds (browsers are not peers); PeerRef::Specific to a browser
  id routes to nothing (NOT_FOUND)
- hub→browser call through compose_root_env's attached overlay
- AccessControl on browser ops gates hub calls (scope match allows,
  missing scope FORBIDDEN)
- overlay dies with the connection; no leak between connections;
  in-flight calls to browser ops resolve on close
- wire-level: 10 interleaved concurrent calls across two WS sessions
  — no cross-correlation, no deadlock; disconnect mid-call resolves
  and a fresh session works (no listener wedge)

byte_adapter: read_eof Notify now gated to the wss feature (its only
consumer is from_wss) so a test-support-only build is warning-free.

Verified: cargo test (182 lib), --all-features (227 lib + 5 MCP + 8
overlay + 10 WS integration), clippy -D warnings (default,
test-support, all-features), fmt.
This commit is contained in:
2026-08-28 15:49:25 +00:00
parent 3a906cbd6a
commit bc99ec7188
5 changed files with 607 additions and 10 deletions
+10 -1
View File
@@ -33,10 +33,12 @@
use std::{
io,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
#[cfg(any(test, feature = "wss"))]
use std::sync::Arc;
use axum::extract::ws::{CloseFrame, Message as AxumMessage, WebSocket};
use futures::channel::mpsc as futures_mpsc;
use futures::{SinkExt, StreamExt};
@@ -81,6 +83,7 @@ pub struct WsByteStream {
pub struct WsPumps {
read_task: tokio::task::JoinHandle<()>,
write_task: tokio::task::JoinHandle<()>,
#[cfg(feature = "wss")]
read_eof: Arc<tokio::sync::Notify>,
}
@@ -93,6 +96,7 @@ impl WsPumps {
/// Fires when the WS read side reaches EOF (socket close from either
/// side) — used by `from_wss`'s connection-drop monitor to await
/// socket EOF (ADR-070).
#[cfg(feature = "wss")]
pub(crate) async fn read_eof(&self) {
self.read_eof.notified().await;
}
@@ -106,9 +110,11 @@ pub fn split_ws_to_bytes(socket: WebSocket) -> (WsByteStream, WsPumps) {
let (read_tx, read_rx) = mpsc::channel::<Vec<u8>>(READ_SLOTS);
let (write_tx, mut write_rx) = futures_mpsc::channel::<WriteMsg>(WRITE_SLOTS);
#[cfg(feature = "wss")]
let read_eof = Arc::new(tokio::sync::Notify::new());
let write_tx_for_read = write_tx.clone();
#[cfg(feature = "wss")]
let read_eof_for_task = Arc::clone(&read_eof);
let read_task = tokio::spawn(async move {
while let Some(msg) = ws_stream.next().await {
@@ -129,6 +135,7 @@ pub fn split_ws_to_bytes(socket: WebSocket) -> (WsByteStream, WsPumps) {
Ok(_) => {}
}
}
#[cfg(feature = "wss")]
read_eof_for_task.notify_waiters();
});
@@ -184,6 +191,7 @@ pub fn split_ws_to_bytes(socket: WebSocket) -> (WsByteStream, WsPumps) {
WsPumps {
read_task,
write_task,
#[cfg(feature = "wss")]
read_eof,
},
)
@@ -367,6 +375,7 @@ where
WsPumps {
read_task,
write_task,
#[cfg(feature = "wss")]
read_eof,
},
)