End-to-End Testing¶
This document describes the E2E test suite for router-hosts.
Overview¶
E2E tests validate the full stack with real mTLS authentication. Tests run in Docker containers with actual TLS certificates and network communication.
Running E2E Tests¶
# Run all E2E tests
task e2e
# Run specific scenario
task e2e:scenario -- disaster_recovery
# Quick run (skip rebuild)
task e2e:quick
Prerequisites¶
- Docker running
ROUTER_HOSTS_IMAGE: Docker image (default:ghcr.io/fzymgc-house/router-hosts:latest)ROUTER_HOSTS_BINARY: Path to CLI binary (default:router-hostsin PATH)
Test Scenarios¶
The E2E suite includes 10 tests across 4 scenario files:
| File | Tests | Description |
|---|---|---|
initial_setup.rs |
1 | First-time deployment workflow |
daily_operations.rs |
5 | CRUD, import/export, aliases, search |
auth_failures.rs |
2 | mTLS authentication rejection |
disaster_recovery.rs |
2 | Snapshot and rollback operations |
Architecture¶
┌─────────────────────────────────────────────────┐
│ Test Host │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ E2E Test │ │ Docker Network │ │
│ │ Binary │◀───────▶│ │ │
│ │ (client) │ mTLS │ ┌────────────┐ │ │
│ └──────────────┘ │ │ Server │ │ │
│ │ │ Container │ │ │
│ │ └────────────┘ │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────┘
macOS E2E Testing¶
Issue: task e2e fails on macOS with "Exec format error" because it uses host binary (macOS) in Linux container.
Solution:
# 1. Build Docker image with Linux binary (multi-stage Docker build)
task docker:build
# 2. Run E2E tests
ROUTER_HOSTS_IMAGE=ghcr.io/fzymgc-house/router-hosts:dev \
ROUTER_HOSTS_BINARY=$(pwd)/target/release/router-hosts \
cargo test -p router-hosts-e2e --release
# Or run specific test
ROUTER_HOSTS_IMAGE=ghcr.io/fzymgc-house/router-hosts:dev \
ROUTER_HOSTS_BINARY=$(pwd)/target/release/router-hosts \
cargo test -p router-hosts-e2e --release test_import_export_roundtrip
Why:
- docker:build compiles Linux binary inside Docker (works cross-platform)
- docker:build-ci copies host binary (fast on Linux CI, broken on macOS)
- Tests need ROUTER_HOSTS_IMAGE=...dev to use locally built image
Writing New E2E Tests¶
New E2E tests go in crates/router-hosts-e2e/tests/scenarios/.
Test Structure¶
use predicates::prelude::*;
use router_hosts_e2e::cli::TestCli;
use router_hosts_e2e::container::TestServer;
#[tokio::test]
async fn test_my_scenario() {
// Setup: Start server container
let server = TestServer::start().await;
let cli = TestCli::new(
server.address(),
server.cert_paths.clone(),
server.temp_dir.path(),
);
// Act: Execute client commands
cli.add_host("192.168.1.1", "test.local")
.build()
.assert()
.success()
.stdout(predicate::str::contains("192.168.1.1"));
// Cleanup
server.stop().await;
}
Best Practices¶
- Isolation: Each test gets its own container and database
- Determinism: Don't rely on system time or random values
- Cleanup: Use RAII (TestContext drops clean up resources)
- Timeouts: Set reasonable timeouts for network operations
- Logging: Use
RUST_LOG=debugto debug failures
Certificate Generation¶
E2E tests generate self-signed certificates at runtime using TestCertificates::generate().
Certificates are stored in a temporary directory managed by the test harness and cleaned up automatically.
Debugging Failed Tests¶
# Run with verbose output
RUST_LOG=debug task e2e
# Attach to running container
docker exec -it router-hosts-test-server /bin/sh
# View container logs
docker logs router-hosts-test-server
CI Integration¶
E2E tests run in GitHub Actions on:
- Every pull request
- Every push to main
The CI uses Linux runners where docker:build-ci works correctly.