---
description:
globs:
alwaysApply: true
---
# Testing Strategy Guide
## Test Organization
### Unit Tests
All unit tests are co-located with source files using `#[cfg(test)]` modules. Current coverage: **141 passing tests** (as of 2025-09).
### Test Structure Pattern
```rust
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_repo() -> (TempDir, PathBuf) {
// Test setup helper
}
#[test]
fn test_functionality() {
// Test implementation
}
}
```
## Cross-Platform Testing Considerations
### Environment Independence
**Critical**: Tests must work across all CI environments (Ubuntu, Windows, macOS).
#### Git Branch Names
❌ **Don't hardcode branch names:**
```rust
// BAD - Fails in CI with different default branches
assert!(branch_info.iter().any(|b| b.name == "master"));
```
✅ **Use environment-agnostic checks:**
```rust
// GOOD - Works with any default branch name
assert!(!branch_info.is_empty());
assert!(branch_info.iter().any(|b| b.is_current));
```
#### Platform-Specific Code
Use conditional compilation for platform-specific functionality:
```rust
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let permissions = metadata.permissions();
assert!(permissions.mode() & 0o111 != 0); // Check executable bit
}
```
### Directory Handling in Tests
**Issue**: Changing directories in tests can cause CI failures, especially with temporary directories.
❌ **Avoid changing current directory when possible:**
```rust
// BAD - Unreliable in CI
env::set_current_dir(&repo_path).unwrap();
run(None, false).await; // Depends on current directory
```
✅ **Use direct path references:**
```rust
// GOOD - Explicit path handling
let result = some_function_with_path(&repo_path);
```
#### When You Must Change Directories
If tests require changing directories (e.g., for commands that use `env::current_dir()`), use **best-effort cleanup**:
```rust
#[tokio::test]
async fn test_with_directory_change() {
let (_temp_dir, repo_path) = create_test_repo().await;
let original_dir = env::current_dir().unwrap();
env::set_current_dir(&repo_path).unwrap();
let result = run().await;
// ✅ Best-effort restoration - don't panic if it fails
let _ = env::set_current_dir(&original_dir);
assert!(result.is_ok());
// _temp_dir dropped here automatically
}
```
**Why best-effort?** On Linux/Ubuntu CI, temporary directory cleanup timing can vary. The temp directory might be deleted before we can `cd` back out, causing `.unwrap()` to panic. Using `let _ =` makes cleanup graceful.
## Test Helper Patterns
### Repository Creation
Standard pattern for creating test Git repositories:
```rust
fn create_test_repo() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().to_path_buf();
// Initialize git repository
Command::new("git")
.args(["init"])
.current_dir(&repo_path)
.output()
.unwrap();
// Configure git user
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(&repo_path)
.output()
.unwrap();
(temp_dir, repo_path)
}
```
### Cascade Initialization
For tests requiring initialized Cascade repositories:
```rust
async fn create_initialized_repo() -> (TempDir, PathBuf) {
let (temp_dir, repo_path) = create_test_repo();
// Initialize cascade
crate::config::initialize_repo(&repo_path, Some("https://test.bitbucket.com".to_string()))
.unwrap();
(temp_dir, repo_path)
}
```
## Critical Test Files
### Git Operations
[src/git/repository.rs](mdc:src/git/repository.rs) - Tests for:
- Repository operations
- Commit handling with proper lifetimes
- Branch management
- Force push workflows
### Stack Management
[src/stack/manager.rs](mdc:src/stack/manager.rs) - Tests for:
- Stack creation and deletion
- Multi-stack scenarios
- Persistence verification
### Hooks System
[src/cli/commands/hooks.rs](mdc:src/cli/commands/hooks.rs) - Tests for:
- Hook installation/uninstallation
- Platform-specific executable permissions
- Hook content generation
## Test Data and Fixtures
### Temporary Files
Always use `tempfile::TempDir` for test isolation:
- Automatic cleanup on test completion
- Unique directories prevent test interference
- Cross-platform path handling
### Test Fixtures
Store reusable test data in [tests/fixtures/](mdc:tests/fixtures):
- Sample repository states
- Configuration files
- Expected output examples
## Integration Testing
### End-to-End Workflows
[tests/integration/](mdc:tests/integration) contains:
- [end_to_end_tests.rs](mdc:tests/integration/end_to_end_tests.rs) - Complete user workflows
- [bitbucket_api_tests.rs](mdc:tests/integration/bitbucket_api_tests.rs) - External API integration
- [squash_and_push_tests.rs](mdc:tests/integration/squash_and_push_tests.rs) - Complex Git operations
### CI Environment Considerations
- Network access may be limited
- External services may be unavailable
- File system permissions may differ
- Default Git configurations vary
## Performance and Reliability
### Test Timeouts
Long-running tests should have reasonable timeouts:
```rust
#[tokio::test(timeout = Duration::from_secs(30))]
async fn test_long_operation() {
// Test implementation
}
```
### Resource Cleanup
Ensure proper cleanup in tests:
- Use RAII patterns with `Drop` traits
- Utilize `tempfile::TempDir` for automatic cleanup
- Avoid global state modifications
## Running Tests
### Local Development
```bash
# Run all tests
cargo test
# Run specific test
cargo test test_name
# Run with output
cargo test -- --nocapture
```
### Pre-Push Validation
The [scripts/pre-push-check.sh](mdc:scripts/pre-push-check.sh) runs:
- Unit tests: `cargo test`
- Integration tests: `cargo test --test '*'`
- Documentation tests: `cargo test --doc`