// SPDX-License-Identifier: AGPL-3.0-only //! Loopback integration test for the chunk-stream transport. //! //! Binds a real QUIC server endpoint, completes the handshake, and drives the server-side [`chunk_stream_task`] end-to-end: a client-sent `ChunkSubscribe` must surface on the simulation-loop events channel as [`ServerEvent::ChunkSubscribe`], and a `ChunkMessage` pushed through the [`ChunkSink`] must be received by the client on the chunk stream. use std::time::Duration; use crate::chunk::{ChunkSink, chunk_stream_task}; use crate::codec::{MAX_CHUNK_FRAME_LEN, read_frame, write_frame}; use crate::endpoint::{client_endpoint, server_endpoint}; use crate::handshake::{accept_connection, connect}; use crate::runtime::ServerEvent; use shared::protocol::chunk::{ChunkMessage, ChunkSubscribe}; use shared::protocol::{ClientHello, FeatureFlags, PROTOCOL_VERSION, PlayerIdentity}; use shared::world::{ChunkData, ChunkPos}; /// Builds a minimal `ClientHello` advertising the current protocol version. fn hello(display_name: &str) -> ClientHello { ClientHello { protocol_version: PROTOCOL_VERSION, client_build: "synvael-client-test".to_owned(), player_identity: PlayerIdentity { display_name: display_name.to_owned(), }, installed_packs: vec![], requested_features: FeatureFlags(0), } } #[tokio::test(flavor = "multi_thread")] async fn chunk_subscribe_and_delivery_round_trip() -> Result<(), Box> { let server = server_endpoint("127.0.0.1:0".parse()?)?; let server_addr = server.local_addr()?; // Stand in for the simulation loop's channels: the events channel the task forwards subscriptions to, and the outbound sink it drains deliveries from. let (events_tx, events_rx) = crossbeam_channel::unbounded::(); let (chunk_tx, chunk_rx) = tokio::sync::mpsc::unbounded_channel::(); let sink = ChunkSink::new(chunk_tx); // Server side: accept one connection, complete the handshake, then run the chunk pump until the client closes. let server_task = tokio::spawn(async move { let incoming = server.accept().await.ok_or("server endpoint closed")?; let conn = accept_connection(incoming, "synvael-server-test".to_owned(), 20).await?; chunk_stream_task(conn.connection, 7, events_tx, chunk_rx).await; Ok::<_, Box>(()) }); // Client side: connect, then open the chunk stream and send a subscription. let client = client_endpoint()?; let connected = connect(&client, server_addr, "localhost", hello("Tester")).await?; let (mut client_send, mut client_recv) = connected.connection.open_bi().await?; let subscribe = ChunkSubscribe { center: ChunkPos::new(1, 2, 3), radius: 4, }; write_frame(&mut client_send, &subscribe).await?; // The task must forward the subscription to the events channel. The crossbeam receiver is blocking, so it is polled on a blocking thread to avoid stalling the runtime. let event = tokio::task::spawn_blocking(move || { events_rx .recv_timeout(Duration::from_secs(5)) .map(|e| (e, events_rx)) }) .await?; let (event, events_rx) = event?; match event { ServerEvent::ChunkSubscribe { id, request } => { assert_eq!(id, 7, "the subscribe must carry the session id"); assert_eq!(request, subscribe, "the subscribe must round-trip intact"); } other => return Err(format!("expected ChunkSubscribe, got {other:?}").into()), } // The simulation loop hands a chunk back through the sink; the client must receive it on the stream. let data = ChunkData::new(ChunkPos::new(1, 2, 3), 0); let delivered = ChunkMessage::Chunk { pos: ChunkPos::new(1, 2, 3), data: data.clone(), }; sink.send(delivered.clone()); let received = read_frame::(&mut client_recv, MAX_CHUNK_FRAME_LEN).await?; assert_eq!(received, delivered, "the chunk must round-trip intact"); // Close the client so the server task's pump ends and the endpoint winds down cleanly. drop(events_rx); drop(sink); drop(connected); server_task.await??; Ok(()) }