The `database` library provides core utilities for setting up database state consistently across tests that need it (both within `libs/` and in `server/tests/`).
**Note:** These helpers assume database pools (`pg_pool`, `sqlx_pool`, `redis_pool`) are initialized *before* tests run, typically via a mechanism like `#[ctor]` or a test runner setup.
- **Unit Tests**: Include unit tests inside library source files using `#[cfg(test)]` modules. **Unit tests must mock all external dependencies (DB, HTTP) and never connect to real services.**
- **Integration Tests**: Place integration tests in the lib's `tests/` directory (e.g., `libs/database/tests/`, `libs/handlers/tests/`). These tests *can* interact with external services (like the test database) using the test utilities.
- **Test Utils**: Library-specific helpers can reside in `src/test_utils.rs` (for internal use) or `tests/common/mod.rs` (for integration tests within that library).
### Server (`server/`) Testing Structure
- **Location**: API server tests reside in `server/tests/`.
- **Goal**: These tests focus *only* on the server's responsibilities: routing, middleware application (auth, CORS, logging), request parsing, and response formatting at the framework level. **They should NOT test handler business logic.**
- **Strategy**: Use Axum's in-memory testing capabilities.
- `build_test_app()`: Constructs the Axum `Router`, wiring up routes and middleware.
- **Handler Mocking**: Replace real handlers (from `libs/handlers`) with mock functions that have the *same signature* but return simple `StatusCode` responses (e.g., `StatusCode::OK`, `StatusCode::CREATED`).
- **Mock Authentication**: Implement a test-only authentication extractor (`MockAuth`) that bypasses real token validation.
- **Mock State**: If necessary, provide a minimal `MockAppState`.
- **Testing Tool**: Use `axum_test_helper::TestClient` (or similar) to send requests directly to the in-memory `Router`.
- **Database Interaction**: If a middleware being tested *requires* specific database state (e.g., checking organization membership), use the standard `database::test_utils` helpers (`TestDb`, `insert_...`, `cleanup_test_data`) to set up that state.
- **Location**: In each library's `tests/` directory.
- **Scope**: Test the public API of the library, including its interaction with external services relevant to that library (e.g., database tests interact with the DB).
- **Database**: Use `database::test_utils` (`TestDb`, `insert_...`, `cleanup_test_data`) to manage test data in a real test database.
- **External APIs**: Can use `mockito` if testing library functions that call external services.
- **Isolation**: Tests should ideally not call functions from *other* libraries directly, focus on the current library's integration.
### Server Integration Tests (`server/tests/`)
- **Location**: `server/tests/`.
- **Scope**: Test the assembled Axum application: routing, middleware, request/response cycle.
- **Handlers**: Mocked (as described above) - do *not* test handler logic here.
- **Database**: Only interact with the database via `database::test_utils` if *required* for testing middleware behavior (e.g., setting up a user for auth middleware).
- **Testing Tool**: `axum_test_helper::TestClient` or similar in-memory tester.
### Environment and Configuration
- Use `.env.test` for test-specific environment variables (Database URLs, API keys).
- Load config via `dotenv`. The `database` test setup might rely on environment variables being loaded beforehand.
- Ensure `DATABASE_URL` points to a dedicated test database.
### Database Setup for Integration Tests
- **Initialization**: Assumes connection pools are initialized globally before tests start (e.g., using `#[ctor]` in `libs/database/src/lib.rs` or `tests/mod.rs`).
- **Test Data Management**: ALWAYS use `database::test_utils`:
```rust
#[tokio::test]
async fn test_something_with_db() -> Result<()> {
// Assumes pools are initialized
let test_db = TestDb::new().await?;
let user = test_db.create_test_user().await?;
let dashboard = test_db.create_test_dashboard_file(&user.id).await?;
insert_test_dashboard_file(&dashboard).await?;
let dashboard_id = dashboard.id;
// ... perform test actions ...
// Clean up the specific data created for this test
cleanup_test_data(&[dashboard_id]).await?;
Ok(())
}
```
- **Schema/Models**: Use the standard definitions from `database::schema` and `database::models`.
- **Isolation**: `TestDb` provides context, but actual data isolation relies on careful use of `cleanup_test_data` at the end of each test.
**IMPORTANT**: Integration tests should use the shared connection pools initialized once for the test run. Tests should *never* create their own pools manually.