Skip to content

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-hosts in 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

  1. Isolation: Each test gets its own container and database
  2. Determinism: Don't rely on system time or random values
  3. Cleanup: Use RAII (TestContext drops clean up resources)
  4. Timeouts: Set reasonable timeouts for network operations
  5. Logging: Use RUST_LOG=debug to 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.