102 lines
3.7 KiB
Rust
102 lines
3.7 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! Loopback integration tests for the Synvael application handshake.
|
|
//!
|
|
//! Each test binds a real QUIC server endpoint on `127.0.0.1:0`, reads the OS-assigned port, and drives a client through the full `ClientHello` -> `HandshakeAck` / `HandshakeReject` exchange, exercising the async `read_frame`/`write_frame` path end-to-end.
|
|
|
|
use net::endpoint::{client_endpoint, server_endpoint};
|
|
use net::handshake::{HandshakeError, accept_connection, connect};
|
|
use shared::protocol::{ClientHello, FeatureFlags, PROTOCOL_VERSION, PlayerIdentity, RejectReason};
|
|
|
|
/// Builds a `ClientHello` for `display_name` advertising `protocol_version`.
|
|
fn hello(display_name: &str, protocol_version: u32) -> ClientHello {
|
|
ClientHello {
|
|
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 happy_path_completes_handshake() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let server = server_endpoint("127.0.0.1:0".parse()?)?;
|
|
let server_addr = server.local_addr()?;
|
|
|
|
// Accept exactly one connection on the server, returning the observed identity. The connection is held open until the client closes it, so the ack frame is reliably delivered before teardown.
|
|
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?;
|
|
let name = conn.hello.player_identity.display_name.clone();
|
|
conn.connection.closed().await;
|
|
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(name)
|
|
});
|
|
|
|
let client = client_endpoint()?;
|
|
let connected = connect(
|
|
&client,
|
|
server_addr,
|
|
"localhost",
|
|
hello("Tester", PROTOCOL_VERSION),
|
|
)
|
|
.await?;
|
|
|
|
assert_eq!(
|
|
connected.ack.protocol_version, PROTOCOL_VERSION,
|
|
"server must ack with the matching protocol version"
|
|
);
|
|
|
|
// Close the client connection so the server's `closed()` wait resolves.
|
|
drop(connected);
|
|
let observed_name = server_task.await??;
|
|
assert_eq!(
|
|
observed_name, "Tester",
|
|
"server must observe the client's display name"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn version_mismatch_is_rejected() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let server = server_endpoint("127.0.0.1:0".parse()?)?;
|
|
let server_addr = server.local_addr()?;
|
|
|
|
let server_task = tokio::spawn(async move {
|
|
let incoming = server.accept().await.ok_or("server endpoint closed")?;
|
|
// The server is expected to return VersionMismatch after sending the reject.
|
|
let result = accept_connection(incoming, "synvael-server-test".to_owned(), 20).await;
|
|
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(result.is_err())
|
|
});
|
|
|
|
let client = client_endpoint()?;
|
|
let result = connect(
|
|
&client,
|
|
server_addr,
|
|
"localhost",
|
|
hello("Tester", PROTOCOL_VERSION + 1),
|
|
)
|
|
.await;
|
|
|
|
match result {
|
|
Err(HandshakeError::Rejected(rej)) => {
|
|
assert_eq!(
|
|
rej.reason,
|
|
RejectReason::ProtocolMismatch,
|
|
"rejection must cite a protocol mismatch"
|
|
);
|
|
}
|
|
other => return Err(format!("expected a rejection, got {other:?}").into()),
|
|
}
|
|
|
|
assert!(
|
|
server_task.await??,
|
|
"server must return an error on mismatch"
|
|
);
|
|
|
|
Ok(())
|
|
}
|