Created test_setup.rs file that consolidates test setup code for interface tests, including db migrations
Some checks are pending
CI/CD Pipeline / Test Backend (push) Waiting to run
CI/CD Pipeline / Test Frontend (push) Waiting to run
CI/CD Pipeline / Build and Push Docker Images (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions

This commit is contained in:
continuist 2025-06-28 01:57:12 -04:00
parent 95a6a6faa0
commit 61117b6fa6
9 changed files with 138 additions and 192 deletions

View file

@ -17,9 +17,6 @@ use axum::{
use domain::{User, Product, CreateUser, UpdateUser, CreateProduct, UpdateProduct};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use application::Service;
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::sync::Arc;
use tower::ServiceExt;
use tower_http::trace::TraceLayer;
@ -30,6 +27,9 @@ use axum::response::IntoResponse;
use serial_test::serial;
use application::UseCase;
// Import the centralized test setup
use crate::test_setup::{setup_test_db, unique_test_data};
/// Application state containing user and product services.
pub struct AppState<U, P> {
user_service: Arc<U>,
@ -49,34 +49,8 @@ where
}
}
// Helper functions
pub async fn setup_test_db() -> PgPool {
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
pub async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
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");
}
pub fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string()[..8].to_string();
(format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix))
}
// Helper functions - now using centralized setup
// These functions are now imported from test_setup module above
pub async fn create_test_app() -> Router {
let pool = setup_test_db().await;

View file

@ -15,41 +15,14 @@ use cli::Cli;
use application::Service;
use memory::{InMemoryUserRepository, InMemoryProductRepository};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use uuid::Uuid;
use serial_test::serial;
use application::UseCase;
// Helper functions for test setup
async fn setup_test_db() -> PgPool {
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
// Import the centralized test setup
use crate::test_setup::{setup_test_db, unique_test_data};
async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
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");
}
fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string()[..8].to_string();
(format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix))
}
// Helper functions are now imported from test_setup module above
// Test CLI with memory repository
#[tokio::test]

View file

@ -14,46 +14,13 @@ use domain::{CreateProduct, CreateUser, Product, UpdateProduct, UpdateUser, User
use memory::{InMemoryUserRepository, InMemoryProductRepository};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use uuid::Uuid;
use serial_test::serial;
// Helper functions for test setup
async fn setup_test_db() -> PgPool {
// Explicitly set DATABASE_URL to use sharenet_test database
std::env::set_var("DATABASE_URL", "postgres://postgres:password@localhost:5432/sharenet_test");
// Import the centralized test setup
use crate::test_setup::{setup_test_db, cleanup_test_data, unique_test_data};
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
sqlx::query("TRUNCATE TABLE products, users RESTART IDENTITY CASCADE").execute(&mut *tx).await.expect("Failed to truncate tables");
tx.commit().await.expect("Failed to commit cleanup transaction");
// Assert tables are empty
let users_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(pool).await.expect("Failed to count users");
let products_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM products").fetch_one(pool).await.expect("Failed to count products");
assert_eq!(users_count.0, 0, "Users table not empty after cleanup");
assert_eq!(products_count.0, 0, "Products table not empty after cleanup");
}
fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string();
(format!("{}_{}", prefix, id), format!("{}_{}@example.com", prefix, id))
}
// Helper functions are now imported from test_setup module above
// Test data structures for comparison
#[derive(Debug, PartialEq, Clone)]

View file

