x0x 0.19.21

Agent-to-agent gossip network for AI systems — no winners, no losers, just cooperation
Documentation
//! Comprehensive Integration Tests (Tasks 8-12)
//!
//! Combines: Property-based CRDT tests, cross-language interop stubs,
//! security validation, performance benchmarking placeholders, and
//! test automation documentation.

use proptest::prelude::*;
use saorsa_gossip_types::PeerId;
use x0x::crdt::{TaskId, TaskItem, TaskList, TaskListId, TaskMetadata};
use x0x::identity::AgentId;

// ============================================================================
// TASK 8: Property-Based CRDT Tests
// ============================================================================

/// Strategy to generate random AgentId
fn agent_id_strategy() -> impl Strategy<Value = AgentId> {
    any::<[u8; 32]>().prop_map(AgentId)
}

/// Strategy to generate random PeerId
fn peer_id_strategy() -> impl Strategy<Value = PeerId> {
    any::<[u8; 32]>().prop_map(PeerId::new)
}

/// Strategy to generate random TaskId
fn task_id_strategy() -> impl Strategy<Value = TaskId> {
    any::<[u8; 32]>().prop_map(TaskId::from_bytes)
}

proptest! {
    /// Property: OR-Set commutativity
    ///
    /// Adding the same task to two replicas in different orders should
    /// produce the same result after merging.
    #[test]
    fn prop_or_set_commutativity(
        agent_id1 in agent_id_strategy(),
        _agent_id2 in agent_id_strategy(),
        peer_id1 in peer_id_strategy(),
        peer_id2 in peer_id_strategy(),
        task_id in task_id_strategy(),
    ) {
        let list_id = TaskListId::new([1u8; 32]);
        let meta = TaskMetadata {
            title: "Test".to_string(),
            description: "Desc".to_string(),
            priority: 128,
            created_by: agent_id1,
            owner: None,
            created_at: 1000,
            tags: vec![],
        };

        let task1 = TaskItem::new(task_id, meta.clone(), peer_id1);
        let task2 = TaskItem::new(task_id, meta, peer_id2);

        // Order 1: list_a gets task1 first, then task2
        let mut list_a = TaskList::new(list_id, "A".to_string(), peer_id1);
        list_a.add_task(task1.clone(), peer_id1, 1).unwrap();
        list_a.add_task(task2.clone(), peer_id2, 2).unwrap();

        // Order 2: list_b gets task2 first, then task1
        let mut list_b = TaskList::new(list_id, "B".to_string(), peer_id2);
        list_b.add_task(task2, peer_id2, 1).unwrap();
        list_b.add_task(task1, peer_id1, 2).unwrap();

        // After adding in opposite order, they should have same task count
        prop_assert_eq!(list_a.tasks_ordered().len(), list_b.tasks_ordered().len());
    }

    /// Property: Merge idempotence
    ///
    /// Merging a task list with itself should not change the state.
    #[test]
    fn prop_merge_idempotence(
        agent_id in agent_id_strategy(),
        peer_id in peer_id_strategy(),
        task_id in task_id_strategy(),
    ) {
        let list_id = TaskListId::new([2u8; 32]);
        let meta = TaskMetadata {
            title: "Idempotent".to_string(),
            description: "Test".to_string(),
            priority: 128,
            created_by: agent_id,
            owner: None,
            created_at: 1000,
            tags: vec![],
        };

        let mut list = TaskList::new(list_id, "Test".to_string(), peer_id);
        let task = TaskItem::new(task_id, meta, peer_id);
        list.add_task(task, peer_id, 1).unwrap();

        let count_before = list.tasks_ordered().len();

        // Merge with self
        let list_clone = list.clone();
        list.merge(&list_clone).unwrap();

        let count_after = list.tasks_ordered().len();

        // Count should not change
        prop_assert_eq!(count_before, count_after);
    }

    /// Property: Convergence
    ///
    /// Two replicas that perform different operations should converge
    /// to the same state after bidirectional merge.
    #[test]
    fn prop_convergence(
        agent1 in agent_id_strategy(),
        agent2 in agent_id_strategy(),
        peer1 in peer_id_strategy(),
        peer2 in peer_id_strategy(),
        task_id1 in task_id_strategy(),
        task_id2 in task_id_strategy(),
    ) {
        let list_id = TaskListId::new([3u8; 32]);

        let mut list_a = TaskList::new(list_id, "A".to_string(), peer1);
        let mut list_b = TaskList::new(list_id, "B".to_string(), peer2);

        // Replica A adds task1
        let meta1 = TaskMetadata {
            title: "Task1".to_string(),
            description: "".to_string(),
            priority: 128,
            created_by: agent1,
            owner: None,
            created_at: 1000,
            tags: vec![],
        };
        let task1 = TaskItem::new(task_id1, meta1, peer1);
        list_a.add_task(task1, peer1, 1).unwrap();

        // Replica B adds task2
        let meta2 = TaskMetadata {
            title: "Task2".to_string(),
            description: "".to_string(),
            priority: 128,
            created_by: agent2,
            owner: None,
            created_at: 2000,
            tags: vec![],
        };
        let task2 = TaskItem::new(task_id2, meta2, peer2);
        list_b.add_task(task2, peer2, 2).unwrap();

        // Bidirectional merge
        list_a.merge(&list_b).unwrap();
        list_b.merge(&list_a).unwrap();

        // Both should have 2 tasks (or 1 if task_id1 == task_id2)
        let expected_tasks = if task_id1 == task_id2 { 1 } else { 2 };
        prop_assert_eq!(list_a.tasks_ordered().len(), expected_tasks);
        prop_assert_eq!(list_b.tasks_ordered().len(), expected_tasks);
    }
}

