fix(server): fix TOCTOU race condition in tests

This commit is contained in:
2026-06-06 15:20:47 +02:00
parent c700d65b34
commit 3c86e9eb36
2 changed files with 39 additions and 29 deletions
+3 -6
View File
@@ -24,7 +24,7 @@ pub mod startup;
/// Logging and tracing setup
pub mod telemetry;
type MaybeListener = Option<poem::listener::TcpListener<String>>;
type MaybeListener = Option<std::net::TcpListener>;
fn prepare(listener: MaybeListener) -> startup::Application {
dotenvy::dotenv().ok();
@@ -70,11 +70,8 @@ pub async fn run(listener: MaybeListener) -> Result<(), std::io::Error> {
}
#[cfg(test)]
fn make_random_tcp_listener() -> poem::listener::TcpListener<String> {
let tcp_listener =
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener");
let port = tcp_listener.local_addr().unwrap().port();
poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"))
fn make_random_tcp_listener() -> std::net::TcpListener {
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener")
}
#[cfg(test)]
+36 -23
View File
@@ -6,6 +6,7 @@
//! - Configuring CORS
//! - Starting the HTTP server
use poem::listener::{Listener, TcpAcceptor};
use poem::middleware::{AddDataEndpoint, Cors, CorsEndpoint};
use poem::{EndpointExt, Route};
use poem_openapi::OpenApiService;
@@ -19,10 +20,21 @@ use crate::{
use crate::middleware::rate_limit::RateLimitEndpoint;
type Server = poem::Server<poem::listener::TcpListener<String>, std::convert::Infallible>;
type Server = poem::Server<poem::listener::BoxListener, std::convert::Infallible>;
/// The configured application with rate limiting, CORS, and settings data.
pub type App = AddDataEndpoint<CorsEndpoint<RateLimitEndpoint<Route>>, Settings>;
struct PreBoundListener(std::net::TcpListener);
impl Listener for PreBoundListener {
type Acceptor = TcpAcceptor;
async fn into_acceptor(self) -> std::io::Result<Self::Acceptor> {
TcpAcceptor::from_std(self.0)
}
}
/// Application builder that holds the server configuration before running.
pub struct Application {
server: Server,
@@ -135,30 +147,33 @@ impl Application {
fn setup_server(
settings: &Settings,
tcp_listener: Option<poem::listener::TcpListener<String>>,
) -> Server {
let tcp_listener = tcp_listener.unwrap_or_else(|| {
let address = format!(
"{}:{}",
settings.application.host, settings.application.port
);
poem::listener::TcpListener::bind(address)
});
poem::Server::new(tcp_listener)
tcp_listener: Option<std::net::TcpListener>,
) -> (Server, u16, String) {
tcp_listener.map_or_else(
|| {
let port = settings.application.port;
let host = settings.application.host.clone();
let address = format!("{host}:{port}");
let server = poem::Server::new(poem::listener::TcpListener::bind(address).boxed());
(server, port, host)
},
|listener| {
let addr = listener.local_addr().expect("Failed to get bound address");
let port = addr.port();
let host = addr.ip().to_string();
let server = poem::Server::new(PreBoundListener(listener).boxed());
(server, port, host)
},
)
}
/// Builds a new application with the given settings and optional TCP listener.
///
/// If no listener is provided, one will be created based on the settings.
#[must_use]
pub fn build(
settings: Settings,
tcp_listener: Option<poem::listener::TcpListener<String>>,
) -> Self {
let port = settings.application.port;
let host = settings.application.host.clone();
pub fn build(settings: Settings, tcp_listener: Option<std::net::TcpListener>) -> Self {
let (server, port, host) = Self::setup_server(&settings, tcp_listener);
let app = Self::setup_app(&settings);
let server = Self::setup_server(&settings, tcp_listener);
Self {
server,
app,
@@ -243,13 +258,11 @@ mod tests {
#[test]
fn application_with_custom_listener() {
let settings = create_test_settings();
let tcp_listener =
let listener =
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
let port = tcp_listener.local_addr().unwrap().port();
let listener = poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"));
let expected_port = listener.local_addr().unwrap().port();
let app = Application::build(settings, Some(listener));
assert_eq!(app.host(), "127.0.0.1");
assert_eq!(app.port(), 8080);
assert_eq!(app.port(), expected_port);
}
}