368 lines
14 KiB
Rust
368 lines
14 KiB
Rust
//! PostgreSQL Connection Test Program
|
||
//! This program tests the PostgreSQL integration with SeaORM
|
||
|
||
use std::sync::Arc;
|
||
use imphnen_libs::postgres::{PostgresConfig, PostgresConnection, PostgresError};
|
||
use imphnen_entities::seaorm::auth::users::{Entity as UsersEntity, Model as UserModel};
|
||
use imphnen_entities::seaorm::auth::roles::{Entity as RolesEntity, Model as RoleModel};
|
||
use sea_orm::{EntityTrait, ActiveModelTrait, Set, TransactionTrait, DbErr, PaginatorTrait};
|
||
use uuid::Uuid;
|
||
use chrono::Utc;
|
||
|
||
#[tokio::main]
|
||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||
println!("🚀 Starting PostgreSQL Connection Test");
|
||
println!("=====================================");
|
||
|
||
// Load configuration from environment
|
||
let config = PostgresConfig::from_env()?;
|
||
println!("✅ Configuration loaded successfully");
|
||
println!(" Database URL: {}", config.database_url.replace("postgres://", "postgres://****:****@"));
|
||
println!(" Pool size: {}", config.pool_size);
|
||
println!(" Connect timeout: {}s", config.connect_timeout);
|
||
println!(" Retry attempts: {}", config.retry_attempts);
|
||
|
||
// Test connection
|
||
println!("\n🔌 Testing PostgreSQL connection...");
|
||
match test_connection(config).await {
|
||
Ok(()) => {
|
||
println!("✅ All PostgreSQL tests passed successfully!");
|
||
Ok(())
|
||
}
|
||
Err(e) => {
|
||
println!("❌ PostgreSQL test failed: {}", e);
|
||
Err(e.into())
|
||
}
|
||
}
|
||
}
|
||
|
||
async fn test_connection(config: PostgresConfig) -> Result<(), PostgresError> {
|
||
// Create connection
|
||
println!(" Creating PostgreSQL connection...");
|
||
let postgres_conn = PostgresConnection::new(config).await?;
|
||
let connection = Arc::new(postgres_conn);
|
||
println!(" ✅ Connection established successfully");
|
||
|
||
// Test basic connectivity
|
||
println!(" Testing basic connectivity...");
|
||
test_basic_connectivity(&connection).await?;
|
||
println!(" ✅ Basic connectivity test passed");
|
||
|
||
// Test table existence
|
||
println!(" Testing table existence...");
|
||
test_table_existence(&connection).await?;
|
||
println!(" ✅ Table existence test passed");
|
||
|
||
// Test CRUD operations
|
||
println!(" Testing CRUD operations...");
|
||
test_crud_operations(&connection).await?;
|
||
println!(" ✅ CRUD operations test passed");
|
||
|
||
// Test transaction support
|
||
println!(" Testing transaction support...");
|
||
test_transactions(&connection).await?;
|
||
println!(" ✅ Transaction support test passed");
|
||
|
||
// Test error handling
|
||
println!(" Testing error handling...");
|
||
test_error_handling(&connection).await?;
|
||
println!(" ✅ Error handling test passed");
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn test_basic_connectivity(connection: &Arc<PostgresConnection>) -> Result<(), PostgresError> {
|
||
// Execute a simple query
|
||
let statement = sea_orm::Statement::from_string(
|
||
connection.get_database_backend(),
|
||
"SELECT 1 as test_value, current_timestamp as current_time".to_string()
|
||
);
|
||
|
||
let result = connection.query_one(statement).await?
|
||
.ok_or_else(|| PostgresError::ConnectionError(sea_orm::DbErr::Custom("No results returned".to_string())))?;
|
||
|
||
// Verify we got expected results
|
||
let test_value: Option<i32> = result.try_get("", "test_value").ok();
|
||
let current_time: Option<String> = result.try_get("", "current_time").ok();
|
||
|
||
if test_value != Some(1) {
|
||
return Err(PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||
format!("Expected test_value=1, got {:?}", test_value)
|
||
)));
|
||
}
|
||
|
||
if current_time.is_none() {
|
||
return Err(PostgresError::ConnectionError(sea_orm::DbErr::Custom(
|
||
"Expected current_time to be set".to_string()
|
||
)));
|
||
}
|
||
|
||
println!(" 📝 Query result: test_value={:?}, current_time={:?}", test_value, current_time);
|
||
Ok(())
|
||
}
|
||
|
||
async fn test_table_existence(connection: &Arc<PostgresConnection>) -> Result<(), PostgresError> {
|
||
// Test if our tables exist
|
||
use sea_orm::EntityTrait;
|
||
|
||
println!(" 📋 Checking users table...");
|
||
let user_count = UsersEntity::find()
|
||
.count(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?;
|
||
println!(" 📊 Users table accessible, current count: {}", user_count);
|
||
|
||
println!(" 📋 Checking roles table...");
|
||
let role_count = RolesEntity::find()
|
||
.count(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?;
|
||
println!(" 📊 Roles table accessible, current count: {}", role_count);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn test_crud_operations(connection: &Arc<PostgresConnection>) -> Result<(), PostgresError> {
|
||
use sea_orm::{ActiveModelTrait, Set};
|
||
|
||
// Create test user
|
||
println!(" ➕ Creating test user...");
|
||
let test_user_id = Uuid::new_v4();
|
||
let now = Utc::now();
|
||
|
||
let user_model = imphnen_entities::seaorm::auth::users::ActiveModel {
|
||
id: Set(test_user_id),
|
||
email: Set(format!("test_user_{}@example.com", test_user_id)),
|
||
password_hash: Set("test_password_hash".to_string()),
|
||
username: Set(format!("testuser_{}", test_user_id)),
|
||
first_name: Set(Some("Test".to_string())),
|
||
last_name: Set(Some("User".to_string())),
|
||
avatar_url: Set(None),
|
||
is_verified: Set(false),
|
||
is_active: Set(true),
|
||
metadata: Set(None),
|
||
role_id: Set(None),
|
||
created_at: Set(now),
|
||
updated_at: Set(now),
|
||
deleted_at: Set(None),
|
||
};
|
||
|
||
let created_user = user_model.insert(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?;
|
||
|
||
println!(" ✅ Created user with ID: {}", created_user.id);
|
||
|
||
// Read user
|
||
println!(" 🔍 Reading test user...");
|
||
let found_user = UsersEntity::find_by_id(test_user_id)
|
||
.one(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?
|
||
.ok_or_else(|| PostgresError::ConnectionError(sea_orm::DbErr::Custom("User not found after creation".to_string())))?;
|
||
|
||
println!(" ✅ Found user: {} ({})", found_user.username, found_user.email);
|
||
|
||
// Update user
|
||
println!(" ✏️ Updating test user...");
|
||
let mut update_model: imphnen_entities::seaorm::auth::users::ActiveModel = found_user.into();
|
||
update_model.first_name = Set(Some("Updated".to_string()));
|
||
update_model.updated_at = Set(Utc::now());
|
||
|
||
let updated_user = update_model.update(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?;
|
||
|
||
println!(" ✅ Updated user first name to: {:?}", updated_user.first_name);
|
||
|
||
// Delete user
|
||
println!(" 🗑️ Deleting test user...");
|
||
UsersEntity::delete_by_id(updated_user.id)
|
||
.exec(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?;
|
||
|
||
println!(" ✅ Test user deleted successfully");
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn test_transactions(connection: &Arc<PostgresConnection>) -> Result<(), PostgresError> {
|
||
println!(" 💰 Testing transaction support...");
|
||
|
||
// Test transaction with rollback
|
||
let transaction_result = connection.conn.transaction(|txn| {
|
||
Box::pin(async move {
|
||
// Create a test user within transaction
|
||
let test_user_id = Uuid::new_v4();
|
||
let now = Utc::now();
|
||
|
||
let user_model = imphnen_entities::seaorm::auth::users::ActiveModel {
|
||
id: Set(test_user_id),
|
||
email: Set(format!("transaction_test_{}@example.com", test_user_id)),
|
||
password_hash: Set("transaction_password_hash".to_string()),
|
||
username: Set(format!("transaction_user_{}", test_user_id)),
|
||
first_name: Set(Some("Transaction".to_string())),
|
||
last_name: Set(Some("Test".to_string())),
|
||
avatar_url: Set(None),
|
||
is_verified: Set(false),
|
||
is_active: Set(true),
|
||
metadata: Set(None),
|
||
role_id: Set(None),
|
||
created_at: Set(now),
|
||
updated_at: Set(now),
|
||
deleted_at: Set(None),
|
||
};
|
||
|
||
let _created_user = user_model.insert(txn)
|
||
.await?;
|
||
|
||
// Simulate an error to trigger rollback (return a sea_orm::DbErr so the TransactionError matches)
|
||
Err::<(), DbErr>(DbErr::Custom("Simulated transaction failure".to_string()))
|
||
})
|
||
}).await;
|
||
|
||
// Transaction should fail and rollback
|
||
match transaction_result {
|
||
Err(e) => {
|
||
let e_text = format!("{:?}", e);
|
||
if e_text.contains("Simulated transaction failure") {
|
||
println!(" ✅ Transaction failed as expected, rollback successful");
|
||
} else {
|
||
return Err(PostgresError::OperationFailed(format!("Unexpected transaction result: {}", e_text)));
|
||
}
|
||
}
|
||
Ok(_) => {
|
||
return Err(PostgresError::OperationFailed("Unexpected transaction result: transaction unexpectedly succeeded".to_string()));
|
||
}
|
||
}
|
||
|
||
// Verify user was not created (due to rollback)
|
||
let user_exists = UsersEntity::find_by_id(Uuid::nil()) // Use nil UUID as we don't know the actual ID
|
||
.one(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?
|
||
.is_some();
|
||
|
||
if user_exists {
|
||
println!(" ⚠️ User found despite rollback - this might indicate an issue");
|
||
} else {
|
||
println!(" ✅ Transaction rollback verified - user not found");
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
async fn test_error_handling(connection: &Arc<PostgresConnection>) -> Result<(), PostgresError> {
|
||
println!(" ⚠️ Testing error handling...");
|
||
|
||
// Test invalid UUID
|
||
println!(" 🔍 Testing invalid UUID handling...");
|
||
let invalid_uuid = Uuid::nil(); // This should exist or be handled gracefully
|
||
|
||
match UsersEntity::find_by_id(invalid_uuid)
|
||
.one(&connection.conn)
|
||
.await
|
||
.map_err(PostgresError::ConnectionError)?
|
||
{
|
||
Some(_) => println!(" ✅ Found user with nil UUID (expected in some cases)"),
|
||
None => println!(" ✅ No user found with nil UUID (expected)"),
|
||
}
|
||
|
||
// Test invalid query
|
||
println!(" 🔍 Testing invalid query handling...");
|
||
let invalid_statement = sea_orm::Statement::from_string(
|
||
connection.get_database_backend(),
|
||
"SELECT * FROM non_existent_table".to_string()
|
||
);
|
||
|
||
match connection.execute(invalid_statement).await {
|
||
Err(_) => println!(" ✅ Invalid query properly handled with error"),
|
||
Ok(_) => println!(" ⚠️ Invalid query unexpectedly succeeded"),
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Additional utility functions for comprehensive testing
|
||
pub mod test_utils {
|
||
use super::*;
|
||
|
||
/// Create a test PostgreSQL configuration
|
||
pub fn create_test_config() -> PostgresConfig {
|
||
PostgresConfig {
|
||
database_url: "postgres://postgres:postgres@localhost:5432/imphnen_test".to_string(),
|
||
pool_size: 5,
|
||
connect_timeout: 10,
|
||
idle_timeout: 30,
|
||
max_lifetime: Some(600),
|
||
retry_attempts: 2,
|
||
retry_delay: 1,
|
||
}
|
||
}
|
||
|
||
/// Create a test user model
|
||
pub fn create_test_user_model() -> UserModel {
|
||
UserModel {
|
||
id: Uuid::new_v4(),
|
||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||
password_hash: "test_password_hash".to_string(),
|
||
username: format!("testuser_{}", Uuid::new_v4()),
|
||
first_name: Some("Test".to_string()),
|
||
last_name: Some("User".to_string()),
|
||
avatar_url: None,
|
||
is_verified: false,
|
||
is_active: true,
|
||
metadata: None,
|
||
role_id: None,
|
||
created_at: Utc::now(),
|
||
updated_at: Utc::now(),
|
||
deleted_at: None,
|
||
}
|
||
}
|
||
|
||
/// Create a test role model
|
||
pub fn create_test_role_model() -> RoleModel {
|
||
RoleModel {
|
||
id: Uuid::new_v4(),
|
||
name: format!("test_role_{}", Uuid::new_v4()),
|
||
description: "Test role description".to_string(),
|
||
permissions: Some(serde_json::json!(["test.permission"])),
|
||
is_system_role: false,
|
||
is_default: false,
|
||
created_at: Utc::now(),
|
||
updated_at: Utc::now(),
|
||
deleted_at: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_create_test_config() {
|
||
let config = test_utils::create_test_config();
|
||
assert_eq!(config.pool_size, 5);
|
||
assert_eq!(config.connect_timeout, 10);
|
||
assert!(config.database_url.contains("imphnen_test"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_create_test_user_model() {
|
||
let user = test_utils::create_test_user_model();
|
||
assert!(!user.email.is_empty());
|
||
assert!(!user.username.is_empty());
|
||
assert!(user.is_active);
|
||
// is_admin field removed; instead, check role-based permission or is_active
|
||
}
|
||
|
||
#[test]
|
||
fn test_create_test_role_model() {
|
||
let role = test_utils::create_test_role_model();
|
||
assert!(!role.name.is_empty());
|
||
assert!(role.permissions.is_some());
|
||
assert!(!role.is_system_role);
|
||
}
|
||
} |