Testing
articwake has comprehensive unit and integration tests. This guide covers running tests locally.
Running Tests
Section titled “Running Tests”All Tests
Section titled “All Tests”cargo test --verboseThis runs:
- Unit tests in each module
- Integration tests in
tests/
Specific Test
Section titled “Specific Test”# Run tests matching a patterncargo test validate_mac
# Run tests in a specific modulecargo test auth::testsTest Output
Section titled “Test Output”Show stdout for passing tests:
cargo test -- --nocaptureTest Coverage
Section titled “Test Coverage”Unit Tests
Section titled “Unit Tests”Located within each source file using #[cfg(test)] modules:
| Module | Tests |
|---|---|
config.rs | MAC validation (8 tests) |
auth.rs | Token generation, rate limiting, PIN verification (12 tests) |
api/unlock.rs | Passphrase validation (8 tests) |
services/wol.rs | MAC parsing, packet creation (13 tests) |
services/network.rs | Host status (5 tests) |
Integration Tests
Section titled “Integration Tests”Located in tests/:
| File | Purpose |
|---|---|
api_integration.rs | End-to-end API endpoint tests |
Linting
Section titled “Linting”Clippy
Section titled “Clippy”cargo clippy -- -D warningsThe CI pipeline treats all warnings as errors.
Common Clippy Fixes
Section titled “Common Clippy Fixes”# Auto-fix what clippy cancargo clippy --fixFormatting
Section titled “Formatting”Check Format
Section titled “Check Format”cargo fmt -- --checkAuto-Format
Section titled “Auto-Format”cargo fmtCI Pipeline
Section titled “CI Pipeline”The GitHub Actions workflow runs on every push and PR:
jobs: test: steps: - cargo test --verbose - cargo clippy -- -D warnings - cargo fmt -- --checkAll checks must pass before merging.
Writing Tests
Section titled “Writing Tests”Unit Test Example
Section titled “Unit Test Example”#[cfg(test)]mod tests { use super::*;
#[test] fn test_validate_mac_colon_separated() { assert!(validate_mac("aa:bb:cc:dd:ee:ff").is_ok()); }
#[test] fn test_validate_mac_invalid() { assert!(validate_mac("invalid").is_err()); }}Async Test Example
Section titled “Async Test Example”#[cfg(test)]mod tests { use super::*;
#[actix_rt::test] async fn test_async_function() { let result = async_operation().await; assert!(result.is_ok()); }}Test with Temp Files
Section titled “Test with Temp Files”#[test]fn test_with_temp_file() { use tempfile::NamedTempFile; use std::io::Write;
let mut file = NamedTempFile::new().unwrap(); writeln!(file, "test content").unwrap();
// Use file.path() in your test}Testing Tips
Section titled “Testing Tips”Mock Configuration
Section titled “Mock Configuration”Tests often need a mock Config:
fn create_test_config() -> Config { Config { bind_host: "127.0.0.1".to_string(), port: 8080, homelab_mac: "aa:bb:cc:dd:ee:ff".to_string(), homelab_ip: "192.168.1.100".to_string(), homelab_broadcast: "255.255.255.255".to_string(), ssh_port: 2222, ssh_key_path: PathBuf::from("/tmp/test-key"), pin_hash_path: PathBuf::from("/tmp/test-hash"), }}Testing Rate Limiting
Section titled “Testing Rate Limiting”Rate limit tests use the actual rate limiter:
#[test]fn test_rate_limit_blocks_over_limit() { let state = AppState::new(config); let ip = "192.168.1.1".parse().unwrap();
// Exhaust the limit for _ in 0..10 { assert!(state.check_rate_limit(ip).is_ok()); }
// 11th attempt should fail assert!(state.check_rate_limit(ip).is_err());}Troubleshooting
Section titled “Troubleshooting”Tests hang
Section titled “Tests hang”Some tests may timeout waiting for network:
- Ensure you’re not testing against a real server
- Check for hardcoded IPs that might be routable
Flaky tests
Section titled “Flaky tests”Rate limit tests can be timing-sensitive:
- Tests run in parallel by default
- Use unique IPs for each test
- Consider using
cargo test -- --test-threads=1