From e25213ed1b64adf5915d34bf464e6b2169015d18 Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 18 Jun 2025 22:38:00 +0200 Subject: [PATCH] tests are robusts running in parallel --- Cargo.lock | 1 + server/Cargo.toml | 1 + server/tests/common/mod.rs | 85 ++++++++++++------- .../post_table_definition_test.rs | 15 +++- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2858e2..d04d3fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2846,6 +2846,7 @@ dependencies = [ "lazy_static", "prost", "prost-types", + "rand 0.9.1", "regex", "rstest", "rust-stemmers", diff --git a/server/Cargo.toml b/server/Cargo.toml index 235806f..b752fef 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -42,3 +42,4 @@ path = "src/lib.rs" tokio = { version = "1.44", features = ["full", "test-util"] } rstest = "0.25.0" lazy_static = "1.5.0" +rand = "0.9.1" diff --git a/server/tests/common/mod.rs b/server/tests/common/mod.rs index b4a5846..57d9643 100644 --- a/server/tests/common/mod.rs +++ b/server/tests/common/mod.rs @@ -1,56 +1,75 @@ // tests/common/mod.rs -use dotenvy; -use sqlx::{postgres::PgPoolOptions, PgPool}; + +use dotenvy::dotenv; +// --- CHANGE 1: Add Alphanumeric to the use statement --- +use rand::distr::Alphanumeric; +use rand::Rng; +use sqlx::{postgres::PgPoolOptions, Connection, Executor, PgConnection, PgPool}; use std::env; -use std::path::Path; -pub async fn setup_test_db() -> PgPool { - // Get path to server directory - let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set"); - let env_path = Path::new(&manifest_dir).join(".env_test"); +// (The get_database_url and get_root_connection functions remain the same) +fn get_database_url() -> String { + dotenv().ok(); + env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set") +} +async fn get_root_connection() -> PgConnection { + PgConnection::connect(&get_database_url()) + .await + .expect("Failed to create root connection to test database") +} - // Load environment variables - dotenvy::from_path(env_path).ok(); - // Create connection pool - let database_url = env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set"); +/// The primary test setup function. +/// Creates a new, unique schema and returns a connection pool that is scoped to that schema. +/// This is the key to test isolation. +pub async fn setup_isolated_db() -> PgPool { + let mut root_conn = get_root_connection().await; + + let schema_name = format!( + "test_{}", + rand::thread_rng() + // --- CHANGE 2: Pass a reference to Alphanumeric directly --- + .sample_iter(&Alphanumeric) + .take(12) + .map(char::from) + .collect::() + .to_lowercase() + ); + + root_conn + .execute(format!("CREATE SCHEMA \"{}\"", schema_name).as_str()) + .await + .unwrap_or_else(|_| panic!("Failed to create schema: {}", schema_name)); + let pool = PgPoolOptions::new() .max_connections(5) - .connect(&database_url) + .after_connect(move |conn, _meta| { + let schema_name = schema_name.clone(); + Box::pin(async move { + conn.execute(format!("SET search_path TO \"{}\"", schema_name).as_str()) + .await?; + Ok(()) + }) + }) + .connect(&get_database_url()) .await - .expect("Failed to create pool"); + .expect("Failed to create isolated pool"); - // Run migrations sqlx::migrate!() .run(&pool) .await - .expect("Migrations failed"); + .expect("Migrations failed in isolated schema"); - // Insert default profile if it doesn't exist - let profile = sqlx::query!( + sqlx::query!( r#" INSERT INTO profiles (name) VALUES ('default') ON CONFLICT (name) DO NOTHING - RETURNING id "# ) - .fetch_optional(&pool) + .execute(&pool) .await - .expect("Failed to insert test profile"); - - let profile_id = if let Some(profile) = profile { - profile.id - } else { - // If the profile already exists, fetch its ID - sqlx::query!( - "SELECT id FROM profiles WHERE name = 'default'" - ) - .fetch_one(&pool) - .await - .expect("Failed to fetch default profile ID") - .id - }; + .expect("Failed to insert test profile in isolated schema"); pool } diff --git a/server/tests/table_definition/post_table_definition_test.rs b/server/tests/table_definition/post_table_definition_test.rs index 46d6550..ecd405c 100644 --- a/server/tests/table_definition/post_table_definition_test.rs +++ b/server/tests/table_definition/post_table_definition_test.rs @@ -1,5 +1,7 @@ // tests/table_definition/post_table_definition_test.rs -use crate::common::setup_test_db; + +use crate::common::setup_isolated_db; + use common::proto::multieko2::table_definition::{ ColumnDefinition, PostTableDefinitionRequest, TableLink, }; @@ -10,9 +12,13 @@ use tonic::Code; // ========= Fixtures ========= +/// THIS IS THE KEY CHANGE. +/// The `pool` fixture, which most of your tests already use, +/// will now provide a completely isolated database schema for every test. #[fixture] async fn pool() -> PgPool { - setup_test_db().await + // Instead of the old setup, we call the new isolated setup. + setup_isolated_db().await } #[fixture] @@ -22,8 +28,8 @@ async fn closed_pool(#[future] pool: PgPool) -> PgPool { pool } -/// A fixture that creates a pre-existing 'customers' table definition, -/// so we can test linking to it. +/// This fixture now works perfectly and is also isolated, +/// because it depends on the `pool` fixture above. No changes needed here! #[fixture] async fn pool_with_preexisting_table(#[future] pool: PgPool) -> PgPool { let pool = pool.await; @@ -43,6 +49,7 @@ async fn pool_with_preexisting_table(#[future] pool: PgPool) -> PgPool { pool } + // ========= Helper Functions ========= /// Checks the PostgreSQL information_schema to verify a table and its columns exist.