/* * 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::Row; use serial_test::serial; /// Setup test database connection for migration tests async fn setup_migration_test_db() -> PgPool { // Use the centralized test setup that ensures migrations are run only once crate::test_setup::setup_test_db().await } #[tokio::test] #[serial] async fn test_migrations_can_be_applied() { let pool = setup_migration_test_db().await; // Run migrations sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations"); // Verify tables exist let users_table_exists = sqlx::query( "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'users')" ) .fetch_one(&pool) .await .expect("Failed to check users table") .get::(0); let products_table_exists = sqlx::query( "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'products')" ) .fetch_one(&pool) .await .expect("Failed to check products table") .get::(0); let migrations_table_exists = sqlx::query( "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '_sqlx_migrations')" ) .fetch_one(&pool) .await .expect("Failed to check migrations table") .get::(0); assert!(users_table_exists, "Users table should exist after migrations"); assert!(products_table_exists, "Products table should exist after migrations"); assert!(migrations_table_exists, "Migrations table should exist after migrations"); // Verify table structure let users_columns = sqlx::query( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'users' ORDER BY ordinal_position" ) .fetch_all(&pool) .await .expect("Failed to get users table columns"); let products_columns = sqlx::query( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'products' ORDER BY ordinal_position" ) .fetch_all(&pool) .await .expect("Failed to get products table columns"); // Check that expected columns exist let user_column_names: Vec = users_columns.iter() .map(|row| row.get::(0)) .collect(); let product_column_names: Vec = products_columns.iter() .map(|row| row.get::(0)) .collect(); assert!(user_column_names.contains(&"id".to_string()), "Users table should have id column"); assert!(user_column_names.contains(&"username".to_string()), "Users table should have username column"); assert!(user_column_names.contains(&"email".to_string()), "Users table should have email column"); assert!(user_column_names.contains(&"created_at".to_string()), "Users table should have created_at column"); assert!(user_column_names.contains(&"updated_at".to_string()), "Users table should have updated_at column"); assert!(product_column_names.contains(&"id".to_string()), "Products table should have id column"); assert!(product_column_names.contains(&"name".to_string()), "Products table should have name column"); assert!(product_column_names.contains(&"description".to_string()), "Products table should have description column"); assert!(product_column_names.contains(&"created_at".to_string()), "Products table should have created_at column"); assert!(product_column_names.contains(&"updated_at".to_string()), "Products table should have updated_at column"); } #[tokio::test] #[serial] async fn test_migrations_are_idempotent() { let pool = setup_migration_test_db().await; // Run migrations first time sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations first time"); // Run migrations second time (should be idempotent) sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations second time"); // Verify tables still exist and are functional let users_count = sqlx::query("SELECT COUNT(*) FROM users") .fetch_one(&pool) .await .expect("Failed to count users") .get::(0); let products_count = sqlx::query("SELECT COUNT(*) FROM products") .fetch_one(&pool) .await .expect("Failed to count products") .get::(0); assert_eq!(users_count, 0, "Users table should be empty after migrations"); assert_eq!(products_count, 0, "Products table should be empty after migrations"); } #[tokio::test] #[serial] async fn test_migration_constraints() { let pool = setup_migration_test_db().await; // Run migrations sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations"); // Test unique constraint on username sqlx::query("INSERT INTO users (id, username, email, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(uuid::Uuid::new_v4()) .bind("testuser") .bind("test@example.com") .bind(chrono::Utc::now()) .bind(chrono::Utc::now()) .execute(&pool) .await .expect("Failed to insert first user"); // Try to insert another user with the same username (should fail) let result = sqlx::query("INSERT INTO users (id, username, email, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(uuid::Uuid::new_v4()) .bind("testuser") .bind("test2@example.com") .bind(chrono::Utc::now()) .bind(chrono::Utc::now()) .execute(&pool) .await; assert!(result.is_err(), "Should not be able to insert user with duplicate username"); } #[tokio::test] #[serial] async fn test_migration_data_types() { let pool = setup_migration_test_db().await; // Run migrations sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations"); // Test that UUID columns accept valid UUIDs let user_id = uuid::Uuid::new_v4(); let product_id = uuid::Uuid::new_v4(); sqlx::query("INSERT INTO users (id, username, email, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(user_id) .bind("testuser") .bind("test@example.com") .bind(chrono::Utc::now()) .bind(chrono::Utc::now()) .execute(&pool) .await .expect("Failed to insert user with UUID"); sqlx::query("INSERT INTO products (id, name, description, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)") .bind(product_id) .bind("Test Product") .bind("Test Description") .bind(chrono::Utc::now()) .bind(chrono::Utc::now()) .execute(&pool) .await .expect("Failed to insert product with UUID"); // Verify the data was inserted correctly let user = sqlx::query("SELECT id FROM users WHERE id = $1") .bind(user_id) .fetch_one(&pool) .await .expect("Failed to fetch user"); let product = sqlx::query("SELECT id FROM products WHERE id = $1") .bind(product_id) .fetch_one(&pool) .await .expect("Failed to fetch product"); assert_eq!(user.get::(0), user_id, "User ID should match"); assert_eq!(product.get::(0), product_id, "Product ID should match"); }