test(relay): cover real client and gateway sessions
Add a relay-server test that connects the production client-core session and the production gateway session to the same in-process relay. The test verifies room join catch-up, negotiated peer identities, Ethernet datagrams in both directions, stats reporting, and room/session cleanup after graceful shutdown. The existing relay tests covered forwarding with raw Quinn peers. This adds a higher-level proof that the three MVP components agree on the same control and datagram contracts before the final Windows TAP and physical LAN test. Test Plan: - cargo fmt --check - cargo test -p lanparty-relay bridges_real_client_and_gateway_sessions -- --nocapture - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings - git diff --check Refs: PLAN.md Phase 1 relay/client/gateway proof
This commit is contained in:
Generated
+2
@@ -540,7 +540,9 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
|
"lanparty-client-core",
|
||||||
"lanparty-ctrl",
|
"lanparty-ctrl",
|
||||||
|
"lanparty-gateway",
|
||||||
"lanparty-net",
|
"lanparty-net",
|
||||||
"lanparty-obs",
|
"lanparty-obs",
|
||||||
"lanparty-proto",
|
"lanparty-proto",
|
||||||
|
|||||||
@@ -16,3 +16,7 @@ rcgen.workspace = true
|
|||||||
rustls.workspace = true
|
rustls.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
lanparty-client-core = { path = "../lanparty-client-core" }
|
||||||
|
lanparty-gateway = { path = "../lanparty-gateway" }
|
||||||
|
|||||||
@@ -905,7 +905,9 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use lanparty_client_core::{ClientSessionConfig, connect_client};
|
||||||
use lanparty_ctrl::{RoomCode, decode_control_frame, encode_control_message};
|
use lanparty_ctrl::{RoomCode, decode_control_frame, encode_control_message};
|
||||||
|
use lanparty_gateway::{GatewayConfig, connect_gateway};
|
||||||
use lanparty_proto::{FrameType, MacAddr, decode_datagram, encode_datagram};
|
use lanparty_proto::{FrameType, MacAddr, decode_datagram, encode_datagram};
|
||||||
use quinn::{ClientConfig, crypto::rustls::QuicClientConfig};
|
use quinn::{ClientConfig, crypto::rustls::QuicClientConfig};
|
||||||
|
|
||||||
@@ -1223,6 +1225,139 @@ mod tests {
|
|||||||
assert!(sessions.lock().await.is_empty());
|
assert!(sessions.lock().await.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn bridges_real_client_and_gateway_sessions() {
|
||||||
|
let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);
|
||||||
|
let rooms = Arc::clone(&server.rooms);
|
||||||
|
let sessions = Arc::clone(&server.sessions);
|
||||||
|
let server_addr = server.local_addr().unwrap();
|
||||||
|
let server_task = tokio::spawn(async move {
|
||||||
|
let handles = server.accept_many_for_test(2).await.unwrap();
|
||||||
|
let mut accepted = Vec::with_capacity(handles.len());
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
accepted.push(handle.await.unwrap().unwrap().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
server.shutdown("test complete").await;
|
||||||
|
accepted
|
||||||
|
});
|
||||||
|
let cert_der = certificate.as_ref().to_vec();
|
||||||
|
let room = RoomCode::new("TESTROOM").unwrap();
|
||||||
|
let client_mac = client_mac(1);
|
||||||
|
let gateway = connect_gateway(
|
||||||
|
GatewayConfig::new(
|
||||||
|
server_addr,
|
||||||
|
"lanparty-relay.local",
|
||||||
|
cert_der.clone(),
|
||||||
|
room.clone(),
|
||||||
|
"eth0",
|
||||||
|
1400,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let client = connect_client(
|
||||||
|
ClientSessionConfig::new(
|
||||||
|
server_addr,
|
||||||
|
"lanparty-relay.local",
|
||||||
|
cert_der,
|
||||||
|
room.clone(),
|
||||||
|
client_mac,
|
||||||
|
1400,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gateway.welcome().room_id(), client.welcome().room_id());
|
||||||
|
assert_eq!(gateway.welcome().peer_id(), 1);
|
||||||
|
assert_eq!(client.welcome().peer_id(), 2);
|
||||||
|
assert!(client.welcome().gateway_connected());
|
||||||
|
assert_eq!(
|
||||||
|
gateway.quic_max_datagram_size(),
|
||||||
|
client.quic_max_datagram_size()
|
||||||
|
);
|
||||||
|
|
||||||
|
let ControlMessage::PeerJoined(peer) =
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), gateway.recv_control_event())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected gateway to observe client join");
|
||||||
|
};
|
||||||
|
assert_eq!(peer.peer_id(), client.welcome().peer_id());
|
||||||
|
assert_eq!(peer.role(), Role::Client);
|
||||||
|
assert_eq!(peer.mac(), Some(client_mac));
|
||||||
|
|
||||||
|
let ControlMessage::PeerJoined(peer) =
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), client.recv_control_event())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected client to receive gateway catch-up event");
|
||||||
|
};
|
||||||
|
assert_eq!(peer.peer_id(), gateway.welcome().peer_id());
|
||||||
|
assert_eq!(peer.role(), Role::Gateway);
|
||||||
|
|
||||||
|
let client_to_lan = ethernet_frame(gateway_mac(), client_mac);
|
||||||
|
assert_eq!(
|
||||||
|
client
|
||||||
|
.relay_io()
|
||||||
|
.send_ethernet_with_outcome(&client_to_lan)
|
||||||
|
.unwrap(),
|
||||||
|
lanparty_client_core::ClientSendOutcome::Sent
|
||||||
|
);
|
||||||
|
let received = tokio::time::timeout(Duration::from_secs(5), gateway.recv_ethernet())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(received.source_peer_id(), client.welcome().peer_id());
|
||||||
|
assert_eq!(received.payload(), client_to_lan.as_slice());
|
||||||
|
|
||||||
|
let lan_to_client = ethernet_frame(client_mac, gateway_mac());
|
||||||
|
gateway.send_ethernet(&lan_to_client).unwrap();
|
||||||
|
let received =
|
||||||
|
tokio::time::timeout(Duration::from_secs(5), client.relay_io().recv_ethernet())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(received.source_peer_id(), gateway.welcome().peer_id());
|
||||||
|
assert_eq!(received.payload(), lan_to_client.as_slice());
|
||||||
|
|
||||||
|
client.send_stats_snapshot().await.unwrap();
|
||||||
|
gateway.send_stats_snapshot().await.unwrap();
|
||||||
|
wait_for_peer_stats(
|
||||||
|
&sessions,
|
||||||
|
&room,
|
||||||
|
client.welcome().peer_id(),
|
||||||
|
&client.stats_snapshot(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
wait_for_peer_stats(
|
||||||
|
&sessions,
|
||||||
|
&room,
|
||||||
|
gateway.welcome().peer_id(),
|
||||||
|
&gateway.stats_snapshot(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
client.shutdown("test client done").await;
|
||||||
|
gateway.shutdown("test gateway done").await;
|
||||||
|
|
||||||
|
let accepted = tokio::time::timeout(Duration::from_secs(5), server_task)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(accepted.len(), 2);
|
||||||
|
assert_eq!(rooms.lock().await.room_count(), 0);
|
||||||
|
assert!(sessions.lock().await.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn forwards_graceful_disconnect_reason_to_remaining_peers() {
|
async fn forwards_graceful_disconnect_reason_to_remaining_peers() {
|
||||||
let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);
|
let (server, certificate) = bind_test_server(DEFAULT_MAX_CLIENTS_PER_ROOM);
|
||||||
@@ -1422,6 +1557,10 @@ mod tests {
|
|||||||
MacAddr::new([0x02, 0, 0, 0, 0, last])
|
MacAddr::new([0x02, 0, 0, 0, 0, last])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gateway_mac() -> MacAddr {
|
||||||
|
MacAddr::new([0x0a, 0, 0, 0, 0, 1])
|
||||||
|
}
|
||||||
|
|
||||||
fn ethernet_frame(destination: MacAddr, source: MacAddr) -> Vec<u8> {
|
fn ethernet_frame(destination: MacAddr, source: MacAddr) -> Vec<u8> {
|
||||||
let mut frame = Vec::new();
|
let mut frame = Vec::new();
|
||||||
frame.extend_from_slice(&destination.octets());
|
frame.extend_from_slice(&destination.octets());
|
||||||
|
|||||||
Reference in New Issue
Block a user