Add migration tests to integration-tests crate
This commit is contained in:
parent
243f9f9aef
commit
34a1bd6119
2 changed files with 246 additions and 1 deletions
|
@ -10,4 +10,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod api_postgres_tests;
|
pub mod api_postgres_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod migration_tests;
|
243
backend/crates/integration-tests/src/migration_tests.rs
Normal file
243
backend/crates/integration-tests/src/migration_tests.rs
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* 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 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::<bool, _>(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::<bool, _>(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::<bool, _>(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<String> = users_columns.iter()
|
||||||
|
.map(|row| row.get::<String, _>(0))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let product_column_names: Vec<String> = products_columns.iter()
|
||||||
|
.map(|row| row.get::<String, _>(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::<i64, _>(0);
|
||||||
|
|
||||||
|
let products_count = sqlx::query("SELECT COUNT(*) FROM products")
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to count products")
|
||||||
|
.get::<i64, _>(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::<uuid::Uuid, _>(0), user_id, "User ID should match");
|
||||||
|
assert_eq!(product.get::<uuid::Uuid, _>(0), product_id, "Product ID should match");
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue