mirror of https://github.com/buster-so/buster.git
delete testkit
This commit is contained in:
parent
2abf522c78
commit
7e982c3653
|
@ -9,7 +9,6 @@ members = [
|
|||
"libs/sharing",
|
||||
"libs/sql_analyzer",
|
||||
"libs/search",
|
||||
"testkit",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
# Testkit Library Usage Guide
|
||||
|
||||
## Overview
|
||||
The testkit library initializes database pools during build time and provides utilities for test isolation. The pools themselves are accessed directly from the database library.
|
||||
|
||||
## Key Features
|
||||
- Pre-initialized database connections at build time
|
||||
- Environment variable management for test-specific configurations
|
||||
- Test ID generation for test isolation
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
```rust
|
||||
use database::pool::get_pg_pool;
|
||||
use testkit::test_id;
|
||||
use anyhow::Result;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_database_operations() -> Result<()> {
|
||||
// Create a unique test ID for isolation
|
||||
let test_id = test_id();
|
||||
|
||||
// Pools are already initialized during build time
|
||||
// Get the pool from the database library
|
||||
let pool = get_pg_pool();
|
||||
|
||||
// Use the pool in your test
|
||||
let conn = pool.get().await?;
|
||||
|
||||
// Perform database operations...
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
The testkit automatically loads environment variables from `.env.test` if available, otherwise from `.env` during the build process. Key environment variables include:
|
||||
|
||||
- `TEST_DATABASE_URL` - PostgreSQL connection string for tests
|
||||
- `TEST_POOLER_URL` - Connection string for the SQL pooler
|
||||
- `TEST_REDIS_URL` - Redis connection string
|
||||
- `TEST_DATABASE_POOL_SIZE` - Maximum connections in Diesel pool (default: 10)
|
||||
- `TEST_SQLX_POOL_SIZE` - Maximum connections in SQLx pool (default: 10)
|
||||
|
||||
### Accessing Database Pools
|
||||
Access the pre-initialized database pools directly through the database library:
|
||||
|
||||
```rust
|
||||
// Get the Diesel PostgreSQL pool (AsyncPgConnection)
|
||||
let pg_pool = database::pool::get_pg_pool();
|
||||
|
||||
// Get the SQLx PostgreSQL pool
|
||||
let sqlx_pool = database::pool::get_sqlx_pool();
|
||||
|
||||
// Get the Redis pool
|
||||
let redis_pool = database::pool::get_redis_pool();
|
||||
```
|
||||
|
||||
### Test ID Generation
|
||||
For test isolation, you can generate unique IDs to tag test data:
|
||||
|
||||
```rust
|
||||
let test_id = testkit::test_id();
|
||||
|
||||
// Use this ID to mark test data for cleanup
|
||||
diesel::insert_into(users::table)
|
||||
.values((
|
||||
users::name.eq("Test User"),
|
||||
users::email.eq("test@example.com"),
|
||||
users::test_id.eq(test_id), // Use the test ID for later cleanup
|
||||
))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Pool Initialization
|
||||
The testkit initializes pools during the build process using `build.rs`. This ensures pools are always available when your tests run, with no initialization overhead or risk of connection timing issues.
|
||||
|
||||
### Error Handling
|
||||
- Build-time initialization failures are reported as warnings but don't fail the build
|
||||
- Unit tests that don't need database access will run fine even if pool initialization failed
|
||||
- Integration tests that need database access will fail fast if the database isn't available
|
||||
|
||||
### Database Reset (Optional Feature)
|
||||
For clearing test data, use the `db_reset` feature:
|
||||
|
||||
```rust
|
||||
// Only available when the `db_reset` feature is enabled
|
||||
#[cfg(feature = "db_reset")]
|
||||
testkit::reset_test_database().await?;
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Direct Pool Access**: Always use `get_pg_pool()`, `get_sqlx_pool()`, or `get_redis_pool()` directly
|
||||
2. **Use Test IDs**: Generate and use test IDs for proper test isolation
|
||||
3. **Cleanup After Tests**: Always clean up test data after tests
|
||||
4. **Connection Pooling**: Reuse connections from the pool instead of creating new ones
|
||||
5. **Environment Variables**: Use the `.env.test` file for test-specific configuration
|
||||
6. **Avoid Database in Unit Tests**: Unit tests should mock database operations
|
||||
|
||||
## Example Integration Test Pattern
|
||||
|
||||
```rust
|
||||
#[tokio::test]
|
||||
async fn test_example() -> Result<()> {
|
||||
// Generate unique test ID
|
||||
let test_id = testkit::test_id();
|
||||
|
||||
// Get pre-initialized database connection directly from database lib
|
||||
let pool = database::pool::get_pg_pool();
|
||||
let mut conn = pool.get().await?;
|
||||
|
||||
// Setup test data with test_id for tracking
|
||||
let test_user = create_test_user(&mut conn, &test_id).await?;
|
||||
|
||||
// Run the test against the test data
|
||||
let result = your_function_under_test(test_user.id).await?;
|
||||
assert_eq!(result.name, test_user.name);
|
||||
|
||||
// Clean up test data using test_id
|
||||
cleanup_test_data(&mut conn, &test_id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_test_user(conn: &mut PgConnection, test_id: &str) -> Result<User> {
|
||||
// Insert user with test_id
|
||||
// ...
|
||||
}
|
||||
|
||||
async fn cleanup_test_data(conn: &mut PgConnection, test_id: &str) -> Result<()> {
|
||||
// Delete all data with matching test_id
|
||||
// ...
|
||||
}
|
||||
```
|
|
@ -1,29 +0,0 @@
|
|||
[package]
|
||||
name = "testkit"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Test utilities for Buster API database pools"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
diesel = { workspace = true }
|
||||
diesel-async = { workspace = true }
|
||||
sqlx = { workspace = true }
|
||||
bb8-redis = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
dotenv = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
database = { path = "../libs/database" }
|
||||
|
||||
[build-dependencies]
|
||||
dotenv = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
database = { path = "../libs/database" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
db_reset = [] # Use with caution - allows resetting the test database
|
|
@ -1,104 +0,0 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Create default .env.test if it doesn't exist
|
||||
ensure_test_env_exists();
|
||||
|
||||
// Load environment variables from .env
|
||||
load_env_file();
|
||||
|
||||
// Try to initialize pools but don't fail the build if it fails
|
||||
if let Err(e) = try_init_pools() {
|
||||
println!("cargo:warning=Failed to initialize pools: {}", e);
|
||||
println!("cargo:warning=This is not a build error - pools will be initialized when tests run");
|
||||
} else {
|
||||
println!("cargo:warning=Successfully initialized database pools");
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_test_env_exists() {
|
||||
let test_env_path = Path::new(".env.test");
|
||||
|
||||
// Only create if it doesn't exist
|
||||
if !test_env_path.exists() {
|
||||
println!("cargo:warning=Creating default .env.test file");
|
||||
|
||||
let default_content = r#"
|
||||
# Test Environment Configuration
|
||||
TEST_ENV=test
|
||||
TEST_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
||||
TEST_POOLER_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
||||
TEST_REDIS_URL=redis://localhost:6379
|
||||
TEST_DATABASE_POOL_SIZE=10
|
||||
TEST_SQLX_POOL_SIZE=10
|
||||
TEST_LOG=true
|
||||
TEST_LOG_LEVEL=debug
|
||||
"#.trim();
|
||||
|
||||
fs::write(test_env_path, default_content)
|
||||
.expect("Failed to create default .env.test file");
|
||||
|
||||
println!("cargo:warning=Created default .env.test file");
|
||||
}
|
||||
}
|
||||
|
||||
fn load_env_file() {
|
||||
// Try loading .env.test first, then fall back to .env
|
||||
if Path::new(".env.test").exists() {
|
||||
if let Ok(_) = dotenv::from_filename(".env.test") {
|
||||
println!("cargo:warning=Loaded environment from .env.test");
|
||||
}
|
||||
} else if let Ok(_) = dotenv::dotenv() {
|
||||
println!("cargo:warning=Loaded environment from .env");
|
||||
}
|
||||
|
||||
// Override DATABASE_URL to use TEST_DATABASE_URL for tests
|
||||
if let Ok(test_db_url) = env::var("TEST_DATABASE_URL") {
|
||||
env::set_var("DATABASE_URL", test_db_url);
|
||||
println!("cargo:warning=Using TEST_DATABASE_URL for DATABASE_URL");
|
||||
}
|
||||
|
||||
// Override POOLER_URL to use TEST_POOLER_URL for tests
|
||||
if let Ok(test_pooler_url) = env::var("TEST_POOLER_URL") {
|
||||
env::set_var("POOLER_URL", test_pooler_url);
|
||||
println!("cargo:warning=Using TEST_POOLER_URL for POOLER_URL");
|
||||
}
|
||||
|
||||
// Override REDIS_URL to use TEST_REDIS_URL for tests
|
||||
if let Ok(test_redis_url) = env::var("TEST_REDIS_URL") {
|
||||
env::set_var("REDIS_URL", test_redis_url);
|
||||
println!("cargo:warning=Using TEST_REDIS_URL for REDIS_URL");
|
||||
}
|
||||
|
||||
// Override pool sizes to prevent excessive connections in test environment
|
||||
if let Ok(test_pool_size) = env::var("TEST_DATABASE_POOL_SIZE") {
|
||||
env::set_var("DATABASE_POOL_SIZE", test_pool_size);
|
||||
}
|
||||
|
||||
if let Ok(test_sqlx_pool_size) = env::var("TEST_SQLX_POOL_SIZE") {
|
||||
env::set_var("SQLX_POOL_SIZE", test_sqlx_pool_size);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_init_pools() -> Result<(), String> {
|
||||
// Create a runtime for async operations
|
||||
let runtime = match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build() {
|
||||
Ok(rt) => rt,
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
|
||||
// Try to initialize pools through the testkit's internal function
|
||||
// This won't fail the build if it can't connect to the database
|
||||
runtime.block_on(async {
|
||||
match database::pool::init_pools().await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use dotenv::dotenv;
|
||||
use std::path::Path;
|
||||
use std::sync::Once;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
static ENV_INIT: Once = Once::new();
|
||||
static POOL_INIT: OnceCell<()> = OnceCell::new();
|
||||
|
||||
/// Initialize the test environment by setting up .env.test
|
||||
/// This should only be used internally or by build.rs
|
||||
fn init_test_env() {
|
||||
ENV_INIT.call_once(|| {
|
||||
// Try loading .env.test first, then fall back to .env
|
||||
if Path::new(".env.test").exists() {
|
||||
dotenv::from_filename(".env.test").ok();
|
||||
} else {
|
||||
dotenv().ok();
|
||||
}
|
||||
|
||||
// Override DATABASE_URL to use TEST_DATABASE_URL for tests
|
||||
if let Ok(test_db_url) = std::env::var("TEST_DATABASE_URL") {
|
||||
std::env::set_var("DATABASE_URL", test_db_url);
|
||||
}
|
||||
|
||||
// Override POOLER_URL to use TEST_POOLER_URL for tests
|
||||
if let Ok(test_pooler_url) = std::env::var("TEST_POOLER_URL") {
|
||||
std::env::set_var("POOLER_URL", test_pooler_url);
|
||||
}
|
||||
|
||||
// Override REDIS_URL to use TEST_REDIS_URL for tests
|
||||
if let Ok(test_redis_url) = std::env::var("TEST_REDIS_URL") {
|
||||
std::env::set_var("REDIS_URL", test_redis_url);
|
||||
}
|
||||
|
||||
// Override pool sizes to prevent excessive connections in test environment
|
||||
if let Ok(test_pool_size) = std::env::var("TEST_DATABASE_POOL_SIZE") {
|
||||
std::env::set_var("DATABASE_POOL_SIZE", test_pool_size);
|
||||
}
|
||||
|
||||
if let Ok(test_sqlx_pool_size) = std::env::var("TEST_SQLX_POOL_SIZE") {
|
||||
std::env::set_var("SQLX_POOL_SIZE", test_sqlx_pool_size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This function is only for internal use by build.rs - the pools should be
|
||||
// initialized at build time, not during test runtime
|
||||
#[doc(hidden)]
|
||||
pub async fn _internal_init_pools() -> Result<()> {
|
||||
// Setup the environment first
|
||||
init_test_env();
|
||||
|
||||
// Only initialize pools once
|
||||
if POOL_INIT.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Use the database crate's init_pools function
|
||||
let result = match database::pool::init_pools().await {
|
||||
Ok(_) => {
|
||||
// Success case - cache the result
|
||||
let _ = POOL_INIT.set(());
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
// Log the error but still cache the attempt to prevent repeated tries
|
||||
tracing::error!("Failed to initialize test pools: {}", e);
|
||||
let _ = POOL_INIT.set(());
|
||||
Err(e)
|
||||
}
|
||||
};
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// No need for pool accessor functions - users should access pools directly
|
||||
// through the database library (database::pool::get_pg_pool(), etc.)
|
||||
|
||||
/// Generate a unique test ID - useful for creating test resources
|
||||
pub fn test_id() -> String {
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
/// Clean up a test database connection - useful in test teardown
|
||||
pub async fn cleanup_connection() -> Result<()> {
|
||||
// This is a placeholder for any future cleanup that might be needed
|
||||
// Currently the pools handle their own cleanup
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue