Skip to content

Commit c8fc5dd

Browse files
committed
test(middleware): cover upstream WebSocket disconnect
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
1 parent 60e28b7 commit c8fc5dd

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

crates/openshell-supervisor-network/src/l7/websocket.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4071,6 +4071,72 @@ network_policies:
40714071
.expect("middleware server");
40724072
}
40734073

4074+
#[tokio::test]
4075+
async fn upstream_eof_reports_one_upstream_disconnect_while_downstream_is_open() {
4076+
let (mut session, mut observed, shutdown_tx, server_task) =
4077+
recording_middleware_session("wss").await;
4078+
assert!(session.start("").await.allowed);
4079+
assert!(matches!(
4080+
tokio::time::timeout(std::time::Duration::from_secs(2), observed.recv())
4081+
.await
4082+
.expect("middleware observes session start"),
4083+
Some(ObservedWebSocketRequest::SessionStart)
4084+
));
4085+
4086+
let (client_app, mut relay_client) = tokio::io::duplex(4096);
4087+
let (mut relay_upstream, upstream_app) = tokio::io::duplex(4096);
4088+
let relay = tokio::spawn(async move {
4089+
relay_with_options(
4090+
&mut relay_client,
4091+
&mut relay_upstream,
4092+
Vec::new(),
4093+
"api.openai.com",
4094+
443,
4095+
RelayOptions {
4096+
policy_name: "rest-api",
4097+
assembly_budget: WebSocketAssemblyBudget::default(),
4098+
resolver: None,
4099+
generation_guard: None,
4100+
provider_credentials: None,
4101+
target: "/",
4102+
inspector: None,
4103+
compression: WebSocketCompression::None,
4104+
middleware_session: Some(session),
4105+
middleware_context: None,
4106+
deny_uninspected_credentials: false,
4107+
},
4108+
)
4109+
.await
4110+
});
4111+
4112+
drop(upstream_app);
4113+
tokio::time::timeout(std::time::Duration::from_secs(2), relay)
4114+
.await
4115+
.expect("relay finishes after upstream disconnect")
4116+
.expect("join relay")
4117+
.expect("upstream EOF ends relay normally");
4118+
assert!(matches!(
4119+
tokio::time::timeout(std::time::Duration::from_secs(2), observed.recv())
4120+
.await
4121+
.expect("middleware observes session end"),
4122+
Some(ObservedWebSocketRequest::SessionEnd(
4123+
openshell_core::proto::MiddlewareSessionEndReason::UpstreamDisconnect,
4124+
))
4125+
));
4126+
assert!(
4127+
observed.try_recv().is_err(),
4128+
"upstream EOF must produce exactly one session end"
4129+
);
4130+
4131+
drop(client_app);
4132+
let _ = shutdown_tx.send(());
4133+
tokio::time::timeout(std::time::Duration::from_secs(2), server_task)
4134+
.await
4135+
.expect("middleware server shuts down")
4136+
.expect("join middleware server")
4137+
.expect("middleware server");
4138+
}
4139+
40744140
#[tokio::test]
40754141
async fn denied_websocket_session_start_reports_middleware_failure_before_close() {
40764142
let (session, mut observed, shutdown_tx, server_task) =

proto/supervisor_middleware.proto

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,9 @@ enum MiddlewareSessionEndReason {
399399
MIDDLEWARE_SESSION_END_REASON_UNSPECIFIED = 0;
400400
// Evaluation completed.
401401
MIDDLEWARE_SESSION_END_REASON_NORMAL = 1;
402-
// The sandbox peer disconnected.
402+
// The sandbox peer disconnected. Before the directional split, wire value 2
403+
// represented either peer disconnect. Mixed deployments across that
404+
// pre-0.1.0 change are unsupported.
403405
MIDDLEWARE_SESSION_END_REASON_DOWNSTREAM_DISCONNECT = 2;
404406
// A policy reload replaced the active middleware chain.
405407
MIDDLEWARE_SESSION_END_REASON_POLICY_RELOAD = 3;

0 commit comments

Comments
 (0)