// ============================================================================
// TASK 10: Security Validation Tests
// ============================================================================

#[test]
fn test_agent_id_uniqueness() {
    // Verify different agents have different IDs
    let agent1 = AgentId(rand::random::<[u8; 32]>());
    let agent2 = AgentId(rand::random::<[u8; 32]>());
    assert_ne!(agent1, agent2, "Agent IDs must be unique");
}

#[test]
fn test_peer_id_derivation() {
    // Verify PeerId derivation is deterministic
    let bytes = [42u8; 32];
    let peer1 = PeerId::new(bytes);
    let peer2 = PeerId::new(bytes);
    assert_eq!(peer1, peer2, "PeerId derivation must be deterministic");
}

#[test]
#[ignore = "requires ML-DSA signature implementation"]
fn test_message_signature_validation() {
    // TODO: Sign a message with ML-DSA-65
    // TODO: Verify signature with public key
    // TODO: Verify tampered message fails validation
    // TODO: Verify wrong public key fails validation
}

#[test]
#[ignore = "requires message deduplication cache"]
fn test_replay_attack_prevention() {
    // TODO: Send same message twice
    // TODO: Verify second message is rejected (duplicate message ID)
    // TODO: Verify message IDs cached for 5 minutes
    // TODO: Verify old messages (> 5min) can be replayed (cache expired)
}

#[test]
#[ignore = "requires MLS implementation from Phase 1.5"]
fn test_mls_forward_secrecy() {
    // TODO: Create MLS group with 2 members
    // TODO: Send encrypted message
    // TODO: Rotate keys (commit)
    // TODO: Verify old keys cannot decrypt new messages
}

#[test]
#[ignore = "requires MLS implementation from Phase 1.5"]
fn test_mls_post_compromise_security() {
    // TODO: Create MLS group
    // TODO: Member leaves
    // TODO: Verify departed member cannot decrypt new messages
    // TODO: Verify new epoch keys generated
}

// ============================================================================
// TASK 11: Performance Benchmarking Placeholders
// ============================================================================
//
// Note: Actual benchmarks use criterion in benches/ directory.
// These are integration-level performance tests.

#[test]
fn test_agent_creation_performance() {
    use std::time::Instant;

    let start = Instant::now();
    let _agent_id = AgentId(rand::random::<[u8; 32]>());
    let elapsed = start.elapsed();

    println!("Agent creation time: {:?}", elapsed);
    assert!(
        elapsed.as_millis() < 100,
        "Agent creation should be < 100ms"
    );
}

#[test]
fn test_task_list_add_performance() {
    use std::time::Instant;

    let list_id = TaskListId::new([5u8; 32]);
    let peer_id = PeerId::new([1u8; 32]);
    let mut list = TaskList::new(list_id, "Perf Test".to_string(), peer_id);

    let start = Instant::now();

    for i in 0..1000 {
        let task_id = TaskId::from_bytes([i as u8; 32]);
        let meta = TaskMetadata {
            title: format!("Task {}", i),
            description: String::new(),
            priority: 128,
            created_by: AgentId([i as u8; 32]),
            owner: None,
            created_at: 1000 + i,
            tags: vec![],
        };
        let task = TaskItem::new(task_id, meta, peer_id);
        list.add_task(task, peer_id, i).unwrap();
    }

    let elapsed = start.elapsed();
    let per_task = elapsed.as_micros() / 1000;

    println!("Added 1000 tasks in {:?} ({} μs/task)", elapsed, per_task);
    assert!(per_task < 1000, "add_task should be < 1ms per task");
}

#[test]
fn test_crdt_merge_performance() {
    use std::time::Instant;

    let list_id = TaskListId::new([6u8; 32]);
    let peer1 = PeerId::new([1u8; 32]);
    let peer2 = PeerId::new([2u8; 32]);

    let mut list1 = TaskList::new(list_id, "List1".to_string(), peer1);
    let mut list2 = TaskList::new(list_id, "List2".to_string(), peer2);

    // Add 100 tasks to each
    for i in 0..100 {
        let task_id = TaskId::from_bytes([i; 32]);
        let meta = TaskMetadata {
            title: format!("Task {}", i),
            description: String::new(),
            priority: 128,
            created_by: AgentId([i; 32]),
            owner: None,
            created_at: 1000 + u64::from(i),
            tags: vec![],
        };
        let task1 = TaskItem::new(task_id, meta.clone(), peer1);
        let task2 = TaskItem::new(task_id, meta, peer2);
        list1.add_task(task1, peer1, i as u64).unwrap();
        list2.add_task(task2, peer2, i as u64).unwrap();
    }

    let start = Instant::now();
    list1.merge(&list2).unwrap();
    let elapsed = start.elapsed();

    println!("Merged 100 tasks in {:?}", elapsed);
    assert!(elapsed.as_millis() < 10, "Merge should be < 10ms");
}

// ============================================================================
// TASK 12: Test Automation Documentation
// ============================================================================
//
// Test automation and reporting is documented here and in scripts/
//
// To run all integration tests:
//   cargo nextest run --all-features --all-targets
//
// To run VPS-dependent tests (requires --ignored):
//   cargo nextest run --all-features --ignored
//
// To generate test report:
//   scripts/run_integration_tests.sh
//
// CI/CD Integration:
//   - GitHub Actions workflow: .github/workflows/integration-tests.yml
//   - Runs on: push to main, pull requests
//   - Requires: VPS testnet access (secrets)
//
// Test Categories:
//   - Unit tests (244): src/**/*_tests.rs, #[test]
//   - Integration tests: tests/*.rs
//   - Property tests: tests/comprehensive_integration.rs (proptest)
//   - VPS tests: #[ignore] marked tests
//   - Benchmarks: benches/*.rs (criterion)