From 55da2e2e2ebac60c91098b8fb9138455d03a7649 Mon Sep 17 00:00:00 2001 From: LukeMathWalker Date: Sun, 14 Feb 2021 16:27:04 +0000 Subject: [PATCH] Share startup logic. --- src/configuration.rs | 8 ++++---- src/main.rs | 29 +++----------------------- src/startup.rs | 32 +++++++++++++++++++++++++++++ tests/api/helpers.rs | 48 ++++++++++++++++++-------------------------- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/src/configuration.rs b/src/configuration.rs index 79752e6..b4a31c8 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -3,21 +3,21 @@ use serde_aux::field_attributes::deserialize_number_from_string; use sqlx::postgres::{PgConnectOptions, PgSslMode}; use std::convert::{TryFrom, TryInto}; -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Clone)] pub struct Settings { pub database: DatabaseSettings, pub application: ApplicationSettings, pub email_client: EmailClientSettings, } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Clone)] pub struct ApplicationSettings { #[serde(deserialize_with = "deserialize_number_from_string")] pub port: u16, pub host: String, } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Clone)] pub struct DatabaseSettings { pub username: String, pub password: String, @@ -48,7 +48,7 @@ impl DatabaseSettings { } } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Clone)] pub struct EmailClientSettings { pub base_url: String, pub sender_email: String, diff --git a/src/main.rs b/src/main.rs index 53b84e6..cf698f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,5 @@ -use sqlx::postgres::PgPoolOptions; -use std::net::TcpListener; use zero2prod::configuration::get_configuration; -use zero2prod::email_client::EmailClient; -use zero2prod::startup::run; +use zero2prod::startup::build; use zero2prod::telemetry::{get_subscriber, init_subscriber}; #[actix_web::main] @@ -11,27 +8,7 @@ async fn main() -> std::io::Result<()> { init_subscriber(subscriber); let configuration = get_configuration().expect("Failed to read configuration."); - let connection_pool = PgPoolOptions::new() - .connect_timeout(std::time::Duration::from_secs(2)) - .connect_with(configuration.database.with_db()) - .await - .expect("Failed to connect to Postgres."); - - let sender_email = configuration - .email_client - .sender() - .expect("Invalid sender email address."); - let email_client = EmailClient::new( - configuration.email_client.base_url, - sender_email, - configuration.email_client.authorization_token, - ); - - let address = format!( - "{}:{}", - configuration.application.host, configuration.application.port - ); - let listener = TcpListener::bind(address)?; - run(listener, connection_pool, email_client)?.await?; + let server = build(configuration).await?; + server.await?; Ok(()) } diff --git a/src/startup.rs b/src/startup.rs index 4cd7c2c..40da8d2 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -1,12 +1,44 @@ +use crate::configuration::{DatabaseSettings, Settings}; use crate::email_client::EmailClient; use crate::routes::{health_check, subscribe}; use actix_web::dev::Server; use actix_web::web::Data; use actix_web::{web, App, HttpServer}; +use sqlx::postgres::PgPoolOptions; use sqlx::PgPool; use std::net::TcpListener; use tracing_actix_web::TracingLogger; +pub async fn build(configuration: Settings) -> Result { + let connection_pool = get_connection_pool(&configuration.database) + .await + .expect("Failed to connect to Postgres."); + + let sender_email = configuration + .email_client + .sender() + .expect("Invalid sender email address."); + let email_client = EmailClient::new( + configuration.email_client.base_url, + sender_email, + configuration.email_client.authorization_token, + ); + + let address = format!( + "{}:{}", + configuration.application.host, configuration.application.port + ); + let listener = TcpListener::bind(address)?; + run(listener, connection_pool, email_client) +} + +pub async fn get_connection_pool(configuration: &DatabaseSettings) -> Result { + PgPoolOptions::new() + .connect_timeout(std::time::Duration::from_secs(2)) + .connect_with(configuration.with_db()) + .await +} + pub fn run( listener: TcpListener, db_pool: PgPool, diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index 5aeed20..04fea9b 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -1,9 +1,7 @@ use sqlx::{Connection, Executor, PgConnection, PgPool}; -use std::net::TcpListener; use uuid::Uuid; use zero2prod::configuration::{get_configuration, DatabaseSettings}; -use zero2prod::email_client::EmailClient; -use zero2prod::startup::run; +use zero2prod::startup::{build, get_connection_pool}; use zero2prod::telemetry::{get_subscriber, init_subscriber}; // Ensure that the `tracing` stack is only initialised once using `lazy_static` @@ -21,35 +19,29 @@ pub struct TestApp { } pub async fn spawn_app() -> TestApp { - // The first time `initialize` is invoked the code in `TRACING` is executed. - // All other invocations will instead skip execution. - lazy_static::initialize(&TRACING); + // Randomise configuration to ensure test isolation + let configuration = { + let mut c = get_configuration().expect("Failed to read configuration."); + // Use a different database for each test case + c.database.database_name = Uuid::new_v4().to_string(); + // Use a random OS port + c.application.port = 0; + c + }; - let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port"); - // We retrieve the port assigned to us by the OS - let port = listener.local_addr().unwrap().port(); - let address = format!("http://127.0.0.1:{}", port); + // Create and migrate the database + configure_database(&configuration.database).await; - let mut configuration = get_configuration().expect("Failed to read configuration."); - configuration.database.database_name = Uuid::new_v4().to_string(); - let connection_pool = configure_database(&configuration.database).await; - - let sender_email = configuration - .email_client - .sender() - .expect("Invalid sender email address."); - let email_client = EmailClient::new( - configuration.email_client.base_url, - sender_email, - configuration.email_client.authorization_token, - ); - - let server = - run(listener, connection_pool.clone(), email_client).expect("Failed to bind address"); + // Launch the application as a background task + let server = build(configuration.clone()) + .await + .expect("Failed to build application."); let _ = tokio::spawn(server); TestApp { - address, - db_pool: connection_pool, + address: todo!(), + db_pool: get_connection_pool(&configuration.database) + .await + .expect("Failed to connect to the database"), } }