use communitas_core::{ConnectivityWatchdog, ResourceLimitError, ResourceLimits, WatchdogConfig};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::time::sleep;
#[tokio::test]
async fn test_watchdog_starts_monitoring_bootstrap() {
let config = WatchdogConfig {
check_interval: Duration::from_millis(50),
detection_threshold: Duration::from_millis(200),
recovery_check_interval: Duration::from_millis(100),
enabled: true,
};
let watchdog = ConnectivityWatchdog::new(config);
let call_count = Arc::new(AtomicUsize::new(0));
let call_count_clone = call_count.clone();
let should_fail = Arc::new(AtomicBool::new(true));
let should_fail_clone = should_fail.clone();
let health_check = move || {
let count = call_count_clone.clone();
let fail = should_fail_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
!fail.load(Ordering::SeqCst) }
};
let handle = watchdog.clone().start_monitoring(health_check);
assert!(!watchdog.is_local_only_mode());
sleep(Duration::from_millis(300)).await;
assert!(
watchdog.is_local_only_mode(),
"Watchdog should enter local-only mode when bootstrap fails"
);
should_fail.store(false, Ordering::SeqCst);
sleep(Duration::from_millis(250)).await;
assert!(
!watchdog.is_local_only_mode(),
"Watchdog should exit local-only mode when bootstrap recovers"
);
let checks = call_count.load(Ordering::SeqCst);
assert!(
checks >= 5,
"Health check should be called at least 5 times, got {}",
checks
);
handle.abort();
}
#[tokio::test]
async fn test_gossip_context_respects_local_only_mode() {
}
#[tokio::test]
async fn test_membership_enforces_peer_limits() {
let limits = ResourceLimits {
max_peer_connections: 2,
..ResourceLimits::default()
};
let mut current_peers = 0;
let result1 = limits.enforce_peer_limit(current_peers);
assert!(result1.is_ok());
current_peers += 1;
let result2 = limits.enforce_peer_limit(current_peers);
assert!(result2.is_ok());
current_peers += 1;
let result3 = limits.enforce_peer_limit(current_peers);
assert!(result3.is_err());
match result3 {
Err(ResourceLimitError::PeerLimitExceeded { current, limit }) => {
assert_eq!(current, 2);
assert_eq!(limit, 2);
}
_ => panic!("Expected PeerLimitExceeded error"),
}
}
#[tokio::test]
async fn test_document_operations_enforce_size_limits() {
let limits = ResourceLimits {
crdt_document_limit_mb: 10,
..ResourceLimits::default()
};
assert!(limits.enforce_document_limit(5).is_ok());
let result = limits.enforce_document_limit(11);
assert!(result.is_err());
match result {
Err(ResourceLimitError::DocumentTooLarge { size_mb, limit_mb }) => {
assert_eq!(size_mb, 11);
assert_eq!(limit_mb, 10);
}
_ => panic!("Expected DocumentTooLarge error"),
}
}
#[test]
fn test_resource_limits_customization() {
let limits = ResourceLimits {
max_peer_connections: 100,
max_relay_connections: 5,
max_memory_mb: 4096,
crdt_document_limit_mb: 100,
..ResourceLimits::default()
};
assert_eq!(limits.max_peer_connections, 100);
assert_eq!(limits.max_relay_connections, 5);
assert_eq!(limits.max_memory_mb, 4096);
assert_eq!(limits.crdt_document_limit_mb, 100);
assert!(limits.validate().is_ok());
}
#[tokio::test]
async fn test_watchdog_can_be_disabled() {
let config = WatchdogConfig {
enabled: false,
..Default::default()
};
let watchdog = ConnectivityWatchdog::new(config);
let call_count = Arc::new(AtomicUsize::new(0));
let call_count_clone = call_count.clone();
let health_check = move || {
let count = call_count_clone.clone();
async move {
count.fetch_add(1, Ordering::SeqCst);
false }
};
let handle = watchdog.clone().start_monitoring(health_check);
sleep(Duration::from_millis(200)).await;
assert!(!watchdog.is_local_only_mode());
assert_eq!(call_count.load(Ordering::SeqCst), 0);
handle.abort();
}
#[tokio::test]
async fn test_concurrent_retries_use_jitter() {
use communitas_core::retry_utils::{RetryConfig, retry_with_backoff};
use std::time::Instant;
let config = RetryConfig {
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(500),
max_retries: 3,
backoff_multiplier: 2.0,
};
let start_times = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let mut handles = vec![];
for _ in 0..10 {
let config = config.clone();
let times = start_times.clone();
let handle = tokio::spawn(async move {
let start = Instant::now();
times.lock().await.push(start);
let _ = retry_with_backoff(|| async { Err::<(), _>(anyhow::anyhow!("Fail")) }, config)
.await;
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await;
}
let times = start_times.lock().await;
let mut differs = false;
for i in 0..times.len() - 1 {
if times[i].elapsed() > Duration::from_millis(1) {
differs = true;
break;
}
}
assert!(
differs,
"Concurrent retries should have jittered start times"
);
}
#[tokio::test]
async fn test_end_to_end_local_only_mode_blocks_wan_dials() {
let watchdog = ConnectivityWatchdog::default();
watchdog.force_local_only();
let should_dial_wan = !watchdog.is_local_only_mode();
assert!(
!should_dial_wan,
"WAN dials should be blocked in local-only mode"
);
watchdog.force_online();
let should_dial_wan = !watchdog.is_local_only_mode();
assert!(should_dial_wan, "WAN dials should be allowed when online");
}
#[test]
fn test_resource_limits_prevent_oom() {
let limits = ResourceLimits {
max_memory_mb: 1024,
..ResourceLimits::default()
};
assert!(limits.check_memory_usage(512).is_ok());
assert!(limits.check_memory_usage(1024).is_ok());
assert!(limits.check_memory_usage(1025).is_err());
assert!(limits.check_memory_usage(2048).is_err());
}
#[test]
fn test_bandwidth_limit_conversion() {
let limits = ResourceLimits {
upload_rate_limit_mbps: Some(10),
download_rate_limit_mbps: Some(100),
..ResourceLimits::default()
};
let upload_bps = limits.upload_rate_bytes_per_sec();
let download_bps = limits.download_rate_bytes_per_sec();
assert_eq!(upload_bps, Some(1_250_000));
assert_eq!(download_bps, Some(12_500_000));
}
#[test]
fn test_connection_timeout_enforcement() {
let default_limits = ResourceLimits::default();
let low_res_limits = ResourceLimits::low_resource();
let high_perf_limits = ResourceLimits::high_performance();
assert_eq!(default_limits.connection_timeout, Duration::from_secs(30));
assert_eq!(low_res_limits.connection_timeout, Duration::from_secs(15));
assert_eq!(high_perf_limits.connection_timeout, Duration::from_secs(60));
}