testing and claude updates

This commit is contained in:
dal 2025-04-07 16:50:16 -06:00
parent 994975672b
commit dc701e9ea5
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 153 additions and 204 deletions

View File

@ -22,31 +22,25 @@ Always prefer dependency injection patterns to enable easy mocking of dependenci
### Test Utilities and Setup
The project provides a comprehensive test infrastructure that offers standard utilities for database tests, permission testing, and asset management. These utilities are located in the `libs/database/tests/common` directory.
#### Core Test Types
#### Core Test Infrastructure Components
1. **TestDb**: Central database test utility for connections and cleanup
2. **PermissionTestHelpers**: Utilities for testing permissions
3. **AssetTestHelpers**: Utilities for creating and managing test assets
4. **AuthenticatedUser**: Test user representation for auth scenarios
#### TestDb
The `TestDb` struct is the foundation of the test infrastructure, providing database connections, test isolation via unique IDs, and cleanup functionality:
The test infrastructure provides several core test utilities for consistent test setup:
```rust
// Example of TestDb and TestSetup implementation
pub struct TestDb {
pub test_id: String, // Unique identifier for test isolation
pub organization_id: Uuid, // Test organization ID
pub user_id: Uuid, // Test user ID
initialized: bool, // Tracks initialization state
pub test_id: String,
pub organization_id: Uuid,
pub user_id: Uuid,
initialized: bool,
}
impl TestDb {
/// Creates a new test database environment
pub async fn new() -> Result<Self> {
// Uses the pools already initialized in the test framework
// Load environment variables
dotenv().ok();
// Initialize test pools using existing pools
let test_id = format!("test-{}", Uuid::new_v4());
let organization_id = Uuid::new_v4();
let user_id = Uuid::new_v4();
@ -59,199 +53,133 @@ impl TestDb {
})
}
/// Gets a database connection for testing
pub async fn diesel_conn(&self) -> Result<PooledConnection<'_, AsyncPgConnection>> {
// Get connections from pre-initialized pools
pub async fn diesel_conn(&self) -> Result<AsyncPgConnection> {
get_pg_pool()
.get()
.await
.map_err(|e| anyhow!("Failed to get diesel connection: {}", e))
}
/// Creates a mock authenticated user for testing
pub fn user(&self) -> AuthenticatedUser {
AuthenticatedUser {
id: self.user_id,
organization_id: self.organization_id,
// ... other fields ...
}
pub async fn sqlx_conn(&self) -> Result<PgConnection> {
get_sqlx_pool()
.acquire()
.await
.map_err(|e| anyhow!("Failed to get sqlx connection: {}", e))
}
/// Creates test data including users and organizations
pub async fn redis_conn(&self) -> Result<RedisConnection> {
get_redis_pool()
.get()
.await
.map_err(|e| antml!("Failed to get redis connection: {}", e))
}
// Create test organization
pub async fn create_organization(&self) -> Result<Organization> {
// Implementation for creating a test organization
let org = Organization {
id: Uuid::new_v4(),
name: "Test Organization".to_string(),
domain: Some("test.org".to_string()),
created_at: Utc::now(),
updated_at: Utc::now(),
deleted_at: None,
};
let mut conn = self.diesel_conn().await?;
diesel::insert_into(organizations::table)
.values(&org)
.execute(&mut conn)
.await?;
Ok(org)
}
// Create test user
pub async fn create_user(&self) -> Result<User> {
// Implementation for creating a test user
let user = User {
id: Uuid::new_v4(),
email: format!("test-{}@example.com", Uuid::new_v4()),
name: Some("Test User".to_string()),
config: json!({}),
created_at: Utc::now(),
updated_at: Utc::now(),
attributes: json!({}),
avatar_url: None,
};
let mut conn = self.diesel_conn().await?;
diesel::insert_into(users::table)
.values(&user)
.execute(&mut conn)
.await?;
Ok(user)
}
/// Creates a user-organization relationship
// Create user-organization relationship
pub async fn create_user_to_org(
&self,
user_id: Uuid,
org_id: Uuid,
role: UserOrganizationRole,
) -> Result<UserToOrganization> {
// Implementation for creating user-org relationship
let user_org = UserToOrganization {
user_id,
organization_id: org_id,
role,
sharing_setting: SharingSetting::Private,
edit_sql: true,
upload_csv: true,
export_assets: true,
email_slack_enabled: true,
created_at: Utc::now(),
updated_at: Utc::now(),
deleted_at: None,
created_by: user_id,
updated_by: user_id,
deleted_by: None,
status: UserOrganizationStatus::Active,
};
let mut conn = self.diesel_conn().await?;
diesel::insert_into(users_to_organizations::table)
.values(&user_org)
.execute(&mut conn)
.await?;
Ok(user_org)
}
/// Creates an authenticated user with organization
// Create authenticated user for testing
pub async fn create_authenticated_user(
&self,
role: Option<UserOrganizationRole>,
) -> Result<(AuthenticatedUser, Organization)> {
// Implementation for creating authenticated test user
}
let org = self.create_organization().await?;
let user = self.create_user().await?;
/// Cleans up all test data
pub async fn cleanup(&self) -> Result<()> {
// Removes all data created with this test instance
let role = role.unwrap_or(UserOrganizationRole::Admin);
self.create_user_to_org(user.id, org.id, role).await?;
let auth_user = AuthenticatedUser {
id: user.id,
email: user.email,
name: user.name,
organization_id: org.id,
role,
sharing_setting: SharingSetting::Private,
edit_sql: true,
upload_csv: true,
export_assets: true,
email_slack_enabled: true,
};
Ok((auth_user, org))
}
}
```
#### AuthenticatedUser
The `AuthenticatedUser` struct provides a simplified version of the application's authenticated user for testing:
```rust
#[derive(Debug, Clone)]
pub struct AuthenticatedUser {
pub id: Uuid,
pub organization_id: Uuid,
pub email: String,
pub name: Option<String>,
pub role: UserOrganizationRole,
// ... other fields ...
}
impl AuthenticatedUser {
/// Creates a new random authenticated user with given role
pub fn mock_with_role(role: UserOrganizationRole) -> Self {
// Implementation
}
/// Creates a new random administrator user
pub fn mock_admin() -> Self {
// Implementation
}
// Other helper methods for different roles
}
```
#### PermissionTestHelpers
The `PermissionTestHelpers` struct provides utilities for testing permissions:
```rust
pub struct PermissionTestHelpers;
impl PermissionTestHelpers {
/// Creates a permission for an asset
pub async fn create_permission(
test_db: &TestDb,
asset_id: Uuid,
asset_type: AssetType,
identity_id: Uuid,
identity_type: IdentityType,
role: AssetPermissionRole,
) -> Result<AssetPermission> {
// Implementation
}
/// Creates a user permission for an asset
pub async fn create_user_permission(
test_db: &TestDb,
asset_id: Uuid,
asset_type: AssetType,
user_id: Uuid,
role: AssetPermissionRole,
) -> Result<AssetPermission> {
// Implementation
}
/// Verifies that a permission exists with expected role
pub async fn verify_permission(
test_db: &TestDb,
asset_id: Uuid,
identity_id: Uuid,
expected_role: AssetPermissionRole,
) -> Result<()> {
// Implementation
}
/// Gets all permissions for an asset
pub async fn get_asset_permissions(
test_db: &TestDb,
asset_id: Uuid,
) -> Result<Vec<AssetPermission>> {
// Implementation
}
// Other permission helper methods
}
```
#### AssetTestHelpers
The `AssetTestHelpers` struct provides utilities for creating and managing test assets:
```rust
pub struct AssetTestHelpers;
impl AssetTestHelpers {
/// Creates a test metric file
pub async fn create_test_metric(
test_db: &TestDb,
name: &str,
) -> Result<MetricFile> {
// Implementation
}
/// Creates a test dashboard file
pub async fn create_test_dashboard(
test_db: &TestDb,
name: &str,
) -> Result<DashboardFile> {
// Implementation
}
/// Creates a test collection
pub async fn create_test_collection(
test_db: &TestDb,
name: &str,
) -> Result<Collection> {
// Implementation
}
/// Creates a test chat
pub async fn create_test_chat(
test_db: &TestDb,
title: &str,
) -> Result<Chat> {
// Implementation
}
/// Creates a test asset with permission in one step
pub async fn create_test_metric_with_permission(
test_db: &TestDb,
name: &str,
user_id: Uuid,
role: AssetPermissionRole,
) -> Result<MetricFile> {
// Implementation
}
// Similar methods for other asset types with permissions
}
```
#### TestSetup
The `TestSetup` struct combines the core utilities for easy test setup:
```rust
// Helper struct for test setup
pub struct TestSetup {
pub user: AuthenticatedUser,
pub organization: Organization,
@ -259,7 +187,6 @@ pub struct TestSetup {
}
impl TestSetup {
/// Creates a new test setup with authenticated user
pub async fn new(role: Option<UserOrganizationRole>) -> Result<Self> {
let test_db = TestDb::new().await?;
let (user, org) = test_db.create_authenticated_user(role).await?;

View File

@ -122,31 +122,53 @@ async fn example_handler(req: PostChatRequest, user: AuthenticatedUser) -> Resul
- Run tests with: `cargo test -p handlers`
- Create tests for each handler in the corresponding `tests/` directory
### Automatic Test Environment Setup
### Automatic Test Environment Setup and Best Practices
Integration tests in this library use an automatic database pool initialization system. The environment is set up once when the test module is loaded, eliminating the need for explicit initialization in each test.
Integration tests in this library benefit from an automatic database pool initialization system configured in `tests/mod.rs`. This setup uses `lazy_static` and `ctor` to initialize database pools (Postgres, Redis) once when the test module loads, meaning individual tests **do not** need to perform pool initialization.
**Important Notes:**
- This is a test-only feature that is excluded from release builds
- The test dependencies (`lazy_static` and `ctor`) are listed under `[dev-dependencies]` in Cargo.toml
- The entire `/tests` directory is only compiled during test runs (`cargo test`)
**Best Practice:** While you *can* directly use `get_pg_pool()` etc., it is **strongly recommended** to use the `TestSetup` or `TestDb` utilities from the `database` library's test commons (`database::tests::common::db`) for structuring your tests:
Key components:
- `tests/mod.rs` contains the initialization code using `lazy_static` and `ctor`
- Database pools are initialized only once for all tests
- Tests can directly use `get_pg_pool()` without any setup code
1. **Consistent Setup:** `TestSetup` provides a standard starting point with a pre-created test user, organization, and a `TestDb` instance. Use `TestDb` directly if you need more control over the initial setup.
2. **Helper Methods:** `TestDb` offers convenient methods for obtaining connections (`diesel_conn`, `sqlx_conn`, `redis_conn`) and creating common test entities (users, orgs, relationships) linked to the test instance.
3. **Crucial Cleanup:** `TestDb` includes a vital `cleanup()` method that removes data created during the test, ensuring test isolation. **This cleanup method MUST be called at the end of every test.**
**Example Test Structure:**
Example test:
```rust
use anyhow::Result;
use database::pool::get_pg_pool;
// Adjust import path based on actual visibility/re-exports
use database::tests::common::db::{TestSetup, TestDb};
use database::enums::UserOrganizationRole;
#[tokio::test]
async fn test_handler_functionality() -> Result<()> {
// Database pool is already initialized
let pool = get_pg_pool();
async fn test_handler_with_setup() -> Result<()> {
// 1. Initialize test environment using TestSetup
// This gives a user, org, and db instance.
let setup = TestSetup::new(Some(UserOrganizationRole::Member)).await?;
// Test code here using the pool
// 2. Get connections via the db instance
let mut conn = setup.db.diesel_conn().await?;
// 3. Use setup data (setup.user, setup.organization) and helpers (setup.db.create_...)
// Perform test logic...
// assert!(...)
// 4. !!! Crucially, cleanup test data !!!
setup.db.cleanup().await?;
Ok(())
}
#[tokio::test]
async fn test_handler_with_direct_db() -> Result<()> {
// Alternative: Use TestDb directly if no initial user/org needed
let db = TestDb::new().await?;
let mut conn = db.diesel_conn().await?;
// Perform test logic...
// !!! Crucially, cleanup test data !!!
db.cleanup().await?;
Ok(())
}
```
@ -154,4 +176,4 @@ async fn test_handler_functionality() -> Result<()> {
To add new test modules, simply:
1. Create a new module in the `tests/` directory
2. Add it to the module declarations in `tests/mod.rs`
3. Write standard async tests using `#[tokio::test]`
3. Write standard async tests using `#[tokio::test]` following the `TestSetup`/`TestDb` pattern above.