Skip to content

Commit

Permalink
Move start_server to library
Browse files Browse the repository at this point in the history
And use it in integration tests.
  • Loading branch information
praseodym committed Mar 5, 2025
1 parent f73f0af commit e48839a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 87 deletions.
86 changes: 3 additions & 83 deletions backend/src/bin/abacus.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
#[cfg(feature = "dev-database")]
use abacus::fixtures;
use abacus::router;
use axum::serve::ListenerExt;
use abacus::{fixtures, start_server};
use clap::Parser;
use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
use std::{
error::Error,
net::{Ipv4Addr, SocketAddr},
str::FromStr,
};
use tokio::{net::TcpListener, signal};
use tracing::{info, level_filters::LevelFilter, trace};
use tokio::net::TcpListener;
use tracing::{info, level_filters::LevelFilter};
use tracing_subscriber::EnvFilter;

/// Abacus API and asset server
Expand All @@ -35,27 +33,6 @@ struct Args {
reset_database: bool,
}

/// Start the API server on the given port, using the given database pool.
async fn start_server(pool: SqlitePool, listener: TcpListener) -> Result<(), Box<dyn Error>> {
let app = router(pool)?;

info!("Starting API server on http://{}", listener.local_addr()?);
let listener = listener.tap_io(|tcp_stream| {
if let Err(err) = tcp_stream.set_nodelay(true) {
trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");
}
});

axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(shutdown_signal())
.await?;

Ok(())
}

/// Main entry point for the application. Sets up the database, and starts the
/// API server and in-memory file router on port 8080.
#[tokio::main]
Expand Down Expand Up @@ -102,60 +79,3 @@ async fn create_sqlite_pool(

Ok(pool)
}

/// Graceful shutdown, useful for Docker containers.
///
/// Copied from the
/// [axum graceful-shutdown example](https://github.com/tokio-rs/axum/blob/6318b57fda6b524b4d3c7909e07946e2b246ebd2/examples/graceful-shutdown/src/main.rs)
/// (under the MIT license).
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};

#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};

#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}

#[cfg(test)]
mod test {
use sqlx::SqlitePool;
use test_log::test;
use tokio::net::TcpListener;

use super::start_server;

#[test(sqlx::test)]
async fn test_abacus_starts(pool: SqlitePool) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();

let task = tokio::spawn(async move {
start_server(pool, listener).await.unwrap();
});

let result = reqwest::get(format!("http://{addr}/api/user/whoami"))
.await
.unwrap();

assert_eq!(result.status(), 401);

task.abort();
let _ = task.await;
}
}
83 changes: 82 additions & 1 deletion backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ use axum::{
extract::FromRef,
middleware,
routing::{get, post, put},
serve::ListenerExt,
};
#[cfg(feature = "memory-serve")]
use memory_serve::MemoryServe;
use sqlx::SqlitePool;
use std::error::Error;
use std::{error::Error, net::SocketAddr};
use tokio::{net::TcpListener, signal};
use tower_http::trace::TraceLayer;
use tracing::{info, trace};
#[cfg(feature = "openapi")]
use utoipa_swagger_ui::SwaggerUi;

Expand Down Expand Up @@ -241,3 +244,81 @@ pub fn create_openapi() -> utoipa::openapi::OpenApi {
struct ApiDoc;
ApiDoc::openapi()
}

/// Start the API server on the given port, using the given database pool.
pub async fn start_server(pool: SqlitePool, listener: TcpListener) -> Result<(), Box<dyn Error>> {
let app = router(pool)?;

info!("Starting API server on http://{}", listener.local_addr()?);
let listener = listener.tap_io(|tcp_stream| {
if let Err(err) = tcp_stream.set_nodelay(true) {
trace!("failed to set TCP_NODELAY on incoming connection: {err:#}");

Check warning on line 255 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L255

Added line #L255 was not covered by tests
}
});

axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(shutdown_signal())
.await?;

Ok(())
}

Check warning on line 267 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L266-L267

Added lines #L266 - L267 were not covered by tests

/// Graceful shutdown, useful for Docker containers.
///
/// Copied from the
/// [axum graceful-shutdown example](https://github.com/tokio-rs/axum/blob/6318b57fda6b524b4d3c7909e07946e2b246ebd2/examples/graceful-shutdown/src/main.rs)
/// (under the MIT license).
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};

Check warning on line 279 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L278-L279

Added lines #L278 - L279 were not covered by tests

#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};

Check warning on line 287 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L287

Added line #L287 was not covered by tests

#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}

Check warning on line 296 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L296

Added line #L296 was not covered by tests

#[cfg(test)]
mod test {
use sqlx::SqlitePool;
use test_log::test;
use tokio::net::TcpListener;

use super::start_server;

#[test(sqlx::test)]
async fn test_abacus_starts(pool: SqlitePool) {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();

let task = tokio::spawn(async move {
start_server(pool, listener).await.unwrap();
});

Check warning on line 313 in backend/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

backend/src/lib.rs#L313

Added line #L313 was not covered by tests

let result = reqwest::get(format!("http://{addr}/api/user/whoami"))
.await
.unwrap();

assert_eq!(result.status(), 401);

task.abort();
let _ = task.await;
}
}
5 changes: 2 additions & 3 deletions backend/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ use sqlx::SqlitePool;
use std::net::SocketAddr;
use tokio::net::TcpListener;

use abacus::router;
use abacus::start_server;

pub async fn serve_api(pool: SqlitePool) -> SocketAddr {
let app = router(pool).unwrap();
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
axum::serve(listener, app).await.unwrap();
start_server(pool, listener).await.unwrap();
});
addr
}

0 comments on commit e48839a

Please sign in to comment.