diff --git a/src/configuration.rs b/src/configuration.rs index 92d7a6b..a04a53e 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -4,20 +4,20 @@ use sqlx::{postgres::{PgConnectOptions, PgSslMode}, ConnectOptions}; use crate::domain::SubscriberEmail; -#[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 { pub port: u16, pub host: String, } -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize,Clone)] pub struct DatabaseSettings { pub username: String, pub password: Secret, @@ -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 57c0684..d45bb3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,5 @@ -use std::net::TcpListener; -use sqlx::postgres::PgPoolOptions; use zero2prod::configuration::get_configuration; -use zero2prod::email_client; -use zero2prod::startup::run; +use zero2prod::startup::Application; use zero2prod::telemetry::{get_subscriber, init_subscriber}; #[tokio::main] @@ -11,20 +8,7 @@ async fn main() -> std::io::Result<()> { init_subscriber(subscriber); let config = get_configuration().expect("Failed to read configuration"); - - let connection_pool = PgPoolOptions::new().acquire_timeout(std::time::Duration::from_secs(2)) - .max_connections(10).connect_lazy_with(config.database.with_db()); - - let sender_email = config.email_client.sender().unwrap(); - let timeout = config.email_client.timeout(); - let email_client = email_client::EmailClient::new( - config.email_client.base_url, - sender_email, - config.email_client.authorization_token, - timeout, - ); - - let address = format!("{}:{}", config.application.host, config.application.port); - let listener = TcpListener::bind(address).expect("Failed to bind random port"); - run(listener, connection_pool ,email_client)?.await + let app = Application::build(config).await?; + app.run_until_stopped().await?; + Ok(()) } diff --git a/src/startup.rs b/src/startup.rs index 35bb696..d67ba69 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -27,3 +27,44 @@ pub fn run( Ok(server) } + +pub struct Application { + pub port: u16, + pub server: Server, +} + +impl Application { + pub async fn build( + config: crate::configuration::Settings, + ) -> Result { + let connection_pool: Pool = get_connection_pool(config.database); + + let sender_email = config.email_client.sender().unwrap(); + let timeout = config.email_client.timeout(); + let email_client = EmailClient::new( + config.email_client.base_url, + sender_email, + config.email_client.authorization_token, + timeout, + ); + + let port = config.application.port; + let listener = TcpListener::bind(format!("{}:{}", config.application.host, port))?; + let server = run(listener, connection_pool, email_client)?; + Ok(Self { port, server }) + } + + pub fn port(&self) -> u16 { + self.port + } + + pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { + self.server.await + } +} + + + +pub fn get_connection_pool(config: crate::configuration::DatabaseSettings) -> Pool { + Pool::connect_lazy_with(config.with_db()) +} \ No newline at end of file diff --git a/tests/api/helpers.rs b/tests/api/helpers.rs index f03831a..8fc64eb 100644 --- a/tests/api/helpers.rs +++ b/tests/api/helpers.rs @@ -1,10 +1,7 @@ use once_cell::sync::Lazy; -use secrecy::ExposeSecret; use sqlx::{postgres::PgPoolOptions, Connection, Executor, PgConnection, Pool, Postgres}; -use testcontainers::{clients::Cli, RunnableImage}; use uuid::Uuid; -use std:: net::TcpListener; -use zero2prod::{configuration::{get_configuration, DatabaseSettings}, email_client, telemetry::{get_subscriber, init_subscriber}}; +use zero2prod::{configuration::{get_configuration, DatabaseSettings}, startup::{get_connection_pool, Application}, telemetry::{get_subscriber, init_subscriber}}; pub struct TestApp { pub address: String, @@ -24,44 +21,26 @@ static TRACING: Lazy<()> = Lazy::new(|| { } }); -fn create_db(config: &DatabaseSettings) -> RunnableImage { - RunnableImage::from(testcontainers_modules::postgres::Postgres::default()) - .with_env_var(("POSTGRES_PASSWORD", config.password.expose_secret())) - .with_env_var(("POSTGRES_USER", &config.username)) - .with_env_var(("POSTGRES_DB", &config.database_name)) -} - pub async fn spawn_app() -> TestApp { Lazy::force(&TRACING); - let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port"); - let port = listener.local_addr().unwrap().port(); - let address = format!("http://127.0.0.1:{}", port); + let config = { + let mut c = get_configuration().expect("Failed to read configuration"); + c.database.database_name = Uuid::new_v4().to_string(); + c.application.port = 0; + c + }; - let mut config = get_configuration().expect("Failed to read configuration"); - let image = create_db(&config.database); - let docker = Cli::default(); - docker.run(image); - config.database.database_name = Uuid::new_v4().to_string(); - let connection_pool = configure_database(&config.database).await; + configure_database(&config.database).await; - let sender_email = config.email_client.sender().unwrap(); - let timeout = config.email_client.timeout(); - let email_client = email_client::EmailClient::new( - config.email_client.base_url, - sender_email, - config.email_client.authorization_token, - timeout, - ); - - let server = zero2prod::startup::run(listener, connection_pool.clone(), email_client).expect("Failed to bind address"); - let _fut = tokio::spawn(server); + let app = Application::build(config.clone()).await.expect("Failed to build app."); + let address = format!("http://127.0.0.1:{}", app.port()); + let _fut = tokio::spawn(app.run_until_stopped()); TestApp { address, - connection_pool, + connection_pool: get_connection_pool(config.database), } - } pub async fn configure_database(config: &DatabaseSettings) -> Pool {