use sea_orm::{Database, ConnectionTrait, Statement, DatabaseBackend}; use erp_server_migration::MigratorTrait; /// 测试数据库 — 使用本地 PostgreSQL 创建隔离测试库 /// /// 连接本地 PostgreSQL(wiki/infrastructure.md 配置),为每个测试创建独立的测试数据库。 /// 不依赖 Docker/Testcontainers,与开发环境一致。 pub struct TestDb { db: Option, db_name: String, } impl TestDb { pub async fn new() -> Self { let db_name = format!("erp_test_{}", uuid::Uuid::now_v7().simple()); // 连接本地 PostgreSQL 的默认库(postgres)来创建测试库 let admin_url = "postgres://postgres:123123@localhost:5432/postgres"; let admin_db = Database::connect(admin_url) .await .expect("连接本地 PostgreSQL 失败,请确认服务正在运行"); admin_db .execute(Statement::from_string( DatabaseBackend::Postgres, format!("CREATE DATABASE \"{}\"", db_name), )) .await .expect("创建测试数据库失败"); drop(admin_db); // 连接测试库 let test_url = format!("postgres://postgres:123123@localhost:5432/{}", db_name); let db = Database::connect(&test_url) .await .expect("连接测试数据库失败"); // 运行所有迁移 erp_server_migration::Migrator::up(&db, None) .await .expect("执行数据库迁移失败"); Self { db: Some(db), db_name } } /// 获取数据库连接引用 pub fn db(&self) -> &sea_orm::DatabaseConnection { self.db.as_ref().expect("数据库连接已被释放") } } impl Drop for TestDb { fn drop(&mut self) { let db_name = self.db_name.clone(); self.db.take(); // 尝试在独立线程中清理,避免在 tokio runtime 内创建新 runtime let _ = std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build(); if let Ok(rt) = rt { rt.block_on(async { let admin_url = "postgres://postgres:123123@localhost:5432/postgres"; if let Ok(admin_db) = Database::connect(admin_url).await { let disconnect_sql = format!( "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{}'", db_name ); admin_db .execute(Statement::from_string(DatabaseBackend::Postgres, disconnect_sql)) .await .ok(); admin_db .execute(Statement::from_string( DatabaseBackend::Postgres, format!("DROP DATABASE IF EXISTS \"{}\"", db_name), )) .await .ok(); } }); } }); } }