@ -13,47 +13,13 @@ use application::{Service, UseCase};
use domain::{CreateProduct, CreateUser, Product, UpdateProduct, UpdateUser, User};
use memory::{InMemoryUserRepository, InMemoryProductRepository};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use uuid::Uuid;
use serial_test::serial;
// Helper functions for test setup
async fn setup_test_db() -> PgPool {
// Explicitly set DATABASE_URL to use sharenet_test database
std::env::set_var("DATABASE_URL", "postgres://postgres:password@localhost:5432/sharenet_test");
// Import the centralized test setup
use crate::test_setup::{setup_test_db, unique_test_data};
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
sqlx::query("TRUNCATE TABLE products, users RESTART IDENTITY CASCADE").execute(&mut *tx).await.expect("Failed to truncate tables");
tx.commit().await.expect("Failed to commit cleanup transaction");
// Assert tables are empty
let users_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users").fetch_one(pool).await.expect("Failed to count users");
let products_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM products").fetch_one(pool).await.expect("Failed to count products");
assert_eq!(users_count.0, 0, "Users table not empty after cleanup");
assert_eq!(products_count.0, 0, "Products table not empty after cleanup");
}
fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string();
(format!("{}_{}", prefix, id), format!("{}_{}@example.com", prefix, id))
}
// Helper functions are now imported from test_setup module above
// Test data structures for comparison
#[derive(Debug, PartialEq, Clone)]

View file

@ -9,6 +9,8 @@
* Copyright (c) 2024 Continuist <continuist02@gmail.com>
*/
pub mod test_setup;
#[cfg(test)]
pub mod api_postgres_tests;
#[cfg(test)]

View file

@ -10,38 +10,15 @@
*/
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use sqlx::Row;
use std::env;
use serial_test::serial;
/// Setup test database connection for migration tests
async fn setup_migration_test_db() -> PgPool {
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
// Clean up any existing data
cleanup_migration_test_data(&pool).await;
pool
}
/// Clean up test data for migration tests
async fn cleanup_migration_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
// Drop tables if they exist (for migration tests)
sqlx::query("DROP TABLE IF EXISTS products CASCADE").execute(&mut *tx).await.expect("Failed to drop products table");
sqlx::query("DROP TABLE IF EXISTS users CASCADE").execute(&mut *tx).await.expect("Failed to drop users table");
sqlx::query("DROP TABLE IF EXISTS _sqlx_migrations CASCADE").execute(&mut *tx).await.expect("Failed to drop migrations table");
tx.commit().await.expect("Failed to commit cleanup transaction");
// Use the centralized test setup that ensures migrations are run only once
crate::test_setup::setup_test_db().await
}
#[tokio::test]

View file

@ -20,8 +20,11 @@ use serde_json::json;
use serial_test::serial;
use tower::ServiceExt;
// Import the centralized test setup
use crate::test_setup::unique_test_data;
// Reuse AppState and helper functions from api_postgres_tests
use crate::api_postgres_tests::{create_test_app, unique_test_data};
use crate::api_postgres_tests::create_test_app;
/// Performance metrics structure
#[derive(Debug)]

View file

@ -0,0 +1,111 @@
/*
* 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 <continuist02@gmail.com>
*/
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<Arc<PgPool>> = 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<PgPool> {
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<PgPool> {
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");
}

View file

@ -14,9 +14,6 @@ use application::Service;
use domain::{CreateProduct, CreateUser, Product, User, UpdateProduct, UpdateUser};
use memory::{InMemoryUserRepository, InMemoryProductRepository};
use postgres::{PostgresUserRepository, PostgresProductRepository};
use sqlx::PgPool;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::sync::Arc;
use std::collections::HashMap;
use tokio::sync::RwLock;
@ -25,34 +22,10 @@ use uuid::Uuid;
use serial_test::serial;
use application::UseCase;
// Helper functions for test setup
async fn setup_test_db() -> PgPool {
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(5)
.connect(&database_url)
.await
.expect("Failed to connect to test database");
sqlx::migrate!("../../migrations")
.run(&pool)
.await
.expect("Failed to run migrations");
cleanup_test_data(&pool).await;
pool
}
// Import the centralized test setup
use crate::test_setup::{setup_test_db, unique_test_data};
async fn cleanup_test_data(pool: &PgPool) {
let mut tx = pool.begin().await.expect("Failed to begin transaction");
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");
}
fn unique_test_data(prefix: &str) -> (String, String) {
let id = Uuid::new_v4().to_string()[..8].to_string();
(format!("{}_{}", prefix, id), format!("{}_test@example.com", prefix))
}
// Helper functions are now imported from test_setup module above
// Mock services for testing TUI without real repositories
#[derive(Clone)]