test(app-server-transport): cover expired pairing token

This commit is contained in:
Anton Panasenko
2026-05-29 03:02:41 -07:00
parent f73c101e1e
commit 19dd22aebf
3 changed files with 124 additions and 4 deletions

View File

@@ -16,6 +16,8 @@ const REMOTE_CONTROL_PAIRING_TIMEOUT: std::time::Duration = std::time::Duration:
pub(super) struct RemoteControlPairingClient {
pairing_url: String,
remote_control_token: String,
server_id: String,
environment_id: String,
expires_at: OffsetDateTime,
}
@@ -23,11 +25,15 @@ impl RemoteControlPairingClient {
pub(super) fn new(
remote_control_target: &RemoteControlTarget,
remote_control_token: String,
server_id: String,
environment_id: String,
expires_at: OffsetDateTime,
) -> Self {
Self {
pairing_url: remote_control_target.pair_url.clone(),
remote_control_token,
server_id,
environment_id,
expires_at,
}
}
@@ -89,7 +95,15 @@ impl RemoteControlPairingClient {
environment_id,
expires_at,
} = pairing;
let _ = server_id;
if server_id != self.server_id || environment_id != self.environment_id {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!(
"remote control pairing returned mismatched enrollment: expected server_id={}, environment_id={}; got server_id={}, environment_id={}",
self.server_id, self.environment_id, server_id, environment_id
),
));
}
let expires_at = OffsetDateTime::parse(&expires_at, &Rfc3339)
.map_err(|err| {
io::Error::new(

View File

@@ -102,6 +102,8 @@ async fn start_remote_control_pairing_uses_server_token_and_maps_response() {
pair_url,
},
"remote-control-token".to_string(),
"server-id".to_string(),
"environment-id".to_string(),
OffsetDateTime::from_unix_timestamp(33_336_362_096).expect("future timestamp should parse"),
);
@@ -168,6 +170,8 @@ async fn start_remote_control_pairing_preserves_backend_error_context() {
pair_url,
},
"remote-control-token".to_string(),
"server-id".to_string(),
"environment-id".to_string(),
OffsetDateTime::from_unix_timestamp(33_336_362_096).expect("future timestamp should parse"),
);
@@ -184,3 +188,100 @@ async fn start_remote_control_pairing_preserves_backend_error_context() {
)
);
}
#[tokio::test]
async fn start_remote_control_pairing_rejects_expired_server_token() {
let client = RemoteControlPairingClient::new(
&RemoteControlTarget {
websocket_url: "ws://unused".to_string(),
enroll_url: "http://unused".to_string(),
refresh_url: "http://unused".to_string(),
pair_url: "http://unused".to_string(),
},
"remote-control-token".to_string(),
"server-id".to_string(),
"environment-id".to_string(),
OffsetDateTime::from_unix_timestamp(0).expect("expired timestamp should parse"),
);
let err = client
.start(StartRemoteControlPairingRequest { manual_code: false })
.await
.expect_err("expired server token should fail pairing");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"remote control pairing is unavailable because the server token expired"
);
}
#[tokio::test]
async fn start_remote_control_pairing_rejects_mismatched_enrollment() {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("listener should bind");
let pair_url = format!(
"http://{}/backend-api/wham/remote/control/server/pair",
listener.local_addr().expect("listener should have addr")
);
let server_task = tokio::spawn(async move {
let (stream, _) = listener.accept().await.expect("request should arrive");
let mut reader = BufReader::new(stream);
loop {
let mut line = String::new();
reader
.read_line(&mut line)
.await
.expect("request line should read");
if line == "\r\n" {
break;
}
}
let response_body = json!({
"pairing_code": "pairing-code",
"manual_pairing_code": null,
"server_id": "other-server-id",
"environment_id": "other-environment-id",
"expires_at": "3026-05-22T12:34:56Z",
})
.to_string();
reader
.get_mut()
.write_all(
format!(
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\r\n{response_body}",
response_body.len()
)
.as_bytes(),
)
.await
.expect("response should write");
});
let client = RemoteControlPairingClient::new(
&RemoteControlTarget {
websocket_url: "ws://unused".to_string(),
enroll_url: "http://unused".to_string(),
refresh_url: "http://unused".to_string(),
pair_url,
},
"remote-control-token".to_string(),
"server-id".to_string(),
"environment-id".to_string(),
OffsetDateTime::from_unix_timestamp(33_336_362_096).expect("future timestamp should parse"),
);
let err = client
.start(StartRemoteControlPairingRequest { manual_code: false })
.await
.expect_err("mismatched enrollment should fail pairing");
server_task.await.expect("server task should finish");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
assert_eq!(
err.to_string(),
"remote control pairing returned mismatched enrollment: expected server_id=server-id, environment_id=environment-id; got server_id=other-server-id, environment_id=other-environment-id"
);
}

View File

@@ -1504,9 +1504,14 @@ fn set_pairing_client(
})?;
*pairing_client
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(
RemoteControlPairingClient::new(remote_control_target, remote_control_token, expires_at),
);
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(RemoteControlPairingClient::new(
remote_control_target,
remote_control_token,
enrollment.server_id.clone(),
enrollment.environment_id.clone(),
expires_at,
));
Ok(())
}