220 lines
No EOL
7.6 KiB
Rust
220 lines
No EOL
7.6 KiB
Rust
/*
|
|
* 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::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::<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");
|
|
}
|