From 4133b3cf39b562fdb2542a914330755a57b318db Mon Sep 17 00:00:00 2001 From: matt-hagemann Date: Fri, 1 Sep 2023 12:27:05 +0200 Subject: [PATCH] feat: cargo make + docker for e2e tests --- Makefile.toml | 52 +++++++++++++++++++ docker-compose.yml | 12 +++-- docker/database/Dockerfile | 5 ++ docker/database/init.sh | 15 ++++++ example.env | 11 +++- sql/bootstrap/roles.down.sql | 4 -- sql/bootstrap/roles.up.sql | 6 --- src/app/run.rs | 4 +- src/utils/migrator.rs | 2 +- tests/app_tests/lifecycle_test.rs | 12 ++--- tests/helpers/hooks.rs | 27 ++-------- tests/helpers/with_lifecycle.rs | 7 ++- .../reject_invalid_register_test.rs | 46 +++++++--------- tests/user_tests/simple_lifecycle_test.rs | 20 +++---- 14 files changed, 132 insertions(+), 91 deletions(-) create mode 100644 Makefile.toml create mode 100644 docker/database/Dockerfile create mode 100644 docker/database/init.sh delete mode 100644 sql/bootstrap/roles.down.sql delete mode 100644 sql/bootstrap/roles.up.sql diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..3ba4b8d --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,52 @@ +[tasks.docker-up] +script = [ + "docker compose up --detach", + "sleep 3", +] + +[tasks.run-server] +script = [ + "cargo run &", + "echo $! > server.pid", +] + +[tasks.run-tests] +script = [ + "cargo test", + "sleep 1" +] + +[tasks.wait-for-server] +script = [ + "until nc -z -v -w5 localhost 8080; do", + " echo 'Waiting for server to start on port 8080...'", + " sleep 1", + "done" +] + +[tasks.kill-server] +script = [ + "kill $(cat server.pid)", + "rm server.pid" +] + +[tasks.docker-down] +script = [ + "docker compose down" +] + +[tasks.full-test] +dependencies = [ + "docker-up", + "run-server", + "wait-for-server", + "run-tests", + "kill-server", + "docker-down" +] + +[tasks.full-clean] +script = [ + "docker rmi ratings-postgres && docker rmi redis", + "cargo clean" +] diff --git a/docker-compose.yml b/docker-compose.yml index 74433e2..f2440d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,12 +3,18 @@ version: '3' services: postgres: - image: postgres + build: ./docker/database restart: always ports: - - 5432:5432 + - "5433:5432" environment: - - POSTGRES_PASSWORD=postgres + POSTGRES_USER: ${DOCKER_POSTGRES_USER} + POSTGRES_PASSWORD: ${DOCKER_POSTGRES_PASSWORD} + MIGRATION_USER: ${DOCKER_MIGRATION_USER} + MIGRATION_PASSWORD: ${DOCKER_MIGRATION_PASSWORD} + SERVICE_USER: ${DOCKER_SERVICE_USER} + SERVICE_PASSWORD: ${DOCKER_SERVICE_PASSWORD} + RATINGS_DB: ${DOCKER_RATINGS_DB} redis: image: redis diff --git a/docker/database/Dockerfile b/docker/database/Dockerfile new file mode 100644 index 0000000..096ace0 --- /dev/null +++ b/docker/database/Dockerfile @@ -0,0 +1,5 @@ +FROM postgres:latest + +COPY init.sh /docker-entrypoint-initdb.d/ + +RUN chmod +x /docker-entrypoint-initdb.d/init.sh diff --git a/docker/database/init.sh b/docker/database/init.sh new file mode 100644 index 0000000..d3335f1 --- /dev/null +++ b/docker/database/init.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +# Create users and databases +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "postgres" <<-EOSQL + CREATE USER $MIGRATION_USER WITH PASSWORD '$MIGRATION_PASSWORD'; + CREATE USER $SERVICE_USER WITH PASSWORD '$SERVICE_PASSWORD'; + CREATE DATABASE $RATINGS_DB; +EOSQL + +# Set permissions on the new database +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$RATINGS_DB" <<-EOSQL + GRANT CONNECT ON DATABASE $RATINGS_DB TO $MIGRATION_USER; + GRANT USAGE, CREATE ON SCHEMA public TO $MIGRATION_USER; +EOSQL diff --git a/example.env b/example.env index fe7b6ce..9e8b1f8 100644 --- a/example.env +++ b/example.env @@ -5,4 +5,13 @@ APP_LOG_LEVEL=info APP_NAME=ratings APP_PORT=8080 # Update this with some real PostgreSQL details -APP_POSTGRES_URI=postgresql://username:password@localhost:5432/ratings +APP_POSTGRES_URI=postgresql://service:covfefe!1@localhost:5433/ratings +APP_MIGRATION_POSTGRES_URI=postgresql://migration_user:strongpassword@localhost:5433/ratings + +DOCKER_POSTGRES_USER=postgres +DOCKER_POSTGRES_PASSWORD=@1234 +DOCKER_MIGRATION_USER=migration_user +DOCKER_MIGRATION_PASSWORD=strongpassword +DOCKER_SERVICE_USER=service +DOCKER_SERVICE_PASSWORD=covfefe!1 +DOCKER_RATINGS_DB=ratings diff --git a/sql/bootstrap/roles.down.sql b/sql/bootstrap/roles.down.sql deleted file mode 100644 index ddc0239..0000000 --- a/sql/bootstrap/roles.down.sql +++ /dev/null @@ -1,4 +0,0 @@ -DROP DATABASE RATINGS; -DROP USER IF EXISTS service; -REVOKE USAGE, CREATE ON SCHEMA public FROM migration_user; -DROP USER IF EXISTS migration_user; diff --git a/sql/bootstrap/roles.up.sql b/sql/bootstrap/roles.up.sql deleted file mode 100644 index de46060..0000000 --- a/sql/bootstrap/roles.up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE USER migration_user WITH PASSWORD 'strongpassword'; -CREATE USER service WITH PASSWORD 'covfefe!1'; -CREATE DATABASE ratings; --- /c ratings; -GRANT CONNECT ON DATABASE ratings TO migration_user; -GRANT USAGE, CREATE ON SCHEMA public TO migration_user; diff --git a/src/app/run.rs b/src/app/run.rs index c8e76fa..1efd51f 100644 --- a/src/app/run.rs +++ b/src/app/run.rs @@ -5,12 +5,14 @@ use tonic::transport::Server; use tower::ServiceBuilder; use tracing::info; -use crate::utils::{Config, Infrastructure}; +use crate::utils::{Config, Infrastructure, Migrator}; use super::interfaces::routes::{build_reflection_service, build_servers}; use super::interfaces::{authentication::authentication, middleware::ContextMiddlewareLayer}; pub async fn run(config: Config) -> Result<(), Box> { + let migrator = Migrator::new(&config.migration_postgres_uri).await?; + migrator.run().await?; let infra = Infrastructure::new(&config).await?; let app_ctx = AppContext::new(&config, infra); diff --git a/src/utils/migrator.rs b/src/utils/migrator.rs index cab57b3..4023de7 100644 --- a/src/utils/migrator.rs +++ b/src/utils/migrator.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use sqlx::{postgres::PgPoolOptions, PgPool}; -const MIGRATIONS_PATH: &str = "sql/migrations"; +const MIGRATIONS_PATH: &str = "./sql/migrations"; #[derive(Clone)] pub struct Migrator { diff --git a/tests/app_tests/lifecycle_test.rs b/tests/app_tests/lifecycle_test.rs index abd1cd6..515da41 100644 --- a/tests/app_tests/lifecycle_test.rs +++ b/tests/app_tests/lifecycle_test.rs @@ -1,7 +1,7 @@ use futures::FutureExt; use ratings::{ app::AppContext, - utils::{Config, Infrastructure, Migrator}, + utils::{Config, Infrastructure}, }; use super::super::helpers::with_lifecycle::with_lifecycle; @@ -19,7 +19,6 @@ async fn app_lifecycle_test() -> Result<(), Box> { let infra = Infrastructure::new(&config).await?; let app_ctx = AppContext::new(&config, infra); - let migrator = Migrator::new(&config.migration_postgres_uri).await?; let data = TestData { user_client: Some(UserClient::new(&config.socket())), app_ctx, @@ -29,12 +28,9 @@ async fn app_lifecycle_test() -> Result<(), Box> { snap_id: Some(data_faker::rnd_id()), }; - with_lifecycle( - async { - vote_once(data.clone()).then(vote_up).await; - }, - migrator, - ) + with_lifecycle(async { + vote_once(data.clone()).then(vote_up).await; + }) .await; Ok(()) } diff --git a/tests/helpers/hooks.rs b/tests/helpers/hooks.rs index 3857d8c..525d307 100644 --- a/tests/helpers/hooks.rs +++ b/tests/helpers/hooks.rs @@ -1,32 +1,11 @@ -use std::sync::{Arc, Once}; - -use once_cell::sync::Lazy; -use ratings::utils::Migrator; -use tokio::sync::Mutex; +use std::sync::Once; static INIT: Once = Once::new(); -static TEST_COUNTER: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(0))); -pub async fn before_all(migrator: Migrator) { +pub async fn before_all() { INIT.call_once(|| { tracing_subscriber::fmt().init(); }); - if let Err(e) = migrator.run().await { - panic!("{}", e) - } - - let counter = Arc::clone(&*TEST_COUNTER); - let mut test_counter = counter.lock().await; - *test_counter += 1; } -pub async fn after_all(migrator: Migrator) { - let counter = Arc::clone(&*TEST_COUNTER); - let mut test_counter = counter.lock().await; - *test_counter -= 1; - if *test_counter == 0 { - if let Err(e) = migrator.revert().await { - panic!("{}", e) - } - } -} +pub async fn after_all() {} diff --git a/tests/helpers/with_lifecycle.rs b/tests/helpers/with_lifecycle.rs index 4b4b603..9dae81b 100644 --- a/tests/helpers/with_lifecycle.rs +++ b/tests/helpers/with_lifecycle.rs @@ -1,13 +1,12 @@ use std::future::Future; use crate::helpers::hooks::{after_all, before_all}; -use ratings::utils::Migrator; -pub async fn with_lifecycle(f: F, migrator: Migrator) +pub async fn with_lifecycle(f: F) where F: Future, { - before_all(migrator.clone()).await; + before_all().await; f.await; - after_all(migrator.clone()).await; + after_all().await; } diff --git a/tests/user_tests/reject_invalid_register_test.rs b/tests/user_tests/reject_invalid_register_test.rs index d1e82bf..f970604 100644 --- a/tests/user_tests/reject_invalid_register_test.rs +++ b/tests/user_tests/reject_invalid_register_test.rs @@ -1,4 +1,4 @@ -use ratings::utils::{Config, Migrator}; +use ratings::utils::Config; use tonic::Code; use super::super::helpers::{client_user::UserClient, with_lifecycle::with_lifecycle}; @@ -6,22 +6,18 @@ use super::super::helpers::{client_user::UserClient, with_lifecycle::with_lifecy #[tokio::test] async fn blank() -> Result<(), Box> { let config = Config::load()?; - let migrator = Migrator::new(&config.migration_postgres_uri).await?; - with_lifecycle( - async { - let id = ""; - let client = UserClient::new(&config.socket()); + with_lifecycle(async { + let id = ""; + let client = UserClient::new(&config.socket()); - match client.register(id).await { - Ok(response) => panic!("expected Err but got Ok: {response:?}"), - Err(status) => { - assert_eq!(status.code(), Code::InvalidArgument) - } + match client.register(id).await { + Ok(response) => panic!("expected Err but got Ok: {response:?}"), + Err(status) => { + assert_eq!(status.code(), Code::InvalidArgument) } - }, - migrator, - ) + } + }) .await; Ok(()) } @@ -29,22 +25,18 @@ async fn blank() -> Result<(), Box> { #[tokio::test] async fn wrong_length() -> Result<(), Box> { let config = Config::load()?; - let migrator = Migrator::new(&config.migration_postgres_uri).await?; - with_lifecycle( - async { - let client_hash = "foobarbazbun"; - let client = UserClient::new(&config.socket()); + with_lifecycle(async { + let client_hash = "foobarbazbun"; + let client = UserClient::new(&config.socket()); - match client.register(client_hash).await { - Ok(response) => panic!("expected Err but got Ok: {response:?}"), - Err(status) => { - assert_eq!(status.code(), Code::InvalidArgument) - } + match client.register(client_hash).await { + Ok(response) => panic!("expected Err but got Ok: {response:?}"), + Err(status) => { + assert_eq!(status.code(), Code::InvalidArgument) } - }, - migrator, - ) + } + }) .await; Ok(()) } diff --git a/tests/user_tests/simple_lifecycle_test.rs b/tests/user_tests/simple_lifecycle_test.rs index 6f1b17c..2e6a94f 100644 --- a/tests/user_tests/simple_lifecycle_test.rs +++ b/tests/user_tests/simple_lifecycle_test.rs @@ -6,7 +6,7 @@ use super::super::helpers::client_user::UserClient; use super::super::helpers::with_lifecycle::with_lifecycle; use futures::FutureExt; use ratings::app::AppContext; -use ratings::utils::{self, Infrastructure, Migrator}; +use ratings::utils::{self, Infrastructure}; use sqlx::Row; use utils::Config; @@ -16,7 +16,6 @@ async fn user_simple_lifecycle_test() -> Result<(), Box> let config = Config::load()?; let infra = Infrastructure::new(&config).await?; let app_ctx = AppContext::new(&config, infra); - let migrator = Migrator::new(&config.migration_postgres_uri).await?; let data = TestData { user_client: Some(UserClient::new(&config.socket())), @@ -27,16 +26,13 @@ async fn user_simple_lifecycle_test() -> Result<(), Box> snap_id: None, }; - with_lifecycle( - async { - register(data.clone()) - .then(authenticate) - .then(vote) - .then(delete) - .await; - }, - migrator, - ) + with_lifecycle(async { + register(data.clone()) + .then(authenticate) + .then(vote) + .then(delete) + .await; + }) .await; Ok(()) }