| use std::time::Duration; |
|
|
| use anyhow::Context; |
| use anyhow::Result; |
| use bytes::Bytes; |
| use codex_http_client::HttpClientFactory; |
| use codex_http_client::OutboundProxyPolicy; |
| use futures::SinkExt; |
| use futures::StreamExt; |
| use pretty_assertions::assert_eq; |
| use tokio::io::AsyncReadExt; |
| use tokio::net::TcpListener; |
| use tokio::sync::mpsc; |
| use tokio::sync::watch; |
| use tokio::time::timeout; |
| use tokio_tungstenite::accept_async; |
| use tokio_tungstenite::tungstenite::Message; |
| use tokio_tungstenite::tungstenite::protocol::CloseFrame; |
| use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; |
|
|
| use super::Forwarder; |
| use crate::ExecServerTelemetry; |
| use crate::noise_relay::stream_handler::NoiseStreamConnection; |
|
|
| #[tokio::test] |
| async fn transport_disconnect_cancels_an_unfinished_websocket_handshake() -> Result<()> { |
| let listener = TcpListener::bind("127.0.0.1:0").await?; |
| let forwarder = Forwarder::new( |
| format!("ws://{}", listener.local_addr()?), |
| &HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault), |
| ExecServerTelemetry::default(), |
| )?; |
| let (_incoming, incoming_rx) = mpsc::channel(1); |
| let (outgoing_tx, _outgoing) = mpsc::channel(1); |
| let (disconnected, disconnected_rx) = watch::channel(false); |
| let connection = NoiseStreamConnection { |
| incoming_rx, |
| outgoing_tx, |
| disconnected_rx, |
| writer_task: tokio::spawn(async {}), |
| executor_registration: None, |
| }; |
| let task = tokio::spawn(forwarder.run_connection(connection)); |
| let deadline = Duration::from_secs(5); |
| let (mut socket, _) = timeout(deadline, listener.accept()).await??; |
| disconnected.send(true)?; |
| timeout(deadline, task).await??; |
| timeout(deadline, socket.read_to_end(&mut Vec::new())).await??; |
| Ok(()) |
| } |
|
|
| #[tokio::test] |
| async fn destination_close_is_acknowledged() -> Result<()> { |
| let listener = TcpListener::bind("127.0.0.1:0").await?; |
| let forwarder = Forwarder::new( |
| format!("ws://{}", listener.local_addr()?), |
| &HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault), |
| ExecServerTelemetry::default(), |
| )?; |
| let (_incoming, incoming_rx) = mpsc::channel(1); |
| let (outgoing_tx, mut outgoing) = mpsc::channel(1); |
| let (_disconnected, disconnected_rx) = watch::channel(false); |
| let task = tokio::spawn(forwarder.run_connection(NoiseStreamConnection { |
| incoming_rx, |
| outgoing_tx, |
| disconnected_rx, |
| writer_task: tokio::spawn(async {}), |
| executor_registration: None, |
| })); |
| let deadline = Duration::from_secs(5); |
| let (socket, _) = timeout(deadline, listener.accept()).await??; |
| let mut socket = timeout(deadline, accept_async(socket)).await??; |
| let response = Bytes::from_static(b"final response"); |
| socket.send(Message::Binary(response.clone())).await?; |
| let close = CloseFrame { |
| code: CloseCode::Normal, |
| reason: "finished".into(), |
| }; |
| socket.close(Some(close.clone())).await?; |
|
|
| let reply = timeout(deadline, socket.next()) |
| .await? |
| .context("destination should receive the Close acknowledgement")??; |
| assert_eq!(reply, Message::Close(Some(close))); |
| timeout(deadline, task).await??; |
| assert_eq!(outgoing.recv().await, Some(response)); |
| assert_eq!(outgoing.recv().await, None); |
| Ok(()) |
| } |
|
|