network-protocol 1.2.1

Secure, high-performance protocol core with backpressure control, structured logging, timeout handling, TLS support, and comprehensive benchmarking for robust Rust networked applications and services.
Documentation
//! Chaos engineering tests
#![allow(clippy::panic)] // Test code uses panics for assertions
#![allow(clippy::vec_init_then_push)] // Test code clarity over optimization
#![allow(clippy::useless_vec)] // Test code prefers explicit vec! for readability
#![allow(clippy::manual_range_contains)] // Test code prefers explicit comparisons
//!
//! Tests network protocol behavior under adverse conditions including
//! packet loss, delays, and network partitions.

#![allow(clippy::expect_used, clippy::unwrap_used)]

use network_protocol::config::PROTOCOL_VERSION;
use network_protocol::core::packet::Packet;
use std::time::Duration;
use tokio::time::{sleep, timeout};

/// Simulates network delay
async fn simulate_delay(duration: Duration) {
    sleep(duration).await;
}

/// Simulates packet loss (returns true if packet should be dropped)
fn simulate_packet_loss(loss_rate: f32) -> bool {
    use rand::Rng;
    let mut rng = rand::rng();
    rng.random::<f32>() < loss_rate
}

#[tokio::test]
async fn test_packet_with_delay() {
    let packet = Packet {
        version: PROTOCOL_VERSION,
        payload: vec![1, 2, 3, 4],
    };

    // Simulate 100ms network delay
    let delayed_packet = {
        simulate_delay(Duration::from_millis(100)).await;
        packet.clone()
    };

    assert_eq!(packet.payload, delayed_packet.payload);
}

#[tokio::test]
async fn test_timeout_on_slow_operation() {
    let slow_operation = async {
        simulate_delay(Duration::from_secs(2)).await;
        "completed"
    };

    // Should timeout after 1 second
    let result = timeout(Duration::from_secs(1), slow_operation).await;
    assert!(result.is_err(), "Operation should have timed out");
}

#[tokio::test]
#[serial_test::serial]
async fn test_retry_on_simulated_failure() {
    let mut _attempt = 0;
    let max_attempts = 5; // Increased from 3 to reduce flakiness

    loop {
        _attempt += 1;

        // Simulate 30% failure rate (reduced from 50% to reduce flakiness)
        if simulate_packet_loss(0.3) {
            if _attempt >= max_attempts {
                panic!("Failed after {} attempts", max_attempts);
            }
            // Retry with exponential backoff
            sleep(Duration::from_millis(50 * _attempt)).await;
            continue;
        }

        // Success
        break;
    }

    assert!(_attempt <= max_attempts);
}

#[tokio::test]
async fn test_concurrent_operations_with_delay() {
    let mut handles = vec![];

    for i in 0..10 {
        handles.push(tokio::spawn(async move {
            // Random delay between 10-100ms
            let delay = Duration::from_millis(10 + (i * 10));
            simulate_delay(delay).await;
            i
        }));
    }

    // Wait for all operations
    let results: Vec<_> = futures::future::join_all(handles).await;

    // All should complete successfully
    assert_eq!(results.len(), 10);
    for result in results {
        assert!(result.is_ok());
    }
}

#[tokio::test]
async fn test_packet_reordering() {
    // Simulate packets arriving out of order
    let packets = vec![
        Packet {
            version: PROTOCOL_VERSION,
            payload: vec![1],
        },
        Packet {
            version: PROTOCOL_VERSION,
            payload: vec![2],
        },
        Packet {
            version: PROTOCOL_VERSION,
            payload: vec![3],
        },
    ];

    let mut received = vec![];

    // Receive packet 2 first (simulated reordering)
    received.push(packets[1].clone());
    received.push(packets[0].clone());
    received.push(packets[2].clone());

    // Reorder by payload value
    received.sort_by_key(|p| p.payload[0]);

    for (i, packet) in received.iter().enumerate() {
        assert_eq!(packet.payload[0], (i + 1) as u8);
    }
}

#[tokio::test]
async fn test_high_packet_loss_scenario() {
    let total_packets = 100;
    let loss_rate = 0.7; // 70% packet loss
    let mut received = 0;

    for _ in 0..total_packets {
        if !simulate_packet_loss(loss_rate) {
            received += 1;
        }
    }

    // With 70% loss, we should receive approximately 30 packets
    // Allow some variance (20-40 packets)
    assert!(
        received >= 15 && received <= 50,
        "Expected 20-40 packets with 70% loss, got {}",
        received
    );
}

#[tokio::test]
async fn test_jitter_simulation() {
    use rand::Rng;
    let mut rng = rand::rng();

    let mut delays = vec![];

    for _ in 0..10 {
        // Random jitter between 10-50ms
        let jitter = Duration::from_millis(rng.random_range(10..50));
        let start = tokio::time::Instant::now();
        simulate_delay(jitter).await;
        let elapsed = start.elapsed();
        delays.push(elapsed);
    }

    // Verify delays have variance (jitter)
    let min_delay = delays.iter().min().unwrap();
    let max_delay = delays.iter().max().unwrap();

    assert!(max_delay > min_delay, "Delays should vary (have jitter)");
}

#[tokio::test]
async fn test_network_partition_simulation() {
    // Simulate a network partition where communication is blocked
    let partition_active = true;

    let send_result = if partition_active {
        Err("Network partitioned")
    } else {
        Ok(())
    };

    assert!(send_result.is_err());
}

#[tokio::test]
async fn test_recovery_after_partition() {
    let mut partition_active = true;
    let mut attempts = 0;
    let max_attempts = 5;

    loop {
        attempts += 1;

        if partition_active {
            // Simulate partition healing after 3 attempts
            if attempts >= 3 {
                partition_active = false;
            } else {
                sleep(Duration::from_millis(100)).await;
                continue;
            }
        }

        // Connection restored
        break;
    }

    assert!(attempts <= max_attempts);
    assert!(!partition_active, "Should have recovered from partition");
}

#[tokio::test]
async fn test_cascading_failure_prevention() {
    // Simulate circuit breaker behavior
    let mut failure_count = 0;
    let failure_threshold = 5;
    let mut circuit_open = false;

    for _attempt in 0..10 {
        if circuit_open {
            // Circuit is open, fail fast
            sleep(Duration::from_millis(10)).await;
            continue;
        }

        // Simulate failures
        if simulate_packet_loss(0.8) {
            failure_count += 1;
            if failure_count >= failure_threshold {
                circuit_open = true;
            }
        } else {
            failure_count = 0; // Reset on success
        }

        sleep(Duration::from_millis(10)).await;
    }

    // Circuit breaker should have opened
    assert!(circuit_open || failure_count < failure_threshold);
}

#[tokio::test]
async fn test_slow_consumer() {
    // Simulate a slow consumer that can't keep up
    let mut queue = vec![];
    let max_queue_size = 10;

    for i in 0..20 {
        if queue.len() >= max_queue_size {
            // Backpressure: drop or wait
            queue.remove(0); // Drop oldest
        }

        queue.push(i);

        // Fast producer (1ms)
        sleep(Duration::from_millis(1)).await;
    }

    // Queue should be at max size due to backpressure
    assert_eq!(queue.len(), max_queue_size);
}

#[tokio::test]
async fn test_thundering_herd() {
    // Simulate many clients connecting simultaneously
    let num_clients = 100;
    let mut handles = vec![];

    let start = tokio::time::Instant::now();

    for i in 0..num_clients {
        handles.push(tokio::spawn(async move {
            // All clients try to connect at once
            sleep(Duration::from_millis(10)).await;
            i
        }));
    }

    let results = futures::future::join_all(handles).await;
    let elapsed = start.elapsed();

    // All should complete
    assert_eq!(results.len(), num_clients);

    // Should handle load efficiently (complete within 1 second)
    assert!(
        elapsed < Duration::from_secs(1),
        "Thundering herd took too long: {:?}",
        elapsed
    );
}

#[tokio::test]
async fn test_intermittent_failures() {
    // Simulate intermittent network issues
    let mut successes = 0;
    let mut failures = 0;

    for _ in 0..100 {
        if simulate_packet_loss(0.2) {
            failures += 1;
            // Retry failed operations
            sleep(Duration::from_millis(5)).await;
            if !simulate_packet_loss(0.2) {
                successes += 1;
            }
        } else {
            successes += 1;
        }
    }

    // Should have mostly successes (retries should help)
    assert!(
        successes > failures,
        "Retries should improve success rate: {} successes vs {} failures",
        successes,
        failures
    );
}