Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 141 additions & 59 deletions tests/e2e/data_types/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@

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

use sha2::{Digest, Sha256};

use super::{TestData, MAX_CHUNK_SIZE};

/// Size of small test data (1KB).
const SMALL_CHUNK_SIZE: usize = 1024;

/// Size of medium test data (1MB).
const MEDIUM_CHUNK_SIZE: usize = 1024 * 1024;

/// Test fixture for chunk operations.
#[allow(clippy::struct_field_names)]
pub struct ChunkTestFixture {
Expand All @@ -38,16 +46,15 @@ impl ChunkTestFixture {
#[must_use]
pub fn new() -> Self {
Self {
small: TestData::generate(1024), // 1KB
medium: TestData::generate(1024 * 1024), // 1MB
large: TestData::generate(MAX_CHUNK_SIZE), // 4MB
small: TestData::generate(SMALL_CHUNK_SIZE),
medium: TestData::generate(MEDIUM_CHUNK_SIZE),
large: TestData::generate(MAX_CHUNK_SIZE),
}
}

/// Compute content address for data (SHA256 hash).
#[must_use]
pub fn compute_address(data: &[u8]) -> [u8; 32] {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(data);
let hash = hasher.finalize();
Expand All @@ -60,6 +67,7 @@ impl ChunkTestFixture {
#[cfg(test)]
mod tests {
use super::*;
use crate::TestHarness;

/// Test 1: Content address computation is deterministic
#[test]
Expand Down Expand Up @@ -100,8 +108,8 @@ mod tests {
#[test]
fn test_fixture_data_sizes() {
let fixture = ChunkTestFixture::new();
assert_eq!(fixture.small.len(), 1024);
assert_eq!(fixture.medium.len(), 1024 * 1024);
assert_eq!(fixture.small.len(), SMALL_CHUNK_SIZE);
assert_eq!(fixture.medium.len(), MEDIUM_CHUNK_SIZE);
assert_eq!(fixture.large.len(), MAX_CHUNK_SIZE);
}

Expand All @@ -112,83 +120,157 @@ mod tests {
}

// =========================================================================
// Integration Tests (require testnet)
// Integration Tests (require local testnet - spun up automatically)
// =========================================================================

/// Test 6: Store and retrieve small chunk
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_store_retrieve_small() {
// TODO: Implement with TestHarness when P2P integration is complete
// let harness = TestHarness::setup().await.unwrap();
// let fixture = ChunkTestFixture::new();
//
// // Store via node 5
// let address = harness.node(5).store_chunk(&fixture.small_data).await.unwrap();
//
// // Retrieve via node 20 (different node)
// let retrieved = harness.node(20).get_chunk(&address).await.unwrap();
// assert_eq!(retrieved, fixture.small_data);
//
// harness.teardown().await.unwrap();
/// Test 6: Store and retrieve small chunk via local testnet.
///
/// This is the core e2e test that validates chunk upload/download works:
/// 1. Spins up a minimal 5-node local testnet
/// 2. Stores a 1KB chunk via one node
/// 3. Retrieves it from the same node
/// 4. Verifies data integrity
///
/// Note: Cross-node retrieval is tested separately in `test_chunk_replication`.
#[tokio::test]
async fn test_chunk_store_retrieve_small() {
let harness = TestHarness::setup_minimal()
.await
.expect("Failed to setup test harness");

let fixture = ChunkTestFixture::new();

// Store via node 0 (bootstrap node)
let store_node = harness.test_node(0).expect("Node 0 should exist");

let address = store_node
.store_chunk(&fixture.small)
.await
.expect("Failed to store chunk");

// Verify the address is a valid SHA256 hash
let expected_address = ChunkTestFixture::compute_address(&fixture.small);
assert_eq!(
address, expected_address,
"Returned address should match computed content address"
);

// Retrieve from the same node
let retrieved = store_node
.get_chunk(&address)
.await
.expect("Failed to retrieve chunk");

let chunk = retrieved.expect("Chunk should exist");
assert_eq!(
chunk.content.as_ref(),
fixture.small.as_slice(),
"Retrieved data should match original"
);

// Verify chunk address matches
assert_eq!(
chunk.address, address,
"Chunk address should match the stored address"
);

harness
.teardown()
.await
.expect("Failed to teardown harness");
}

/// Test 7: Store and retrieve large chunk (4MB max)
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_store_retrieve_large() {
// TODO: Implement with TestHarness
/// Test 7: Store and retrieve large chunk (4MB max).
#[tokio::test]
async fn test_chunk_store_retrieve_large() {
let harness = TestHarness::setup_minimal()
.await
.expect("Failed to setup test harness");

let fixture = ChunkTestFixture::new();

// Store 4MB chunk
let store_node = harness.test_node(0).expect("Node 0 should exist");
let address = store_node
.store_chunk(&fixture.large)
.await
.expect("Failed to store large chunk");

// Retrieve from the same node
let retrieved = store_node
.get_chunk(&address)
.await
.expect("Failed to retrieve large chunk");

let chunk = retrieved.expect("Large chunk should exist");
assert_eq!(chunk.content.len(), fixture.large.len());
assert_eq!(chunk.content.as_ref(), fixture.large.as_slice());

harness
.teardown()
.await
.expect("Failed to teardown harness");
}

/// Test 8: Chunk replication across nodes
// =========================================================================
// Tests requiring additional infrastructure (not yet implemented)
// =========================================================================

/// Test 8: Chunk replication across nodes.
///
/// Store on one node, retrieve from a different node.
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
#[ignore = "TODO: Cross-node DHT replication not yet working in saorsa-core"]
fn test_chunk_replication() {
// TODO: Implement - store on one node, verify retrieval from multiple others
// TODO: Implement when saorsa-core DHT replication is fixed
// - Store chunk on node 0
// - Retrieve from nodes 1-4
// - Verify data matches
}

/// Test 9: Payment verification for chunk storage
/// Test: Payment verification for chunk storage.
#[test]
#[ignore = "Requires real P2P testnet and Anvil - run with --ignored"]
#[ignore = "Requires Anvil EVM testnet integration"]
fn test_chunk_payment_verification() {
// TODO: Implement with TestHarness and TestAnvil
// - Create payment proof via Anvil
// - Store chunk with payment proof
// - Verify payment was validated
}

/// Test 10: Reject oversized chunk
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_reject_oversized() {
// TODO: Attempt to store > 4MB chunk, verify rejection
}
/// Test 8: Reject oversized chunk (> 4MB).
///
/// Chunks have a maximum size of 4MB. Attempting to store a larger
/// chunk should fail with an appropriate error.
#[tokio::test]
#[ignore = "TODO: Size validation not yet implemented in store_chunk"]
async fn test_chunk_reject_oversized() {
let harness = TestHarness::setup_minimal()
.await
.expect("Failed to setup test harness");

/// Test 11: Content address verification
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_content_address_verification() {
// TODO: Store chunk, verify returned address matches computed address
}
// Generate oversized data (4MB * 2)
let oversized_data = TestData::generate(MAX_CHUNK_SIZE * 2);

/// Test 12: Retrieve non-existent chunk returns None
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_retrieve_nonexistent() {
// TODO: Query random address, verify None returned
}
let node = harness.test_node(0).expect("Node 0 should exist");

/// Test 13: Duplicate storage returns same address
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
fn test_chunk_duplicate_storage() {
// TODO: Store same data twice, verify same address returned
// (deduplication via content addressing)
// Attempt to store oversized chunk - should fail
let result = node.store_chunk(&oversized_data).await;

assert!(
result.is_err(),
"Storing oversized chunk should fail, but got: {result:?}"
);

harness
.teardown()
.await
.expect("Failed to teardown harness");
}

/// Test 14: ML-DSA-65 signature on chunk
/// Test: ML-DSA-65 signature on chunk.
#[test]
#[ignore = "Requires real P2P testnet - run with --ignored"]
#[ignore = "Requires signature verification infrastructure"]
fn test_chunk_signature_verification() {
// TODO: Verify chunk is signed with ML-DSA-65 when stored
}
Expand Down
61 changes: 30 additions & 31 deletions tests/e2e/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@

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

use super::testnet::{
DEFAULT_BOOTSTRAP_COUNT, DEFAULT_NODE_COUNT, MINIMAL_BOOTSTRAP_COUNT, MINIMAL_NODE_COUNT,
SMALL_NODE_COUNT, TEST_PORT_RANGE_MAX, TEST_PORT_RANGE_MIN,
};
use super::{NetworkState, TestHarness, TestNetwork, TestNetworkConfig};
use std::time::Duration;

/// Test that a minimal network (5 nodes) can form and stabilize.
#[tokio::test]
#[ignore = "Requires real P2P node spawning - run with --ignored"]
async fn test_minimal_network_formation() {
// Use unique port range to avoid conflicts with parallel tests
let config = TestNetworkConfig {
base_port: 19200, // Different from other tests
..TestNetworkConfig::minimal()
};
let harness = TestHarness::setup_with_config(config)
// TestNetworkConfig automatically generates unique ports and data dirs
let harness = TestHarness::setup_minimal()
.await
.expect("Failed to setup harness");

// Verify network is ready
assert!(harness.is_ready().await);
assert_eq!(harness.node_count(), 5);
assert_eq!(harness.node_count(), MINIMAL_NODE_COUNT);

// Verify we have connections
let total_connections = harness.total_connections().await;
Expand All @@ -39,21 +39,17 @@ async fn test_minimal_network_formation() {
#[tokio::test]
#[ignore = "Requires real P2P node spawning - run with --ignored"]
async fn test_small_network_formation() {
// Use unique port range to avoid conflicts with parallel tests
let config = TestNetworkConfig {
base_port: 19300, // Different from other tests
..TestNetworkConfig::small()
};
let harness = TestHarness::setup_with_config(config)
// TestNetworkConfig automatically generates unique ports and data dirs
let harness = TestHarness::setup_small()
.await
.expect("Failed to setup harness");

// Verify network is ready
assert!(harness.is_ready().await);
assert_eq!(harness.node_count(), 10);
assert_eq!(harness.node_count(), SMALL_NODE_COUNT);

// Verify all nodes are accessible
for i in 0..10 {
for i in 0..SMALL_NODE_COUNT {
assert!(harness.node(i).is_some(), "Node {i} should be accessible");
}

Expand All @@ -69,14 +65,15 @@ async fn test_full_network_formation() {

// Verify network is ready
assert!(harness.is_ready().await);
assert_eq!(harness.node_count(), 25);
assert_eq!(harness.node_count(), DEFAULT_NODE_COUNT);

// Verify bootstrap nodes
let network = harness.network();
assert_eq!(network.bootstrap_nodes().len(), 3);
assert_eq!(network.bootstrap_nodes().len(), DEFAULT_BOOTSTRAP_COUNT);

// Verify regular nodes
assert_eq!(network.regular_nodes().len(), 22);
let expected_regular = DEFAULT_NODE_COUNT - DEFAULT_BOOTSTRAP_COUNT;
assert_eq!(network.regular_nodes().len(), expected_regular);

// Verify we can get random nodes
assert!(harness.random_node().is_some());
Expand All @@ -90,10 +87,10 @@ async fn test_full_network_formation() {
#[tokio::test]
#[ignore = "Requires real P2P node spawning - run with --ignored"]
async fn test_custom_network_config() {
// Override only the settings we care about; ports and data dir are auto-generated
let config = TestNetworkConfig {
node_count: 7,
bootstrap_count: 2,
base_port: 19100,
spawn_delay: Duration::from_millis(100),
stabilization_timeout: Duration::from_secs(60),
..Default::default()
Expand All @@ -114,12 +111,8 @@ async fn test_custom_network_config() {
#[tokio::test]
#[ignore = "Requires real P2P node spawning and Anvil - run with --ignored"]
async fn test_network_with_evm() {
// Use unique port range to avoid conflicts with parallel tests
let config = TestNetworkConfig {
base_port: 19400, // Different from other tests
..TestNetworkConfig::default()
};
let harness = TestHarness::setup_with_evm_and_config(config)
// TestNetworkConfig automatically generates unique ports and data dirs
let harness = TestHarness::setup_with_evm()
.await
.expect("Failed to setup harness with EVM");

Expand Down Expand Up @@ -174,14 +167,20 @@ fn test_network_state() {
#[test]
fn test_config_presets() {
let default = TestNetworkConfig::default();
assert_eq!(default.node_count, 25);
assert_eq!(default.bootstrap_count, 3);
assert_eq!(default.node_count, DEFAULT_NODE_COUNT);
assert_eq!(default.bootstrap_count, DEFAULT_BOOTSTRAP_COUNT);
// Ports are randomly generated in a wide range to avoid collisions
assert!(default.base_port >= TEST_PORT_RANGE_MIN && default.base_port < TEST_PORT_RANGE_MAX);

let minimal = TestNetworkConfig::minimal();
assert_eq!(minimal.node_count, 5);
assert_eq!(minimal.bootstrap_count, 2);
assert_eq!(minimal.node_count, MINIMAL_NODE_COUNT);
assert_eq!(minimal.bootstrap_count, MINIMAL_BOOTSTRAP_COUNT);

let small = TestNetworkConfig::small();
assert_eq!(small.node_count, 10);
assert_eq!(small.bootstrap_count, 3);
assert_eq!(small.node_count, SMALL_NODE_COUNT);
assert_eq!(small.bootstrap_count, DEFAULT_BOOTSTRAP_COUNT);

// Each config should have a unique data directory
assert_ne!(default.test_data_dir, minimal.test_data_dir);
assert_ne!(minimal.test_data_dir, small.test_data_dir);
}
Loading
Loading