mirror of https://github.com/buster-so/buster.git
332 lines
9.4 KiB
Plaintext
332 lines
9.4 KiB
Plaintext
---
|
|
description: This is designed to help understand how to do testing in this project.
|
|
globs:
|
|
alwaysApply: false
|
|
---
|
|
# Testing Rules and Best Practices
|
|
|
|
## General Testing Guidelines
|
|
- All tests must be async and use tokio test framework
|
|
- Tests should be well-documented with clear test case descriptions and expected outputs
|
|
- Each test should focus on testing a single piece of functionality
|
|
- Tests should be independent and not rely on the state of other tests
|
|
- Use meaningful test names that describe what is being tested
|
|
|
|
## Unit Tests
|
|
- Unit tests should be inline with the code they are testing using `#[cfg(test)]` modules
|
|
- Each public function should have corresponding unit tests
|
|
- Mock external dependencies using mockito for HTTP calls
|
|
- Use `mockito::Server::new_async()` instead of `mockito::Server::new()`
|
|
- Test both success and error cases
|
|
- Test edge cases and boundary conditions
|
|
|
|
## Integration Tests
|
|
- Integration tests should be placed in the `/tests` directory
|
|
- Organize integration tests to mirror the main codebase structure
|
|
- Each major feature/resource should have its own test file
|
|
- Test the interaction between multiple components
|
|
- Use real dependencies when possible, mock only what's necessary
|
|
- Include end-to-end workflow tests
|
|
|
|
### Integration Test Setup Requirements
|
|
- All integration tests must import and utilize the application's schema from [schema.rs](mdc:src/database/schema.rs)
|
|
- Database models from [models.rs](mdc:src/database/models.rs) should be used for test data setup and verification
|
|
- Environment setup must use `dotenv` for configuration:
|
|
```rust
|
|
use dotenv::dotenv;
|
|
|
|
#[tokio::test]
|
|
async fn setup_test_environment() {
|
|
dotenv().ok(); // Load environment variables
|
|
// Test environment setup
|
|
}
|
|
```
|
|
- Service configurations should be derived from environment variables:
|
|
```rust
|
|
// Example of service configuration using env vars
|
|
let database_url = std::env::var("DATABASE_URL")
|
|
.expect("DATABASE_URL must be set for integration tests");
|
|
let test_api_key = std::env::var("TEST_API_KEY")
|
|
.expect("TEST_API_KEY must be set for integration tests");
|
|
```
|
|
- Test database setup should include:
|
|
```rust
|
|
use crate::database::{schema, models};
|
|
|
|
async fn setup_test_db() -> PgPool {
|
|
let pool = PgPoolOptions::new()
|
|
.max_connections(5)
|
|
.connect(&std::env::var("TEST_DATABASE_URL")?)
|
|
.await?;
|
|
|
|
// Run migrations or setup test data
|
|
// Use schema and models for consistency
|
|
Ok(pool)
|
|
}
|
|
```
|
|
|
|
### Required Environment Variables
|
|
Create a `.env.test` file with necessary test configurations:
|
|
```env
|
|
TEST_DATABASE_URL=postgres://user:pass@localhost/test_db
|
|
TEST_API_KEY=test-key
|
|
TEST_ENV=test
|
|
# Add other required test environment variables
|
|
```
|
|
|
|
## Test Structure
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use mockito;
|
|
use tokio;
|
|
|
|
// Optional: Setup function for common test initialization
|
|
async fn setup() -> TestContext {
|
|
// Setup code here
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_name() {
|
|
// Test case description in comments
|
|
// Expected output in comments
|
|
|
|
// Arrange
|
|
// Setup test data and dependencies
|
|
|
|
// Act
|
|
// Execute the function being tested
|
|
|
|
// Assert
|
|
// Verify the results
|
|
}
|
|
}
|
|
```
|
|
|
|
## Mocking Guidelines
|
|
- Use mockito for HTTP service mocks
|
|
- Create mock responses that match real API responses
|
|
- Include both successful and error responses in mocks
|
|
- Clean up mocks after tests complete
|
|
|
|
## Error Testing
|
|
- Test error conditions and error handling
|
|
- Verify error messages and error types
|
|
- Test timeout scenarios
|
|
- Test connection failures
|
|
- Test invalid input handling
|
|
|
|
## Database Testing
|
|
- Use a separate test database for integration tests
|
|
- Clean up test data after tests complete
|
|
- Test database connection error handling
|
|
|
|
## Test Output
|
|
- Tests should provide clear error messages
|
|
- Use descriptive assert messages
|
|
- Print relevant debug information in test failures
|
|
- Log test execution progress for long-running tests
|
|
|
|
## CI/CD Considerations
|
|
- All tests must pass in CI environment
|
|
- Tests should be reproducible
|
|
- Tests should not have external dependencies that could fail CI
|
|
- Test execution time should be reasonable
|
|
|
|
## Example Test
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use mockito;
|
|
use tokio;
|
|
|
|
#[tokio::test]
|
|
async fn test_api_call_success() {
|
|
// Test case: Successful API call returns expected response
|
|
// Expected: Response contains user data with status 200
|
|
|
|
let mut server = mockito::Server::new_async().await;
|
|
|
|
let mock = server
|
|
.mock("GET", "/api/user")
|
|
.match_header("authorization", "Bearer test-token")
|
|
.with_status(200)
|
|
.with_body(r#"{"id": "123", "name": "Test User"}"#)
|
|
.create();
|
|
|
|
let client = ApiClient::new(server.url());
|
|
let response = client.get_user().await.unwrap();
|
|
|
|
assert_eq!(response.id, "123");
|
|
assert_eq!(response.name, "Test User");
|
|
|
|
mock.assert();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Example Integration Test
|
|
```rust
|
|
use crate::database::{models, schema};
|
|
use dotenv::dotenv;
|
|
|
|
#[tokio::test]
|
|
async fn test_user_creation_flow() {
|
|
// Load test environment
|
|
dotenv().ok();
|
|
|
|
// Setup test database connection
|
|
let pool = setup_test_db().await.expect("Failed to setup test database");
|
|
|
|
// Create test user using models
|
|
let test_user = models::User {
|
|
id: Uuid::new_v4(),
|
|
email: "test@example.com".to_string(),
|
|
name: Some("Test User".to_string()),
|
|
config: serde_json::Value::Null,
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
attributes: serde_json::Value::Null,
|
|
};
|
|
|
|
// Use schema for database operations
|
|
diesel::insert_into(schema::users::table)
|
|
.values(&test_user)
|
|
.execute(&mut pool.get().await?)
|
|
.expect("Failed to insert test user");
|
|
|
|
// Test application logic
|
|
let response = create_test_client()
|
|
.get("/api/users")
|
|
.send()
|
|
.await?;
|
|
|
|
assert_eq!(response.status(), 200);
|
|
// Additional assertions...
|
|
}
|
|
```
|
|
|
|
## Common Test Utilities
|
|
- All shared test utilities should be placed in `tests/common/mod.rs`
|
|
- Common database setup and teardown functions should be in `tests/common/db.rs`
|
|
- Environment setup utilities should be in `tests/common/env.rs`
|
|
- Shared test fixtures should be in `tests/common/fixtures/`
|
|
|
|
### Common Test Module Structure
|
|
```
|
|
tests/
|
|
├── common/
|
|
│ ├── mod.rs # Main module file that re-exports all common utilities
|
|
│ ├── db.rs # Database setup/teardown utilities
|
|
│ ├── env.rs # Environment configuration utilities
|
|
│ ├── fixtures/ # Test data fixtures
|
|
│ │ ├── mod.rs # Exports all fixtures
|
|
│ │ ├── users.rs # User-related test data
|
|
│ │ └── threads.rs # Thread-related test data
|
|
│ └── helpers.rs # General test helper functions
|
|
└── integration/ # Integration test files
|
|
```
|
|
|
|
### Common Database Setup
|
|
```rust
|
|
// tests/common/db.rs
|
|
use diesel::PgConnection;
|
|
use diesel::r2d2::{ConnectionManager, Pool};
|
|
use crate::database::{models, schema};
|
|
use dotenv::dotenv;
|
|
|
|
pub struct TestDb {
|
|
pub pool: Pool<ConnectionManager<PgConnection>>,
|
|
}
|
|
|
|
impl TestDb {
|
|
pub async fn new() -> anyhow::Result<Self> {
|
|
dotenv().ok();
|
|
|
|
let database_url = std::env::var("TEST_DATABASE_URL")
|
|
.expect("TEST_DATABASE_URL must be set");
|
|
|
|
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
|
let pool = Pool::builder()
|
|
.max_size(5)
|
|
.build(manager)?;
|
|
|
|
Ok(Self { pool })
|
|
}
|
|
|
|
pub async fn setup_test_data(&self) -> anyhow::Result<()> {
|
|
// Add common test data setup here
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn cleanup(&self) -> anyhow::Result<()> {
|
|
// Cleanup test data
|
|
Ok(())
|
|
}
|
|
}
|
|
```
|
|
|
|
### Common Environment Setup
|
|
```rust
|
|
// tests/common/env.rs
|
|
use std::sync::Once;
|
|
use dotenv::dotenv;
|
|
|
|
static ENV_SETUP: Once = Once::new();
|
|
|
|
pub fn setup_test_env() {
|
|
ENV_SETUP.call_once(|| {
|
|
dotenv().ok();
|
|
// Set any default environment variables for tests
|
|
std::env::set_var("TEST_ENV", "test");
|
|
});
|
|
}
|
|
```
|
|
|
|
### Example Test Fixtures
|
|
```rust
|
|
// tests/common/fixtures/users.rs
|
|
use crate::database::models::User;
|
|
use chrono::Utc;
|
|
use uuid::Uuid;
|
|
|
|
pub fn create_test_user() -> User {
|
|
User {
|
|
id: Uuid::new_v4(),
|
|
email: "test@example.com".to_string(),
|
|
name: Some("Test User".to_string()),
|
|
config: serde_json::Value::Null,
|
|
created_at: Utc::now(),
|
|
updated_at: Utc::now(),
|
|
attributes: serde_json::Value::Null,
|
|
}
|
|
}
|
|
```
|
|
|
|
### Using Common Test Utilities
|
|
```rust
|
|
// Example integration test using common utilities
|
|
use crate::tests::common::{db::TestDb, env::setup_test_env, fixtures};
|
|
|
|
#[tokio::test]
|
|
async fn test_user_creation() {
|
|
// Setup test environment
|
|
setup_test_env();
|
|
|
|
// Initialize test database
|
|
let test_db = TestDb::new().await.expect("Failed to setup test database");
|
|
|
|
// Get test user fixture
|
|
let test_user = fixtures::users::create_test_user();
|
|
|
|
// Run test
|
|
let result = create_user(&test_db.pool, &test_user).await?;
|
|
|
|
// Cleanup
|
|
test_db.cleanup().await?;
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
``` |