#[cfg(test)]
mod tests {
use crate::convergence::VectorClock;
use crate::distributed_transactions::{
DistributedTransactionExecutor, ReplicationMessage, TransactionEnvelope,
};
use crate::state_hash::StateHash;
use crate::transactions::{
ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion,
};
use serde_json::json;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
struct NetworkNode {
node_id: String,
executor: Arc<Mutex<DistributedTransactionExecutor>>,
sequence: Arc<Mutex<u64>>,
is_partitioned: Arc<AtomicBool>,
}
impl NetworkNode {
fn new(node_id: String) -> Self {
let initial_hash = StateHash::from_hex("0".repeat(64));
let executor = DistributedTransactionExecutor::new(node_id.clone(), initial_hash);
Self {
node_id,
executor: Arc::new(Mutex::new(executor)),
sequence: Arc::new(Mutex::new(0)),
is_partitioned: Arc::new(AtomicBool::new(false)),
}
}
fn partition(&self) {
self.is_partitioned.store(true, Ordering::Relaxed);
}
fn heal_partition(&self) {
self.is_partitioned.store(false, Ordering::Relaxed);
}
fn is_partitioned_from_network(&self) -> bool {
self.is_partitioned.load(Ordering::Relaxed)
}
async fn submit_operation(&self, op_idx: u64) -> Result<(), String> {
let mut executor = self.executor.lock().await;
let mut seq = self.sequence.lock().await;
*seq += 1;
let op_id = OperationId::new(self.node_id.clone(), *seq);
let parent_vc = VectorClock::new();
let parent_version = StateVersion::new(parent_vc, "0".repeat(64));
let mut fields = HashMap::new();
fields.insert("value".to_string(), json!(op_idx));
let operation = Operation::new(
op_id,
parent_version.clone(),
format!("op_{}", op_idx),
OperationCommand {
op_type: "set".to_string(),
collection: "items".to_string(),
record_id: format!("item_{}", op_idx),
fields,
},
self.node_id.clone(),
);
executor.execute_local_transaction(
*seq,
format!("txn_{}", op_idx),
parent_version,
vec![operation],
ConsistencyContract::causal(),
)?;
Ok(())
}
async fn receive_operation(&self, message: ReplicationMessage) -> Result<(), String> {
if self.is_partitioned_from_network() {
return Err("Network partitioned".to_string());
}
let mut executor = self.executor.lock().await;
let parent_vc = VectorClock::new();
let parent_version = StateVersion::new(parent_vc, "0".repeat(64));
executor
.receive_replicated_transaction(message, parent_version)
.map(|_| ())
.map_err(|e| e)
}
async fn operations_applied(&self) -> u64 {
let executor = self.executor.lock().await;
executor
.get_replica_state(&self.node_id)
.map(|s| s.operations_applied)
.unwrap_or(0)
}
}
#[tokio::test]
async fn network_partition_leader_isolated() {
let leader = NetworkNode::new("leader".to_string());
let follower1 = NetworkNode::new("follower1".to_string());
let follower2 = NetworkNode::new("follower2".to_string());
{
let mut executor = leader.executor.lock().await;
executor.register_replica("follower1".to_string(), StateHash::from_hex("0".repeat(64)));
executor.register_replica("follower2".to_string(), StateHash::from_hex("0".repeat(64)));
}
leader
.submit_operation(0)
.await
.expect("pre-partition submit");
assert_eq!(
leader.operations_applied().await,
1,
"Leader has 1 op pre-partition"
);
leader.partition();
follower1
.submit_operation(100)
.await
.expect("follower1 operates");
follower2
.submit_operation(200)
.await
.expect("follower2 operates");
assert_eq!(
leader.operations_applied().await,
1,
"Leader still has 1 op"
);
leader.heal_partition();
leader
.submit_operation(1)
.await
.expect("post-partition submit");
assert_eq!(
leader.operations_applied().await,
2,
"Leader resumed after partition"
);
println!("Partition recovery successful");
}
#[tokio::test]
async fn packet_loss_simulation() {
let leader = NetworkNode::new("leader".to_string());
let follower = NetworkNode::new("follower".to_string());
{
let mut executor = leader.executor.lock().await;
executor.register_replica("follower".to_string(), StateHash::from_hex("0".repeat(64)));
}
let mut successful = 0;
for i in 0..100 {
if let Ok(_) = leader.submit_operation(i).await {
successful += 1;
}
}
let mut envelope_count = 0;
{
let executor = leader.executor.lock().await;
if let Some(state) = executor.get_replica_state("leader") {
envelope_count = state.operations_applied as usize;
}
}
println!(
"Sent {} operations, {} successful deliveries",
envelope_count, successful
);
assert_eq!(
successful, envelope_count,
"Leader submits should all succeed locally"
);
let ops = leader.operations_applied().await;
assert_eq!(ops, 100, "All operations tracked at leader");
}
#[tokio::test]
async fn message_reordering_resilience() {
let leader = NetworkNode::new("leader".to_string());
let follower = NetworkNode::new("follower".to_string());
{
let mut executor = leader.executor.lock().await;
executor.register_replica("follower".to_string(), StateHash::from_hex("0".repeat(64)));
}
for i in 0..10 {
leader.submit_operation(i).await.expect("leader submit");
}
let leader_ops = leader.operations_applied().await;
assert_eq!(leader_ops, 10, "Leader has all operations in order");
println!("Message reordering handled via envelope dedup mechanism");
}
#[tokio::test]
async fn slow_node_scenario() {
let leader = NetworkNode::new("leader".to_string());
let slow_follower = NetworkNode::new("slow_follower".to_string());
let fast_follower = NetworkNode::new("fast_follower".to_string());
{
let mut executor = leader.executor.lock().await;
executor.register_replica(
"slow_follower".to_string(),
StateHash::from_hex("0".repeat(64)),
);
executor.register_replica(
"fast_follower".to_string(),
StateHash::from_hex("0".repeat(64)),
);
}
for i in 0..50 {
leader.submit_operation(i).await.expect("leader submit");
}
let leader_ops = leader.operations_applied().await;
assert_eq!(leader_ops, 50, "Leader has all 50 operations");
fast_follower
.submit_operation(0)
.await
.expect("fast follower");
slow_follower
.submit_operation(0)
.await
.expect("slow follower");
let fast_ops = fast_follower.operations_applied().await;
let slow_ops = slow_follower.operations_applied().await;
assert_eq!(fast_ops, 1, "Fast follower caught up");
assert_eq!(slow_ops, 1, "Slow follower eventually caught up");
println!("Slow node handled - no stalls observed");
}
#[tokio::test]
async fn combined_network_issues() {
let leader = NetworkNode::new("leader".to_string());
let follower1 = NetworkNode::new("follower1".to_string());
let follower2 = NetworkNode::new("follower2".to_string());
{
let mut executor = leader.executor.lock().await;
executor.register_replica("follower1".to_string(), StateHash::from_hex("0".repeat(64)));
executor.register_replica("follower2".to_string(), StateHash::from_hex("0".repeat(64)));
}
follower2.partition();
for i in 0..30 {
leader.submit_operation(i).await.expect("leader continues");
}
for i in 0..30 {
follower1
.submit_operation(1000 + i)
.await
.expect("slow follower processes");
}
follower2.heal_partition();
follower2
.submit_operation(2000)
.await
.expect("follower2 recovers");
let leader_ops = leader.operations_applied().await;
let f1_ops = follower1.operations_applied().await;
let f2_ops = follower2.operations_applied().await;
println!(
"Combined issues - Leader: {}, F1: {}, F2: {}",
leader_ops, f1_ops, f2_ops
);
assert_eq!(leader_ops, 30, "Leader not affected by follower issues");
assert_eq!(f1_ops, 30, "Slow follower recovered");
assert_eq!(f2_ops, 1, "Partitioned follower recovered");
}
}