From f9f4a75498523f671fecf6399b600d6953e85b0e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 05:26:40 +0700 Subject: [PATCH 1/2] test(drive): improve identity module test coverage Add 48 new tests covering previously untested areas of the drive identity module including partial identity fetches, balance operations, nonce and revision fetching, identity insertion error paths, and IdentityRootStructure enum conversions. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/drive/identity/balance/update.rs | 70 ++++ .../src/drive/identity/fetch/balance/mod.rs | 234 +++++++++++ .../rs-drive/src/drive/identity/fetch/mod.rs | 227 +++++++++++ .../src/drive/identity/fetch/nonce/mod.rs | 130 +++++++ .../identity/fetch/partial_identity/mod.rs | 367 ++++++++++++++++++ .../src/drive/identity/fetch/revision/mod.rs | 132 +++++++ .../identity/insert/add_new_identity/mod.rs | 84 ++++ packages/rs-drive/src/drive/identity/mod.rs | 157 ++++++++ 8 files changed, 1401 insertions(+) diff --git a/packages/rs-drive/src/drive/identity/balance/update.rs b/packages/rs-drive/src/drive/identity/balance/update.rs index aed30f9b248..aceadf267eb 100644 --- a/packages/rs-drive/src/drive/identity/balance/update.rs +++ b/packages/rs-drive/src/drive/identity/balance/update.rs @@ -932,4 +932,74 @@ mod tests { )); } } + + mod remove_from_identity_balance_errors { + use super::*; + use crate::error::identity::IdentityError; + use dpp::block::block_info::BlockInfo; + use dpp::version::PlatformVersion; + + #[test] + fn should_fail_to_remove_more_than_balance() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = create_test_identity(&drive, [0; 32], Some(15), None, platform_version) + .expect("expected to create an identity"); + + // Identity starts with balance from create_test_identity (which is 0 after creation + // since create_test_identity sets balance to 0). Let's add some balance first. + let added_balance = 100; + + drive + .add_to_identity_balance( + identity.id().to_buffer(), + added_balance, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add balance"); + + // Now try to remove more than available + let result = drive.remove_from_identity_balance( + identity.id().to_buffer(), + added_balance + 1, + &BlockInfo::default(), + true, + None, + platform_version, + None, + ); + + assert!(matches!( + result, + Err(Error::Identity(IdentityError::IdentityInsufficientBalance( + _ + ))) + )); + } + + #[test] + fn should_fail_to_remove_from_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let result = drive.remove_from_identity_balance( + [0; 32], + 100, + &BlockInfo::default(), + true, + None, + platform_version, + None, + ); + + assert!(matches!( + result, + Err(Error::Drive(DriveError::CorruptedCodeExecution(_))) + )); + } + } } diff --git a/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs b/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs index 742b0346e28..1bce18871d3 100644 --- a/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs @@ -1,3 +1,237 @@ mod fetch_identity_balance; mod fetch_identity_balance_include_debt; mod fetch_identity_negative_balance; + +#[cfg(feature = "server")] +#[cfg(test)] +mod tests { + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use crate::util::test_helpers::test_utils::identities::create_test_identity; + use dpp::block::block_info::BlockInfo; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::version::PlatformVersion; + + mod fetch_identity_balance { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let balance = drive + .fetch_identity_balance([0; 32], None, platform_version) + .expect("should not error"); + + assert!(balance.is_none()); + } + + #[test] + fn should_return_balance_for_existing_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + let expected_balance = identity.balance(); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let balance = drive + .fetch_identity_balance(identity.id().to_buffer(), None, platform_version) + .expect("should not error") + .expect("should have balance"); + + assert_eq!(balance, expected_balance); + } + + #[test] + fn should_return_balance_with_costs_estimated() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let block_info = BlockInfo::default(); + + // Estimated mode (apply=false) + let (balance, fee_result) = drive + .fetch_identity_balance_with_costs( + identity.id().to_buffer(), + &block_info, + false, + None, + platform_version, + ) + .expect("should return balance with costs"); + + // In estimated mode we get Some(0) for a nonexistent balance + // but we should still get a fee result + assert!(fee_result.processing_fee > 0); + // balance might be Some(0) in estimated mode or Some(actual) in apply mode + assert!(balance.is_some()); + } + } + + mod fetch_identity_balance_include_debt { + use super::*; + use crate::fees::op::LowLevelDriveOperation; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let balance = drive + .fetch_identity_balance_include_debt([0; 32], None, platform_version) + .expect("should not error"); + + assert!(balance.is_none()); + } + + #[test] + fn should_return_positive_balance_for_identity_with_funds() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = create_test_identity(&drive, [0; 32], Some(1), None, platform_version) + .expect("expected an identity"); + + let added_balance = 1000; + + drive + .add_to_identity_balance( + identity.id().to_buffer(), + added_balance, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("should add balance"); + + let balance = drive + .fetch_identity_balance_include_debt( + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("should not error") + .expect("should have balance"); + + assert_eq!(balance, added_balance as i64); + } + + #[test] + fn should_return_negative_balance_for_identity_with_debt() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = create_test_identity(&drive, [0; 32], Some(1), None, platform_version) + .expect("expected an identity"); + + let negative_amount: u64 = 500; + + // Set negative balance (debt) + let batch = vec![drive + .update_identity_negative_credit_operation( + identity.id().to_buffer(), + negative_amount, + platform_version, + ) + .expect("expected operation")]; + + let mut drive_operations: Vec = vec![]; + drive + .apply_batch_low_level_drive_operations( + None, + None, + batch, + &mut drive_operations, + &platform_version.drive, + ) + .expect("should apply batch"); + + let balance = drive + .fetch_identity_balance_include_debt( + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("should not error") + .expect("should have balance"); + + assert_eq!(balance, -(negative_amount as i64)); + } + } + + mod fetch_identity_negative_balance { + use super::*; + + #[test] + fn should_error_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let mut drive_operations = vec![]; + // For a non-existent identity, the path doesn't exist, so grove_get_raw + // returns an error (PathParentLayerNotFound) since the identity subtree + // doesn't exist at all. + let result = drive.fetch_identity_negative_balance_operations( + [0; 32], + true, + None, + &mut drive_operations, + platform_version, + ); + + assert!(result.is_err()); + } + + #[test] + fn should_return_zero_negative_balance_for_new_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = create_test_identity(&drive, [0; 32], Some(1), None, platform_version) + .expect("expected an identity"); + + let mut drive_operations = vec![]; + let negative_balance = drive + .fetch_identity_negative_balance_operations( + identity.id().to_buffer(), + true, + None, + &mut drive_operations, + platform_version, + ) + .expect("should not error") + .expect("should have negative balance"); + + assert_eq!(negative_balance, 0); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/fetch/mod.rs b/packages/rs-drive/src/drive/identity/fetch/mod.rs index 5426940c949..60a630fc866 100644 --- a/packages/rs-drive/src/drive/identity/fetch/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/mod.rs @@ -271,3 +271,230 @@ impl Drive { .collect() } } + +#[cfg(feature = "server")] +#[cfg(test)] +mod tests { + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::version::PlatformVersion; + + mod verify_all_identities_exist { + use super::*; + + #[test] + fn should_return_true_when_all_identities_exist() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identities: Vec = + Identity::random_identities(3, 3, Some(42), platform_version) + .expect("expected random identities"); + + let ids: Vec<[u8; 32]> = identities.iter().map(|i| i.id().to_buffer()).collect(); + + for identity in &identities { + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + } + + let result = drive + .verify_all_identities_exist(&ids, None, platform_version) + .expect("should not error"); + + assert!(result); + } + + #[test] + fn should_return_false_when_some_identities_missing() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + // Include a nonexistent identity id + let ids = vec![identity.id().to_buffer(), [0xff; 32]]; + + let result = drive + .verify_all_identities_exist(&ids, None, platform_version) + .expect("should not error"); + + assert!(!result); + } + } + + mod fetch_identities_balances { + use super::*; + + #[test] + fn should_fetch_balances_for_existing_identities() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identities: Vec = + Identity::random_identities(3, 3, Some(42), platform_version) + .expect("expected random identities"); + + for identity in &identities { + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + } + + let ids: Vec<[u8; 32]> = identities.iter().map(|i| i.id().to_buffer()).collect(); + + let balances = drive + .fetch_identities_balances(&ids, None, platform_version) + .expect("should fetch balances"); + + assert_eq!(balances.len(), 3); + for identity in &identities { + let balance = balances + .get(&identity.id().to_buffer()) + .expect("should have balance for identity"); + assert_eq!(*balance, identity.balance()); + } + } + } + + mod fetch_optional_identities_balances { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identities() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let ids = vec![identity.id().to_buffer(), [0xff; 32]]; + + let balances = drive + .fetch_optional_identities_balances(&ids, None, platform_version) + .expect("should fetch optional balances"); + + assert_eq!(balances.len(), 2); + assert_eq!( + *balances.get(&identity.id().to_buffer()).unwrap(), + Some(identity.balance()) + ); + assert_eq!(*balances.get(&[0xff; 32]).unwrap(), None); + } + } + + mod fetch_many_identity_balances_by_range { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn should_fetch_balances_by_range_ascending() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identities: Vec = + Identity::random_identities(5, 3, Some(42), platform_version) + .expect("expected random identities"); + + for identity in &identities { + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + } + + let balances: BTreeMap<[u8; 32], u64> = drive + .fetch_many_identity_balances_by_range::>( + None, + true, + 10, + None, + platform_version, + ) + .expect("should fetch balances by range"); + + assert_eq!(balances.len(), 5); + } + + #[test] + fn should_respect_limit() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identities: Vec = + Identity::random_identities(5, 3, Some(42), platform_version) + .expect("expected random identities"); + + for identity in &identities { + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + } + + let balances: BTreeMap<[u8; 32], u64> = drive + .fetch_many_identity_balances_by_range::>( + None, + true, + 2, + None, + platform_version, + ) + .expect("should fetch balances by range"); + + assert_eq!(balances.len(), 2); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/fetch/nonce/mod.rs b/packages/rs-drive/src/drive/identity/fetch/nonce/mod.rs index 6eea4fe165d..51f14fe855c 100644 --- a/packages/rs-drive/src/drive/identity/fetch/nonce/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/nonce/mod.rs @@ -1,2 +1,132 @@ mod fetch_identity_nonce; mod prove_identity_nonce; + +#[cfg(feature = "server")] +#[cfg(test)] +mod tests { + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::version::PlatformVersion; + + mod fetch_identity_nonce { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let nonce = drive + .fetch_identity_nonce([0; 32], true, None, platform_version) + .expect("should not error"); + + assert!(nonce.is_none()); + } + + #[test] + fn should_return_initial_nonce_for_new_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let nonce = drive + .fetch_identity_nonce(identity.id().to_buffer(), true, None, platform_version) + .expect("should not error") + .expect("should have nonce"); + + // New identity should have nonce 0 + assert_eq!(nonce, 0); + } + + #[test] + fn should_return_updated_nonce_after_merge() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + // Merge nonce to update it + drive + .merge_identity_nonce( + identity.id().to_buffer(), + 1, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to merge nonce"); + + let nonce = drive + .fetch_identity_nonce(identity.id().to_buffer(), true, None, platform_version) + .expect("should not error") + .expect("should have nonce"); + + // After merging nonce 1, the nonce value should contain 1 + // The nonce encoding includes missing revision bits, so just check it's nonzero + assert!(nonce > 0); + } + + #[test] + fn should_return_nonce_with_fees() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let block_info = BlockInfo::default(); + + let (nonce, fee_result) = drive + .fetch_identity_nonce_with_fees( + identity.id().to_buffer(), + &block_info, + true, + None, + platform_version, + ) + .expect("should not error"); + + assert_eq!(nonce, Some(0)); + assert!(fee_result.processing_fee > 0); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/fetch/partial_identity/mod.rs b/packages/rs-drive/src/drive/identity/fetch/partial_identity/mod.rs index 00576a74355..d511142620e 100644 --- a/packages/rs-drive/src/drive/identity/fetch/partial_identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/partial_identity/mod.rs @@ -3,3 +3,370 @@ mod fetch_identity_balance_with_keys_and_revision; mod fetch_identity_keys; mod fetch_identity_revision_with_keys; mod fetch_identity_with_balance; + +#[cfg(feature = "server")] +#[cfg(test)] +mod tests { + use crate::drive::identity::key::fetch::{IdentityKeysRequest, KeyRequestType}; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::version::PlatformVersion; + + mod fetch_identity_with_balance { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let result = drive + .fetch_identity_with_balance([0; 32], None, platform_version) + .expect("should not error"); + + assert!(result.is_none()); + } + + #[test] + fn should_return_partial_identity_with_balance() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + let expected_balance = identity.balance(); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let partial = drive + .fetch_identity_with_balance(identity.id().to_buffer(), None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.id, identity.id().clone()); + assert_eq!(partial.balance, Some(expected_balance)); + assert!(partial.loaded_public_keys.is_empty()); + assert!(partial.revision.is_none()); + } + + #[test] + fn should_return_none_with_cost_when_not_applying() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let (partial, fee_result) = drive + .fetch_identity_with_balance_with_cost([0; 32], false, None, platform_version) + .expect("should not error"); + + assert!(partial.is_none()); + assert!(fee_result.processing_fee > 0); + } + } + + mod fetch_identity_balance_with_keys { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let key_request = IdentityKeysRequest { + identity_id: [0; 32], + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let result = drive + .fetch_identity_balance_with_keys(key_request, None, platform_version) + .expect("should not error"); + + assert!(result.is_none()); + } + + #[test] + fn should_return_partial_identity_with_balance_and_keys() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let partial = drive + .fetch_identity_balance_with_keys(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.id, identity.id().clone()); + assert_eq!(partial.balance, Some(identity.balance())); + assert_eq!(partial.loaded_public_keys.len(), 3); + assert!(partial.revision.is_none()); + } + + #[test] + fn should_return_partial_identity_with_specific_keys() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(5, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![0, 1]), + limit: Some(2), + offset: None, + }; + + let partial = drive + .fetch_identity_balance_with_keys(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.loaded_public_keys.len(), 2); + assert!(partial.loaded_public_keys.contains_key(&0)); + assert!(partial.loaded_public_keys.contains_key(&1)); + } + + #[test] + fn should_track_not_found_keys() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + // Request key id 99 which does not exist + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::SpecificKeys(vec![0, 99]), + limit: Some(2), + offset: None, + }; + + let partial = drive + .fetch_identity_balance_with_keys(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.loaded_public_keys.len(), 1); + assert!(partial.loaded_public_keys.contains_key(&0)); + assert_eq!(partial.not_found_public_keys.len(), 1); + assert!(partial.not_found_public_keys.contains(&99)); + } + } + + mod fetch_identity_balance_with_keys_and_revision { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let key_request = IdentityKeysRequest { + identity_id: [0; 32], + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let result = drive + .fetch_identity_balance_with_keys_and_revision(key_request, None, platform_version) + .expect("should not error"); + + assert!(result.is_none()); + } + + #[test] + fn should_return_partial_identity_with_balance_keys_and_revision() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let partial = drive + .fetch_identity_balance_with_keys_and_revision(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.id, identity.id().clone()); + assert_eq!(partial.balance, Some(identity.balance())); + assert_eq!(partial.revision, Some(identity.revision())); + assert_eq!(partial.loaded_public_keys.len(), 3); + } + } + + mod fetch_identity_revision_with_keys { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let key_request = IdentityKeysRequest { + identity_id: [0; 32], + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let result = drive + .fetch_identity_revision_with_keys(key_request, None, platform_version) + .expect("should not error"); + + assert!(result.is_none()); + } + + #[test] + fn should_return_partial_identity_with_revision_and_keys() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let partial = drive + .fetch_identity_revision_with_keys(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.id, identity.id().clone()); + assert!(partial.balance.is_none()); + assert_eq!(partial.revision, Some(identity.revision())); + assert_eq!(partial.loaded_public_keys.len(), 3); + } + } + + mod fetch_identity_keys_as_partial_identity { + use super::*; + + #[test] + fn should_return_partial_identity_with_only_keys() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(5, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let key_request = IdentityKeysRequest { + identity_id: identity.id().to_buffer(), + request_type: KeyRequestType::AllKeys, + limit: None, + offset: None, + }; + + let partial = drive + .fetch_identity_keys_as_partial_identity(key_request, None, platform_version) + .expect("should not error") + .expect("should have partial identity"); + + assert_eq!(partial.id, identity.id().clone()); + assert!(partial.balance.is_none()); + assert!(partial.revision.is_none()); + assert_eq!(partial.loaded_public_keys.len(), 5); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/fetch/revision/mod.rs b/packages/rs-drive/src/drive/identity/fetch/revision/mod.rs index dfb1434a71c..cf9e4cb4388 100644 --- a/packages/rs-drive/src/drive/identity/fetch/revision/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/revision/mod.rs @@ -1 +1,133 @@ mod fetch_identity_revision; + +#[cfg(feature = "server")] +#[cfg(test)] +mod tests { + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::version::PlatformVersion; + + mod fetch_identity_revision { + use super::*; + + #[test] + fn should_return_none_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let revision = drive + .fetch_identity_revision([0; 32], true, None, platform_version) + .expect("should not error"); + + assert!(revision.is_none()); + } + + #[test] + fn should_return_initial_revision_for_new_identity() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + let expected_revision = identity.revision(); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let revision = drive + .fetch_identity_revision(identity.id().to_buffer(), true, None, platform_version) + .expect("should not error") + .expect("should have revision"); + + assert_eq!(revision, expected_revision); + } + + #[test] + fn should_return_updated_revision_after_update() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let new_revision = 5; + let block_info = BlockInfo::default(); + + drive + .update_identity_revision( + identity.id().to_buffer(), + new_revision, + &block_info, + true, + None, + platform_version, + None, + ) + .expect("expected to update revision"); + + let revision = drive + .fetch_identity_revision(identity.id().to_buffer(), true, None, platform_version) + .expect("should not error") + .expect("should have revision"); + + assert_eq!(revision, new_revision); + } + + #[test] + fn should_return_revision_with_fees() { + let drive = setup_drive_with_initial_state_structure(None); + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(42), platform_version) + .expect("expected a random identity"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add identity"); + + let block_info = BlockInfo::default(); + + let (revision, fee_result) = drive + .fetch_identity_revision_with_fees( + identity.id().to_buffer(), + &block_info, + true, + None, + platform_version, + ) + .expect("should not error"); + + assert_eq!(revision, Some(identity.revision())); + assert!(fee_result.processing_fee > 0); + } + } +} diff --git a/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs b/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs index acff3056ac6..87dce677697 100644 --- a/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs @@ -225,4 +225,88 @@ mod tests { } assert_eq!(fee_result, expected_fee_result); } + + #[test] + fn should_fail_to_insert_duplicate_non_masternode_identity() { + use crate::error::identity::IdentityError; + use crate::error::Error; + + let platform_version = PlatformVersion::latest(); + let drive = setup_drive(None); + + let transaction = drive.grove.start_transaction(); + + drive + .create_initial_state_structure(Some(&transaction), platform_version) + .expect("expected to create root tree successfully"); + + let identity = Identity::random_identity(5, Some(12345), platform_version) + .expect("expected a random identity"); + + // Insert the identity the first time + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + Some(&transaction), + platform_version, + ) + .expect("expected to insert identity"); + + // Inserting the same non-masternode identity again should fail + let result = drive.add_new_identity( + identity, + false, + &BlockInfo::default(), + true, + Some(&transaction), + platform_version, + ); + + assert!(matches!( + result, + Err(Error::Identity(IdentityError::IdentityAlreadyExists(_))) + )); + } + + #[test] + fn should_succeed_reinserting_masternode_identity() { + let platform_version = PlatformVersion::latest(); + let drive = setup_drive(None); + + let transaction = drive.grove.start_transaction(); + + drive + .create_initial_state_structure(Some(&transaction), platform_version) + .expect("expected to create root tree successfully"); + + let identity = Identity::random_identity(5, Some(12345), platform_version) + .expect("expected a random identity"); + + // Insert as masternode identity + drive + .add_new_identity( + identity.clone(), + true, + &BlockInfo::default(), + true, + Some(&transaction), + platform_version, + ) + .expect("expected to insert identity"); + + // Reinserting the same masternode identity should succeed (re-enable keys) + let result = drive.add_new_identity( + identity, + true, + &BlockInfo::default(), + true, + Some(&transaction), + platform_version, + ); + + assert!(result.is_ok()); + } } diff --git a/packages/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index c4bcc1802dd..7b744664c96 100644 --- a/packages/rs-drive/src/drive/identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/mod.rs @@ -417,3 +417,160 @@ impl From for &'static [u8; 1] { } } } + +#[cfg(test)] +mod tests { + use super::*; + + mod identity_root_structure { + use super::*; + + #[test] + fn should_convert_from_valid_u8_values() { + assert!(matches!( + IdentityRootStructure::try_from(192u8), + Ok(IdentityRootStructure::IdentityTreeRevision) + )); + assert!(matches!( + IdentityRootStructure::try_from(64u8), + Ok(IdentityRootStructure::IdentityTreeNonce) + )); + assert!(matches!( + IdentityRootStructure::try_from(128u8), + Ok(IdentityRootStructure::IdentityTreeKeys) + )); + assert!(matches!( + IdentityRootStructure::try_from(160u8), + Ok(IdentityRootStructure::IdentityTreeKeyReferences) + )); + assert!(matches!( + IdentityRootStructure::try_from(96u8), + Ok(IdentityRootStructure::IdentityTreeNegativeCredit) + )); + assert!(matches!( + IdentityRootStructure::try_from(32u8), + Ok(IdentityRootStructure::IdentityContractInfo) + )); + } + + #[test] + fn should_fail_for_invalid_u8_value() { + let result = IdentityRootStructure::try_from(0u8); + assert!(result.is_err()); + let result = IdentityRootStructure::try_from(1u8); + assert!(result.is_err()); + let result = IdentityRootStructure::try_from(255u8); + assert!(result.is_err()); + } + + #[test] + fn should_display_correct_names() { + assert_eq!( + format!("{}", IdentityRootStructure::IdentityTreeRevision), + "Revision" + ); + assert_eq!( + format!("{}", IdentityRootStructure::IdentityTreeNonce), + "Nonce" + ); + assert_eq!( + format!("{}", IdentityRootStructure::IdentityTreeKeys), + "IdentityKeys" + ); + assert_eq!( + format!("{}", IdentityRootStructure::IdentityTreeKeyReferences), + "IdentityKeyReferences" + ); + assert_eq!( + format!("{}", IdentityRootStructure::IdentityTreeNegativeCredit), + "NegativeCredit" + ); + assert_eq!( + format!("{}", IdentityRootStructure::IdentityContractInfo), + "ContractInfo" + ); + } + + #[test] + fn should_convert_to_u8() { + let val: u8 = IdentityRootStructure::IdentityTreeRevision.into(); + assert_eq!(val, 192); + let val: u8 = IdentityRootStructure::IdentityTreeNonce.into(); + assert_eq!(val, 64); + let val: u8 = IdentityRootStructure::IdentityTreeKeys.into(); + assert_eq!(val, 128); + } + + #[test] + fn should_convert_to_u8_array() { + let val: [u8; 1] = IdentityRootStructure::IdentityTreeRevision.into(); + assert_eq!(val, [192]); + let val: [u8; 1] = IdentityRootStructure::IdentityTreeNonce.into(); + assert_eq!(val, [64]); + } + + #[test] + fn should_convert_to_static_u8_array_ref() { + let val: &'static [u8; 1] = IdentityRootStructure::IdentityTreeRevision.into(); + assert_eq!(val, &[192]); + let val: &'static [u8; 1] = IdentityRootStructure::IdentityContractInfo.into(); + assert_eq!(val, &[32]); + } + } + + mod identity_path_functions { + use super::*; + + #[test] + fn should_create_correct_identity_path() { + let id = [1u8; 32]; + let path = identity_path(&id); + assert_eq!(path.len(), 2); + assert_eq!(path[1], &id); + } + + #[test] + fn should_create_correct_identity_path_vec() { + let id = [2u8; 32]; + let path = identity_path_vec(&id); + assert_eq!(path.len(), 2); + assert_eq!(path[1], id.to_vec()); + } + + #[test] + fn should_create_correct_key_tree_path() { + let id = [3u8; 32]; + let path = identity_key_tree_path(&id); + assert_eq!(path.len(), 3); + assert_eq!(path[1], &id); + assert_eq!(path[2], &[IdentityRootStructure::IdentityTreeKeys as u8]); + } + + #[test] + fn should_create_correct_key_tree_path_vec() { + let id = [4u8; 32]; + let path = identity_key_tree_path_vec(&id); + assert_eq!(path.len(), 3); + assert_eq!(path[1], id.to_vec()); + assert_eq!(path[2], vec![IdentityRootStructure::IdentityTreeKeys as u8]); + } + + #[test] + fn should_create_correct_contract_info_root_path() { + let id = [5u8; 32]; + let path = identity_contract_info_root_path(&id); + assert_eq!(path.len(), 3); + assert_eq!(path[1], &id); + } + + #[test] + fn should_create_correct_contract_info_group_path() { + let id = [6u8; 32]; + let group_id = [7u8; 32]; + let path = identity_contract_info_group_path(&id, &group_id); + assert_eq!(path.len(), 4); + assert_eq!(path[1], &id); + assert_eq!(path[3], &group_id); + } + } +} From a9303fdab37ba6f6a95c99de05b156a5fc94d44d Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 10:41:02 +0700 Subject: [PATCH 2/2] fix(test): address CodeRabbit review feedback - Gate test module with feature flags to prevent compilation errors in minimal feature builds - Add ascending order assertion to range balance fetch test - Fix estimated mode balance assertion to expect exact stateless value - Add key verification to masternode reinsertion test Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/drive/identity/fetch/balance/mod.rs | 8 ++++---- packages/rs-drive/src/drive/identity/fetch/mod.rs | 4 ++++ .../drive/identity/insert/add_new_identity/mod.rs | 13 ++++++++++++- packages/rs-drive/src/drive/identity/mod.rs | 3 ++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs b/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs index 1bce18871d3..81bf17e6978 100644 --- a/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/balance/mod.rs @@ -88,11 +88,11 @@ mod tests { ) .expect("should return balance with costs"); - // In estimated mode we get Some(0) for a nonexistent balance - // but we should still get a fee result + // In estimated (stateless) mode, the balance query does not read + // from the database. It returns Some(0) as a placeholder value + // while computing estimated costs. assert!(fee_result.processing_fee > 0); - // balance might be Some(0) in estimated mode or Some(actual) in apply mode - assert!(balance.is_some()); + assert_eq!(balance, Some(0)); } } diff --git a/packages/rs-drive/src/drive/identity/fetch/mod.rs b/packages/rs-drive/src/drive/identity/fetch/mod.rs index 60a630fc866..b83d851790d 100644 --- a/packages/rs-drive/src/drive/identity/fetch/mod.rs +++ b/packages/rs-drive/src/drive/identity/fetch/mod.rs @@ -460,6 +460,10 @@ mod tests { .expect("should fetch balances by range"); assert_eq!(balances.len(), 5); + + // BTreeMap keys are always sorted, confirming ascending order + let keys: Vec<[u8; 32]> = balances.keys().copied().collect(); + assert!(keys.windows(2).all(|w| w[0] <= w[1])); } #[test] diff --git a/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs b/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs index 87dce677697..793882f2632 100644 --- a/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/insert/add_new_identity/mod.rs @@ -299,7 +299,7 @@ mod tests { // Reinserting the same masternode identity should succeed (re-enable keys) let result = drive.add_new_identity( - identity, + identity.clone(), true, &BlockInfo::default(), true, @@ -308,5 +308,16 @@ mod tests { ); assert!(result.is_ok()); + + // Verify keys are still present after reinsertion + let fetched_keys = drive + .fetch_all_identity_keys( + identity.id().to_buffer(), + Some(&transaction), + platform_version, + ) + .expect("expected to fetch keys"); + + assert_eq!(fetched_keys.len(), 5); } } diff --git a/packages/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index 7b744664c96..4c209271c31 100644 --- a/packages/rs-drive/src/drive/identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/mod.rs @@ -418,7 +418,7 @@ impl From for &'static [u8; 1] { } } -#[cfg(test)] +#[cfg(all(test, any(feature = "server", feature = "verify")))] mod tests { use super::*; @@ -555,6 +555,7 @@ mod tests { assert_eq!(path[2], vec![IdentityRootStructure::IdentityTreeKeys as u8]); } + #[cfg(feature = "server")] #[test] fn should_create_correct_contract_info_root_path() { let id = [5u8; 32];