buster/apps/api/libs/database/tests/CLAUDE.md

9.0 KiB

Database Test Infrastructure Guide

Last Updated: April 7, 2025
Version: 1.1.0

This directory contains the test infrastructure for the database library, providing standardized utilities for database testing, permission testing, and asset management.

Overview

The test infrastructure is located in the common/ directory and consists of:

  1. TestDb: Central utility for database 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

Quick Reference

Key Test Components

Component Purpose Key Methods
TestDb Database connections & test isolation new(), diesel_conn(), cleanup()
TestSetup Pre-configured user, org, and DB new(), with_role()
AssetTestHelpers Create test assets create_test_*() methods
PermissionTestHelpers Manage test permissions create_user_permission(), verify_user_permission()

Common Patterns

// Basic pattern
let test_db = TestDb::new().await?;
// ... test code ...
test_db.cleanup().await?;

// With user setup (preferred)
let setup = TestSetup::new(Some(UserOrganizationRole::Admin)).await?;
// ... test code with setup.user, setup.organization ...
setup.db.cleanup().await?;

// Creating assets with permissions in one step
let metric = AssetTestHelpers::create_test_metric_with_permission(
    &test_db, "Test Metric", user_id, AssetPermissionRole::Owner
).await?;

Using the Test Infrastructure

Basic Usage

Here's a simple example of using the test infrastructure:

#[tokio::test]
async fn test_my_feature() -> Result<()> {
    // Create a test database environment
    let test_db = TestDb::new().await?;
    
    // Create a test metric
    let metric = AssetTestHelpers::create_test_metric(&test_db, "Test Metric").await?;
    
    // Add permission
    PermissionTestHelpers::create_user_permission(
        &test_db,
        metric.id,
        AssetType::MetricFile,
        test_db.user_id,
        AssetPermissionRole::Owner
    ).await?;
    
    // Test your functionality using the created test data
    
    // Clean up (optional - will be done automatically when test_db is dropped)
    test_db.cleanup().await?;
    
    Ok(())
}

TestSetup

For more complex tests requiring an authenticated user and organization:

#[tokio::test]
async fn test_with_authenticated_user() -> Result<()> {
    // Create test setup with admin user
    let setup = TestSetup::new(Some(UserOrganizationRole::WorkspaceAdmin)).await?;
    
    // Use authenticated user in handler
    let result = some_handler(
        &setup.user,
        &setup.organization,
        // ... other params ...
    ).await?;
    
    // Test assertions
    assert!(result.is_ok());
    
    Ok(())
}

Testing with Different User Roles

#[tokio::test]
async fn test_with_different_roles() -> Result<()> {
    // Test with viewer role
    let viewer_setup = TestSetup::new(Some(UserOrganizationRole::Viewer)).await?;
    
    // Test with editor role
    let editor_setup = TestSetup::new(Some(UserOrganizationRole::Querier)).await?;
    
    // Make assertions based on roles
    
    Ok(())
}

Working with Assets and Permissions

Creating Assets

// Create a test metric
let metric = AssetTestHelpers::create_test_metric(&test_db, "Test Metric").await?;

// Create a test dashboard
let dashboard = AssetTestHelpers::create_test_dashboard(&test_db, "Test Dashboard").await?;

// Create a test collection
let collection = AssetTestHelpers::create_test_collection(&test_db, "Test Collection").await?;

// Create a test chat
let chat = AssetTestHelpers::create_test_chat(&test_db, "Test Chat").await?;

Creating Assets with Permissions

// Create a metric with owner permission
let metric = AssetTestHelpers::create_test_metric_with_permission(
    &test_db,
    "Test Metric with Permission",
    user_id,
    AssetPermissionRole::Owner
).await?;

// Create a dashboard with editor permission
let dashboard = AssetTestHelpers::create_test_dashboard_with_permission(
    &test_db,
    "Test Dashboard with Permission",
    user_id,
    AssetPermissionRole::CanEdit
).await?;

Managing Permissions

// Create a permission
let permission = PermissionTestHelpers::create_user_permission(
    &test_db,
    asset_id,
    AssetType::MetricFile,
    user_id,
    AssetPermissionRole::Owner
).await?;

// Verify a permission
PermissionTestHelpers::verify_user_permission(
    &test_db,
    asset_id,
    user_id,
    AssetPermissionRole::Owner
).await?;

// Get all permissions for an asset
let permissions = PermissionTestHelpers::get_asset_permissions(
    &test_db,
    asset_id
).await?;

// Get all permissions for a user
let user_permissions = PermissionTestHelpers::get_user_permissions(
    &test_db,
    user_id
).await?;

Test Fixture Patterns

Creating Complex Test Fixtures

When you need a more complex test setup, you can create a custom function that builds your test environment:

/// Create a test environment with a collection containing multiple metrics
async fn setup_collection_with_metrics(
    test_db: &TestDb, 
    user_id: &Uuid,
    metric_count: usize
) -> Result<(Collection, Vec<MetricFile>)> {
    // Create a collection
    let collection = AssetTestHelpers::create_test_collection_with_permission(
        test_db, 
        "Test Collection", 
        *user_id, 
        AssetPermissionRole::Owner
    ).await?;
    
    // Create multiple metrics and add to collection
    let mut metrics = Vec::new();
    for i in 0..metric_count {
        let metric = AssetTestHelpers::create_test_metric_with_permission(
            test_db,
            &format!("Test Metric {}", i),
            *user_id,
            AssetPermissionRole::Owner
        ).await?;
        
        // Add metric to collection
        CollectionTestHelpers::add_asset_to_collection(
            test_db,
            collection.id,
            metric.id,
            AssetType::MetricFile
        ).await?;
        
        metrics.push(metric);
    }
    
    Ok((collection, metrics))
}

Working with Mock Data

You can create mock data for non-database tests:

/// Create a mock metric for testing
fn create_mock_metric() -> BusterMetric {
    BusterMetric {
        id: Uuid::new_v4(),
        name: "Mock Metric".to_string(),
        description: Some("A metric for testing".to_string()),
        // ... other fields ...
        permission: AssetPermissionRole::Owner,
        // ... fill in required fields ...
    }
}

Troubleshooting

Common Test Issues

  1. Test Data Not Cleaned Up

    • Problem: Test data from a previous run affects current tests
    • Solution: Ensure test_db.cleanup() is called at the end of each test
    • Example Fix: Use Rust's defer! macro or add cleanup in a Drop implementation
  2. Test Database Connection Issues

    • Problem: Tests fail with connection errors
    • Solution: Check that the test database is running and properly configured
    • Example Error: Failed to get connection: connection pool timeout
  3. Missing Test Dependencies

    • Problem: Test fixtures depend on other fixtures that aren't created
    • Solution: Use helper functions to create complete test environments
    • Example Fix: Create combined test setup functions

Debugging Tests

For detailed debugging of test database operations:

# Enable SQL logging during tests
TEST_LOG=debug cargo test -p database my_test_name

# Run a single test with output
cargo test -p database my_test_name -- --nocapture

Best Practices

  1. Use unique test identifiers: The TestDb creates a unique test_id for each test instance. Use this to prefix test asset names for easy identification and cleanup.

  2. Clean up after tests: The TestDb.cleanup() method removes all test data created during the test. Call it explicitly at the end of your test or let it be called automatically when TestDb is dropped.

  3. Isolate tests: Each test should create its own TestDb instance to ensure proper isolation between tests.

  4. Use descriptive names: Provide descriptive names for test assets to make test debugging easier.

  5. Prefer combined helpers: Use methods like create_test_metric_with_permission when you need both an asset and its permission.

  6. Test with all permission levels: Ensure functionality is tested with different permission levels (Owner, CanEdit, CanView, etc.)

  7. Avoid hard-coded IDs: Generate new UUIDs for each test to ensure isolation

  8. Test error cases: Ensure tests cover both success and error cases

Additional Information

For more details on testing patterns and best practices, refer to the documentation/testing.mdc file in the project root.

Changelog

  • 1.1.0 (April 7, 2025): Added quick reference table, test fixture patterns, troubleshooting section
  • 1.0.0 (January 12, 2025): Initial documentation version