@@ -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) =
0 commit comments