/* * This file is part of Sharenet. * * Sharenet is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. * * You may obtain a copy of the license at: * https://creativecommons.org/licenses/by-nc-sa/4.0/ * * Copyright (c) 2024 Continuist */ use sqlx::PgPool; use sqlx::postgres::PgPoolOptions; use std::env; use std::sync::Once; use std::sync::Mutex; use std::sync::Arc; use std::sync::OnceLock; static INIT: Once = Once::new(); static MIGRATION_LOCK: Mutex<()> = Mutex::new(()); /// Global test database pool that is initialized once for all tests static TEST_POOL: OnceLock> = OnceLock::new(); /// Initialize the test database with migrations /// This function ensures migrations are run only once across all tests async fn initialize_test_database() -> Arc { let database_url = env::var("DATABASE_URL") .unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/sharenet_test".to_string()); let pool = PgPoolOptions::new() .max_connections(10) // Increased for concurrent tests .connect(&database_url) .await .expect("Failed to connect to test database"); // Run migrations only once let _lock = MIGRATION_LOCK.lock().unwrap(); sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations"); Arc::new(pool) } /// Get a shared database pool for tests /// This ensures all tests use the same database connection and migrations are run only once pub async fn get_test_pool() -> Arc { if let Some(pool) = TEST_POOL.get() { pool.clone() } else { let pool = initialize_test_database().await; let _ = TEST_POOL.set(pool.clone()); pool } } /// Setup a test database connection with migrations already applied /// This is the main function that test modules should use pub async fn setup_test_db() -> PgPool { let _pool_arc = get_test_pool().await; let pool = PgPoolOptions::new() .max_connections(5) .connect(&get_test_database_url()) .await .expect("Failed to connect to test database"); // Clean up any existing test data cleanup_test_data(&pool).await; pool } /// Clean up test data (does not drop tables, just clears data) pub async fn cleanup_test_data(pool: &PgPool) { let mut tx = pool.begin().await.expect("Failed to begin transaction"); // Delete in reverse order of foreign key dependencies sqlx::query("DELETE FROM products").execute(&mut *tx).await.expect("Failed to delete products"); sqlx::query("DELETE FROM users").execute(&mut *tx).await.expect("Failed to delete users"); tx.commit().await.expect("Failed to commit cleanup transaction"); } /// Get the test database URL pub fn get_test_database_url() -> String { env::var("DATABASE_URL") .unwrap_or_else(|_| "postgres://postgres:password@localhost:5432/sharenet_test".to_string()) } /// Generate unique test data to avoid conflicts between concurrent tests pub fn unique_test_data(prefix: &str) -> (String, String) { use uuid::Uuid; let id = Uuid::new_v4().to_string()[..8].to_string(); (format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix)) } /// Initialize the test environment /// This should be called once at the start of the test suite pub async fn initialize_test_environment() { INIT.call_once(|| { // This will be called only once, even if multiple tests call this function println!("Initializing test environment..."); }); // Ensure database is set up let _pool = get_test_pool().await; println!("Test environment initialized"); }