Skip to content

Conversation

@echobt
Copy link
Contributor

@echobt echobt commented Feb 3, 2026

Summary

This PR adds extensive unit test coverage across multiple Platform v2 crates to ensure all core modules function correctly.

Changes

Test Coverage Added

Crate Module Lines Added
p2p-consensus network.rs 419
challenge-registry version.rs 232
challenge-registry lifecycle.rs 221
challenge-registry health.rs 178
challenge-registry error.rs 185
distributed-storage error.rs 206
core challenge.rs 198
rpc-server server.rs 114

Total: 1,753 lines of new test code

Testing

All 609+ tests pass with cargo test --workspace

Verification

  • cargo check --workspace - Compiles successfully
  • cargo test --workspace - All tests pass

Summary by CodeRabbit

  • Tests
    • Added comprehensive unit test coverage across error handling, health monitoring, lifecycle state management, version constraints, challenge configuration, distributed storage, peer-to-peer consensus networking, and RPC server operations to validate system behavior and error handling paths.

Add extensive test coverage across multiple crates:
- p2p-consensus: network.rs with 419 lines of tests
- challenge-registry: version, lifecycle, health, and error modules
- rpc-server: RpcConfig and RpcServer tests
- distributed-storage: StorageError type tests
- core: Challenge and ChallengeConfig tests

All 609+ tests pass with cargo test --workspace
@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive unit test coverage across multiple crates and modules, including error handling, health monitoring, lifecycle management, versioning, challenge configuration, distributed storage, P2P networking, and RPC server functionality. No production code or public APIs were modified.

Changes

