Vapora/docs/adrs/0023-testing-strategy.md
Jesús Pérez 7110ffeea2
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
chore: extend doc: adr, tutorials, operations, etc
2026-01-12 03:32:47 +00:00

284 lines
6.2 KiB
Markdown

# ADR-023: Multi-Layer Testing Strategy
**Status**: Accepted | Implemented
**Date**: 2024-11-01
**Deciders**: Quality Assurance Team
**Technical Story**: Building confidence through unit, integration, and real-database tests
---
## Decision
Implementar **multi-layer testing**: unit tests (inline), integration tests (tests/ dir), real DB connections.
---
## Rationale
1. **Unit Tests**: Fast feedback on logic changes
2. **Integration Tests**: Verify components work together
3. **Real DB Tests**: Catch database schema/query issues
4. **218+ Tests**: Comprehensive coverage across 13 crates
---
## Alternatives Considered
### ❌ Unit Tests Only
- **Pros**: Fast
- **Cons**: Miss integration bugs, schema issues
### ❌ Integration Tests Only
- **Pros**: Comprehensive
- **Cons**: Slow, harder to debug
### ✅ Multi-Layer (CHOSEN)
- All three layers catch different issues
---
## Trade-offs
**Pros**:
- ✅ Fast feedback (unit)
- ✅ Integration validation (integration)
- ✅ Real-world confidence (real DB)
- ✅ 218+ tests total coverage
**Cons**:
- ⚠️ Slow full test suite (~5 minutes)
- ⚠️ DB tests require test environment
- ⚠️ More test code to maintain
---
## Implementation
**Unit Tests (Inline)**:
```rust
// crates/vapora-agents/src/learning_profile.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expertise_score_empty() {
let profile = TaskTypeLearning {
agent_id: "test".to_string(),
task_type: "architecture".to_string(),
executions_total: 0,
records: vec![],
..Default::default()
};
assert_eq!(profile.expertise_score(), 0.0);
}
#[test]
fn test_confidence_weighting() {
let profile = TaskTypeLearning {
executions_total: 20,
..Default::default()
};
assert_eq!(profile.confidence(), 1.0);
let profile_partial = TaskTypeLearning {
executions_total: 10,
..Default::default()
};
assert_eq!(profile_partial.confidence(), 0.5);
}
}
```
**Integration Tests**:
```rust
// crates/vapora-backend/tests/integration_tests.rs
#[tokio::test]
async fn test_create_project_full_flow() {
// Setup: create test database
let db = setup_test_db().await;
let app_state = create_test_app_state(db.clone()).await;
// Execute: create project via HTTP
let response = app_state
.handle_request(
"POST",
"/api/projects",
json!({
"title": "Test Project",
"description": "A test",
}),
)
.await;
// Verify: response is 201 Created
assert_eq!(response.status(), 201);
// Verify: project in database
let project = db
.query("SELECT * FROM projects LIMIT 1")
.await
.unwrap()
.take::<Project>(0)
.unwrap()
.unwrap();
assert_eq!(project.title, "Test Project");
}
```
**Real Database Tests**:
```rust
// crates/vapora-backend/tests/database_tests.rs
#[tokio::test]
async fn test_multi_tenant_isolation() {
let db = setup_real_surrealdb().await;
// Create projects for two tenants
let project_1 = db
.create("projects")
.content(Project {
tenant_id: "tenant:1".to_string(),
title: "Project 1".to_string(),
..Default::default()
})
.await
.unwrap();
let project_2 = db
.create("projects")
.content(Project {
tenant_id: "tenant:2".to_string(),
title: "Project 2".to_string(),
..Default::default()
})
.await
.unwrap();
// Query: tenant 1 should only see their project
let results = db
.query("SELECT * FROM projects WHERE tenant_id = 'tenant:1'")
.await
.unwrap()
.take::<Vec<Project>>(0)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].title, "Project 1");
}
```
**Test Utilities**:
```rust
// crates/vapora-backend/tests/common/mod.rs
pub async fn setup_test_db() -> Surreal<Mem> {
let db = Surreal::new::<surrealdb::engine::local::Mem>()
.await
.unwrap();
db.use_ns("vapora").use_db("test").await.unwrap();
// Initialize schema
init_schema(&db).await.unwrap();
db
}
pub async fn setup_real_surrealdb() -> Surreal<Ws> {
// Connect to test SurrealDB instance
let db = Surreal::new::<Ws>("ws://localhost:8000")
.await
.unwrap();
db.signin(/* test credentials */).await.unwrap();
db.use_ns("test").use_db("test").await.unwrap();
db
}
```
**Running Tests**:
```bash
# Run all tests
cargo test --workspace
# Run unit tests only (fast)
cargo test --workspace --lib
# Run integration tests
cargo test --workspace --test "*"
# Run with output
cargo test --workspace -- --nocapture
# Run specific test
cargo test -p vapora-backend test_multi_tenant_isolation
# Coverage report
cargo tarpaulin --workspace --out Html
```
**Key Files**:
- `crates/*/src/` (unit tests inline)
- `crates/*/tests/` (integration tests)
- `crates/*/tests/common/` (test utilities)
---
## Verification
```bash
# Count tests across workspace
cargo test --workspace -- --list | grep "test " | wc -l
# Run all tests with statistics
cargo test --workspace 2>&1 | grep -E "^test |passed|failed"
# Coverage report
cargo tarpaulin --workspace --out Html
# Output: coverage/index.html
```
**Expected Output**:
- 218+ tests total
- All tests passing
- Coverage > 70%
- Unit tests < 5 seconds
- Integration tests < 60 seconds
---
## Consequences
### Testing Cadence
- Pre-commit: run unit tests
- PR: run all tests
- CI/CD: run all tests + coverage
### Test Environment
- Unit tests: in-memory databases
- Integration: SurrealDB in-memory
- Real DB: Docker container (CI/CD only)
### Debugging
- Unit test failure: easy to debug (isolated)
- Integration failure: check component interaction
- DB failure: verify schema and queries
---
## References
- [Rust Testing Documentation](https://doc.rust-lang.org/book/ch11-00-testing.html)
- `crates/*/tests/` (integration tests)
- `crates/vapora-backend/tests/common/` (test utilities)
---
**Related ADRs**: ADR-022 (Error Handling), ADR-004 (SurrealDB)