Cohort / File(s) Summary
Challenge Registry Tests
crates/challenge-registry/src/error.rs, health.rs, lifecycle.rs, version.rs
Added 816 lines of unit tests covering RegistryError variants, health status defaults and transitions, lifecycle state machines with validation of all state paths, version constraints and comparison logic, and error conversions from IO, serde_json, and bincode.
Core Challenge Tests
crates/core/src/challenge.rs
Added 198 lines of tests for Challenge and ChallengeConfig, covering defaults, code verification, activation state, timestamps, metadata conversion, and edge cases with empty and large WASM payloads.
Distributed Storage Tests
crates/distributed-storage/src/error.rs
Added 206 lines of tests for StorageError variants, serialization/deserialization conversions (sled, bincode, serde_json), Send/Sync trait compliance, and StorageResult semantics.
P2P Network Tests
crates/p2p-consensus/src/network.rs
Added 419 lines of tests for PeerMapping operations, network error display, replay attack detection, rate limit enforcement, nonce cleanup, and peer connectivity tracking.
RPC Server Tests
crates/rpc-server/src/server.rs
Added 114 lines of tests for RpcConfig cloning and debugging, router creation, CORS configuration, and JSON-RPC batch handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with testing cheer,
A thousand tests now crystal clear!
From errors caught to health so sound,
Quality is what we've found!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding comprehensive unit tests across multiple Platform v2 modules.
Docstring Coverage ✅ Passed Docstring coverage is 91.01% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/comprehensive-unit-tests-1770142134

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@crates/distributed-storage/src/error.rs`:
- Around line 184-232: The three tests (test_from_sled_error,
test_from_bincode_error, test_from_serde_json_error) use `if let Err` which can
silently skip assertions if the call unexpectedly succeeds; change each to force
the error path by calling unwrap_err() or expect_err() on the failing operation
(e.g., replace `let sled_result = sled::open("/\0invalid"); if let Err(sled_err)
= sled_result { ... }` with `let sled_err =
sled::open("/\0invalid").unwrap_err()` and similarly use
`bincode::deserialize(invalid_data).unwrap_err()` and
`serde_json::from_str(invalid_json).unwrap_err()`), then convert the obtained
error into a `StorageError` and keep the existing to_string() starts_with
assertions.

In `@crates/p2p-consensus/src/network.rs`:
- Around line 962-982: The test shows PeerMapping::insert currently leaves a
stale entry in hotkey_to_peer when overwriting a peer's hotkey; update
PeerMapping::insert to first check peer_to_hotkey for an existing hotkey for the
given peer_id, and if found remove that old mapping from hotkey_to_peer before
inserting the new hotkey (ensure peer_to_hotkey is then updated to the new
hotkey and hotkey_to_peer points to the peer_id). Alternatively, if stale
mappings are intended, add a comment in PeerMapping::insert explaining why
hotkey_to_peer is not cleaned on overwrite.
🧹 Nitpick comments (3)
crates/rpc-server/src/server.rs (1)

746-753: Weak assertion does not verify route registration.

The assertion std::mem::size_of_val(&router) > 0 is always true for any non-zero-sized type and doesn't actually verify that routes are registered. The comment acknowledges this limitation, but the test provides minimal value beyond verifying router() doesn't panic.

Consider either:

  1. Accepting this as a smoke test (rename to test_rpc_server_router_creation_does_not_panic)
  2. Using axum_test or similar to actually invoke routes and verify responses
crates/challenge-registry/src/version.rs (1)

200-209: Consider documenting the "1.2.3.4" parsing behavior.

Line 208 tests that "1.2.3.4" parses successfully with extra parts ignored. While the comment notes this, the production code's behavior of silently ignoring extra version components might be surprising. Consider whether this should return None for stricter semver compliance, or add documentation to the parse function explaining this lenient behavior.

crates/p2p-consensus/src/network.rs (1)

1083-1084: Consider using #[test] instead of #[tokio::test] for synchronous tests.

These tests don't contain any .await calls, so they don't require the tokio runtime. Using plain #[test] avoids the overhead of spawning a runtime.

Example change for one test
-    #[tokio::test]
-    async fn test_replay_attack_detection() {
+    #[test]
+    fn test_replay_attack_detection() {

This applies to: test_replay_attack_detection, test_rate_limit_enforcement, test_clean_old_nonces, test_clean_rate_limit_entries, test_network_connected_peer_count, test_network_has_min_peers.

Also applies to: 1128-1129, 1171-1172, 1214-1215, 1265-1266, 1302-1303

Comment on lines +184 to +232
#[test]
fn test_from_sled_error() {
// Create a sled error by opening an invalid path scenario
// sled::Error doesn't have public constructors, so we trigger a real error
let sled_result = sled::open("/\0invalid");
if let Err(sled_err) = sled_result {
let storage_err: StorageError = sled_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Database error:"),
"Expected 'Database error:' prefix, got: {}",
display
);
}
}

#[test]
fn test_from_bincode_error() {
// Create a bincode error by attempting to deserialize invalid data
let invalid_data: &[u8] = &[0xff, 0xff, 0xff, 0xff];
let bincode_result: Result<String, bincode::Error> =
bincode::deserialize(invalid_data);
if let Err(bincode_err) = bincode_result {
let storage_err: StorageError = bincode_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
}

#[test]
fn test_from_serde_json_error() {
// Create a serde_json error by parsing invalid JSON
let invalid_json = "{invalid json}";
let json_result: Result<serde_json::Value, serde_json::Error> =
serde_json::from_str(invalid_json);
if let Err(json_err) = json_result {
let storage_err: StorageError = json_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

From conversion tests may silently pass if error creation fails.

The if let Err pattern means these tests will silently pass without assertions if the error creation unexpectedly succeeds. Consider using unwrap_err() or expect_err() to ensure the error path is always exercised:

🔧 Suggested fix for test_from_sled_error
     #[test]
     fn test_from_sled_error() {
-        // Create a sled error by opening an invalid path scenario
-        // sled::Error doesn't have public constructors, so we trigger a real error
-        let sled_result = sled::open("/\0invalid");
-        if let Err(sled_err) = sled_result {
-            let storage_err: StorageError = sled_err.into();
-            let display = storage_err.to_string();
-            assert!(
-                display.starts_with("Database error:"),
-                "Expected 'Database error:' prefix, got: {}",
-                display
-            );
-        }
+        // sled::Error doesn't have public constructors, so we trigger a real error
+        let sled_err = sled::open("/\0invalid").expect_err("Expected sled::open to fail");
+        let storage_err: StorageError = sled_err.into();
+        let display = storage_err.to_string();
+        assert!(
+            display.starts_with("Database error:"),
+            "Expected 'Database error:' prefix, got: {}",
+            display
+        );
     }

The same pattern should be applied to test_from_bincode_error and test_from_serde_json_error.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn test_from_sled_error() {
// Create a sled error by opening an invalid path scenario
// sled::Error doesn't have public constructors, so we trigger a real error
let sled_result = sled::open("/\0invalid");
if let Err(sled_err) = sled_result {
let storage_err: StorageError = sled_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Database error:"),
"Expected 'Database error:' prefix, got: {}",
display
);
}
}
#[test]
fn test_from_bincode_error() {
// Create a bincode error by attempting to deserialize invalid data
let invalid_data: &[u8] = &[0xff, 0xff, 0xff, 0xff];
let bincode_result: Result<String, bincode::Error> =
bincode::deserialize(invalid_data);
if let Err(bincode_err) = bincode_result {
let storage_err: StorageError = bincode_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
}
#[test]
fn test_from_serde_json_error() {
// Create a serde_json error by parsing invalid JSON
let invalid_json = "{invalid json}";
let json_result: Result<serde_json::Value, serde_json::Error> =
serde_json::from_str(invalid_json);
if let Err(json_err) = json_result {
let storage_err: StorageError = json_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
}
#[test]
fn test_from_sled_error() {
// sled::Error doesn't have public constructors, so we trigger a real error
let sled_err = sled::open("/\0invalid").expect_err("Expected sled::open to fail");
let storage_err: StorageError = sled_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Database error:"),
"Expected 'Database error:' prefix, got: {}",
display
);
}
#[test]
fn test_from_bincode_error() {
// Create a bincode error by attempting to deserialize invalid data
let invalid_data: &[u8] = &[0xff, 0xff, 0xff, 0xff];
let bincode_err = bincode::deserialize::<String>(invalid_data)
.expect_err("Expected bincode::deserialize to fail");
let storage_err: StorageError = bincode_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
#[test]
fn test_from_serde_json_error() {
// Create a serde_json error by parsing invalid JSON
let invalid_json = "{invalid json}";
let json_err = serde_json::from_str::<serde_json::Value>(invalid_json)
.expect_err("Expected serde_json::from_str to fail");
let storage_err: StorageError = json_err.into();
let display = storage_err.to_string();
assert!(
display.starts_with("Serialization error:"),
"Expected 'Serialization error:' prefix, got: {}",
display
);
}
🤖 Prompt for AI Agents
In `@crates/distributed-storage/src/error.rs` around lines 184 - 232, The three
tests (test_from_sled_error, test_from_bincode_error,
test_from_serde_json_error) use `if let Err` which can silently skip assertions
if the call unexpectedly succeeds; change each to force the error path by
calling unwrap_err() or expect_err() on the failing operation (e.g., replace
`let sled_result = sled::open("/\0invalid"); if let Err(sled_err) = sled_result
{ ... }` with `let sled_err = sled::open("/\0invalid").unwrap_err()` and
similarly use `bincode::deserialize(invalid_data).unwrap_err()` and
`serde_json::from_str(invalid_json).unwrap_err()`), then convert the obtained
error into a `StorageError` and keep the existing to_string() starts_with
assertions.

Comment on lines +962 to +982
#[test]
fn test_peer_mapping_overwrite() {
let mapping = PeerMapping::new();
let peer_id = PeerId::random();
let hotkey1 = Hotkey([1u8; 32]);
let hotkey2 = Hotkey([2u8; 32]);

// Insert with first hotkey
mapping.insert(peer_id, hotkey1.clone());
assert_eq!(mapping.get_hotkey(&peer_id), Some(hotkey1.clone()));
assert_eq!(mapping.get_peer(&hotkey1), Some(peer_id));

// Overwrite with second hotkey
mapping.insert(peer_id, hotkey2.clone());
assert_eq!(mapping.get_hotkey(&peer_id), Some(hotkey2.clone()));
assert_eq!(mapping.get_peer(&hotkey2), Some(peer_id));

// Old hotkey should still point to the peer (due to current impl not cleaning old entry)
// This tests the actual behavior - hotkey_to_peer is not cleaned on overwrite
assert_eq!(mapping.get_peer(&hotkey1), Some(peer_id));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test documents potential data inconsistency in PeerMapping::insert().

The test correctly captures the current behavior where overwriting a peer's hotkey leaves a stale entry in hotkey_to_peer. This means get_peer(&old_hotkey) will return the peer even after their hotkey changed.

If this is unintended, the production insert() method should be updated to remove the old hotkey mapping:

Proposed fix for production code (lines 113-116)
 pub fn insert(&self, peer_id: PeerId, hotkey: Hotkey) {
+    // Remove old hotkey mapping if peer already exists
+    if let Some(old_hotkey) = self.peer_to_hotkey.read().get(&peer_id).cloned() {
+        self.hotkey_to_peer.write().remove(&old_hotkey);
+    }
     self.peer_to_hotkey.write().insert(peer_id, hotkey.clone());
     self.hotkey_to_peer.write().insert(hotkey, peer_id);
 }

If the current behavior is intentional, consider updating the comment to explain why stale mappings are acceptable.

🤖 Prompt for AI Agents
In `@crates/p2p-consensus/src/network.rs` around lines 962 - 982, The test shows
PeerMapping::insert currently leaves a stale entry in hotkey_to_peer when
overwriting a peer's hotkey; update PeerMapping::insert to first check
peer_to_hotkey for an existing hotkey for the given peer_id, and if found remove
that old mapping from hotkey_to_peer before inserting the new hotkey (ensure
peer_to_hotkey is then updated to the new hotkey and hotkey_to_peer points to
the peer_id). Alternatively, if stale mappings are intended, add a comment in
PeerMapping::insert explaining why hotkey_to_peer is not cleaned on overwrite.

@echobt echobt merged commit 31ac6bd into main Feb 3, 2026
7 checks passed
@echobt echobt deleted the test/comprehensive-unit-tests-1770142134 branch February 3, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant