diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs index 2aaee552d74..a753758d280 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs @@ -249,3 +249,223 @@ impl TryFrom<&InstantAssetLockProof> for RawInstantLockProof { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::fixtures::raw_instant_asset_lock_proof_fixture; + + // --------------------------------------------------------------- + // Default + // --------------------------------------------------------------- + + #[test] + fn test_default_instant_asset_lock_proof() { + let proof = InstantAssetLockProof::default(); + assert_eq!(proof.output_index(), 0); + assert_eq!(proof.transaction().version, 0); + assert_eq!(proof.transaction().lock_time, 0); + assert_eq!(proof.transaction().input.len(), 1); + assert_eq!(proof.transaction().output.len(), 1); + assert!(proof.transaction().special_transaction_payload.is_none()); + } + + // --------------------------------------------------------------- + // Constructor and accessors + // --------------------------------------------------------------- + + #[test] + fn test_new_stores_fields_correctly() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + assert_eq!(proof.output_index(), 0); + // Verify the instant lock and transaction are accessible + let _il = proof.instant_lock(); + let _tx = proof.transaction(); + } + + #[test] + fn test_instant_lock_accessor() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let il = proof.instant_lock(); + assert_eq!(il.version, 1); + assert_eq!(il.inputs.len(), 1); + } + + #[test] + fn test_transaction_accessor() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let tx = proof.transaction(); + assert_eq!(tx.version, 0); + assert_eq!(tx.lock_time, 0); + assert_eq!(tx.input.len(), 1); + } + + #[test] + fn test_output_index_accessor() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + assert_eq!(proof.output_index(), 0); + } + + #[test] + fn test_output_index_with_custom_value() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + // Create a new proof with a different output_index + let custom_proof = + InstantAssetLockProof::new(proof.instant_lock.clone(), proof.transaction.clone(), 5); + assert_eq!(custom_proof.output_index(), 5); + } + + // --------------------------------------------------------------- + // output() + // --------------------------------------------------------------- + + #[test] + fn test_output_returns_some_for_valid_asset_lock_transaction() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + // The fixture creates a transaction with AssetLockPayloadType containing one credit output + let output = proof.output(); + assert!(output.is_some()); + } + + #[test] + fn test_output_returns_none_for_default_transaction() { + let proof = InstantAssetLockProof::default(); + // Default transaction has no special_transaction_payload + assert!(proof.output().is_none()); + } + + #[test] + fn test_output_returns_none_for_out_of_range_index() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + // Fixture has output_index 0 and only 1 credit output, so index 99 should be out of range + let modified_proof = + InstantAssetLockProof::new(proof.instant_lock.clone(), proof.transaction.clone(), 99); + assert!(modified_proof.output().is_none()); + } + + // --------------------------------------------------------------- + // out_point() + // --------------------------------------------------------------- + + #[test] + fn test_out_point_returns_some_for_valid_proof() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let outpoint = proof.out_point(); + assert!(outpoint.is_some()); + let outpoint = outpoint.unwrap(); + assert_eq!(outpoint.txid, proof.transaction.txid()); + assert_eq!(outpoint.vout, 0); + } + + #[test] + fn test_out_point_returns_none_for_default() { + let proof = InstantAssetLockProof::default(); + assert!(proof.out_point().is_none()); + } + + // --------------------------------------------------------------- + // create_identifier() + // --------------------------------------------------------------- + + #[test] + fn test_create_identifier_succeeds_for_valid_proof() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let result = proof.create_identifier(); + assert!(result.is_ok()); + let identifier = result.unwrap(); + // Identifier should be 32 bytes + assert_eq!(identifier.as_slice().len(), 32); + } + + #[test] + fn test_create_identifier_deterministic() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let id1 = proof.create_identifier().unwrap(); + let id2 = proof.create_identifier().unwrap(); + assert_eq!(id1, id2); + } + + #[test] + fn test_create_identifier_fails_for_default() { + let proof = InstantAssetLockProof::default(); + let result = proof.create_identifier(); + assert!(result.is_err()); + } + + // --------------------------------------------------------------- + // to_object() + // --------------------------------------------------------------- + + #[test] + fn test_to_object_succeeds() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let result = proof.to_object(); + assert!(result.is_ok()); + } + + #[test] + fn test_to_cleaned_object_succeeds() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let result = proof.to_cleaned_object(); + assert!(result.is_ok()); + } + + // --------------------------------------------------------------- + // RawInstantLockProof round-trip + // --------------------------------------------------------------- + + #[test] + fn test_raw_instant_lock_proof_round_trip() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let raw = RawInstantLockProof::try_from(&proof).unwrap(); + let recovered = InstantAssetLockProof::try_from(raw).unwrap(); + + assert_eq!(recovered.output_index, proof.output_index); + assert_eq!(recovered.instant_lock, proof.instant_lock); + assert_eq!(recovered.transaction.txid(), proof.transaction.txid()); + } + + #[test] + fn test_raw_instant_lock_proof_preserves_output_index() { + let base = raw_instant_asset_lock_proof_fixture(None, None); + let proof = + InstantAssetLockProof::new(base.instant_lock.clone(), base.transaction.clone(), 7); + let raw = RawInstantLockProof::try_from(&proof).unwrap(); + assert_eq!(raw.output_index, 7); + let recovered = InstantAssetLockProof::try_from(raw).unwrap(); + assert_eq!(recovered.output_index(), 7); + } + + // --------------------------------------------------------------- + // Eq / Clone + // --------------------------------------------------------------- + + #[test] + fn test_clone_equals_original() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let cloned = proof.clone(); + assert_eq!(proof, cloned); + } + + #[test] + fn test_different_output_index_not_equal() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let mut modified = proof.clone(); + modified.output_index = 1; + assert_ne!(proof, modified); + } + + // --------------------------------------------------------------- + // TryFrom + // --------------------------------------------------------------- + + #[test] + fn test_try_from_value_round_trip() { + let proof = raw_instant_asset_lock_proof_fixture(None, None); + let value = proof.to_object().unwrap(); + let recovered = InstantAssetLockProof::try_from(value).unwrap(); + assert_eq!(proof.output_index, recovered.output_index); + assert_eq!(proof.instant_lock, recovered.instant_lock); + assert_eq!(proof.transaction.txid(), recovered.transaction.txid()); + } +} diff --git a/packages/rs-drive/src/drive/votes/paths.rs b/packages/rs-drive/src/drive/votes/paths.rs index 0b15ab80001..c47a36a5551 100644 --- a/packages/rs-drive/src/drive/votes/paths.rs +++ b/packages/rs-drive/src/drive/votes/paths.rs @@ -530,3 +530,451 @@ pub fn vote_contested_resource_identity_votes_tree_path_for_identity_vec( identity_id.to_vec(), ] } + +#[cfg(test)] +mod tests { + use super::*; + + const VOTES_BYTE: u8 = RootTree::Votes as u8; + + // --------------------------------------------------------------- + // vote_root_path / vote_root_path_vec + // --------------------------------------------------------------- + + #[test] + fn test_vote_root_path_has_single_element() { + let path = vote_root_path(); + assert_eq!(path.len(), 1); + assert_eq!(path[0], &[VOTES_BYTE]); + } + + #[test] + fn test_vote_root_path_vec_matches_slice_form() { + let path_vec = vote_root_path_vec(); + let path = vote_root_path(); + assert_eq!(path_vec.len(), path.len()); + assert_eq!(path_vec[0], path[0]); + } + + // --------------------------------------------------------------- + // vote_decisions_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_vote_decisions_tree_path_structure() { + let path = vote_decisions_tree_path(); + assert_eq!(path.len(), 2); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[VOTE_DECISIONS_TREE_KEY as u8]); + } + + #[test] + fn test_vote_decisions_tree_path_vec_matches_slice_form() { + let path_vec = vote_decisions_tree_path_vec(); + let path = vote_decisions_tree_path(); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_contested_resource_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_vote_contested_resource_tree_path_structure() { + let path = vote_contested_resource_tree_path(); + assert_eq!(path.len(), 2); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + } + + #[test] + fn test_vote_contested_resource_tree_path_vec_matches_slice_form() { + let path_vec = vote_contested_resource_tree_path_vec(); + let path = vote_contested_resource_tree_path(); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_end_date_queries_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_vote_end_date_queries_tree_path_structure() { + let path = vote_end_date_queries_tree_path(); + assert_eq!(path.len(), 2); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[END_DATE_QUERIES_TREE_KEY as u8]); + } + + #[test] + fn test_vote_end_date_queries_tree_path_vec_matches_slice_form() { + let path_vec = vote_end_date_queries_tree_path_vec(); + let path = vote_end_date_queries_tree_path(); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_contested_resource_active_polls_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_vote_contested_resource_active_polls_tree_path_structure() { + let path = vote_contested_resource_active_polls_tree_path(); + assert_eq!(path.len(), 3); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + assert_eq!(path[2], &[ACTIVE_POLLS_TREE_KEY as u8]); + } + + #[test] + fn test_vote_contested_resource_active_polls_tree_path_vec_matches_slice_form() { + let path_vec = vote_contested_resource_active_polls_tree_path_vec(); + let path = vote_contested_resource_active_polls_tree_path(); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_contested_resource_active_polls_contract_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_active_polls_contract_tree_path_includes_contract_id() { + let contract_id: [u8; 32] = [42u8; 32]; + let path = vote_contested_resource_active_polls_contract_tree_path(&contract_id); + assert_eq!(path.len(), 4); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + assert_eq!(path[2], &[ACTIVE_POLLS_TREE_KEY as u8]); + assert_eq!(path[3], &contract_id); + } + + #[test] + fn test_active_polls_contract_tree_path_vec_matches_slice_form() { + let contract_id: [u8; 32] = [7u8; 32]; + let path_vec = vote_contested_resource_active_polls_contract_tree_path_vec(&contract_id); + let path = vote_contested_resource_active_polls_contract_tree_path(&contract_id); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + #[test] + fn test_active_polls_contract_tree_path_different_ids_differ() { + let id_a: [u8; 32] = [1u8; 32]; + let id_b: [u8; 32] = [2u8; 32]; + let path_a = vote_contested_resource_active_polls_contract_tree_path(&id_a); + let path_b = vote_contested_resource_active_polls_contract_tree_path(&id_b); + // First three path elements should be the same + assert_eq!(path_a[0], path_b[0]); + assert_eq!(path_a[1], path_b[1]); + assert_eq!(path_a[2], path_b[2]); + // Contract ID (4th element) should differ + assert_ne!(path_a[3], path_b[3]); + } + + // --------------------------------------------------------------- + // vote_contested_resource_active_polls_contract_document_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_active_polls_contract_document_tree_path_structure() { + let contract_id: [u8; 32] = [10u8; 32]; + let doc_type_name = "domain"; + let path = vote_contested_resource_active_polls_contract_document_tree_path( + &contract_id, + doc_type_name, + ); + assert_eq!(path.len(), 5); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + assert_eq!(path[2], &[ACTIVE_POLLS_TREE_KEY as u8]); + assert_eq!(path[3], &contract_id); + assert_eq!(path[4], doc_type_name.as_bytes()); + } + + #[test] + fn test_active_polls_contract_document_tree_path_vec_matches_slice_form() { + let contract_id: [u8; 32] = [99u8; 32]; + let doc_type_name = "preorder"; + let path_vec = vote_contested_resource_active_polls_contract_document_tree_path_vec( + &contract_id, + doc_type_name, + ); + let path = vote_contested_resource_active_polls_contract_document_tree_path( + &contract_id, + doc_type_name, + ); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + #[test] + fn test_active_polls_contract_document_tree_path_different_doc_types() { + let contract_id: [u8; 32] = [5u8; 32]; + let path_a = + vote_contested_resource_active_polls_contract_document_tree_path(&contract_id, "alpha"); + let path_b = + vote_contested_resource_active_polls_contract_document_tree_path(&contract_id, "beta"); + // Same prefix + assert_eq!(path_a[..4], path_b[..4]); + // Different document type name + assert_ne!(path_a[4], path_b[4]); + } + + // --------------------------------------------------------------- + // vote_contested_resource_contract_documents_storage_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_contract_documents_storage_path_has_storage_key() { + let contract_id: [u8; 32] = [11u8; 32]; + let doc_type_name = "note"; + let path = + vote_contested_resource_contract_documents_storage_path(&contract_id, doc_type_name); + assert_eq!(path.len(), 6); + assert_eq!(path[5], &[CONTESTED_DOCUMENT_STORAGE_TREE_KEY]); + } + + #[test] + fn test_contract_documents_storage_path_vec_matches_slice_form() { + let contract_id: [u8; 32] = [11u8; 32]; + let doc_type_name = "note"; + let path_vec = vote_contested_resource_contract_documents_storage_path_vec( + &contract_id, + doc_type_name, + ); + let path = + vote_contested_resource_contract_documents_storage_path(&contract_id, doc_type_name); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_contested_resource_contract_documents_indexes_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_contract_documents_indexes_path_has_indexes_key() { + let contract_id: [u8; 32] = [22u8; 32]; + let doc_type_name = "profile"; + let path = + vote_contested_resource_contract_documents_indexes_path(&contract_id, doc_type_name); + assert_eq!(path.len(), 6); + assert_eq!(path[5], &[CONTESTED_DOCUMENT_INDEXES_TREE_KEY]); + } + + #[test] + fn test_contract_documents_indexes_path_vec_matches_slice_form() { + let contract_id: [u8; 32] = [22u8; 32]; + let doc_type_name = "profile"; + let path_vec = vote_contested_resource_contract_documents_indexes_path_vec( + &contract_id, + doc_type_name, + ); + let path = + vote_contested_resource_contract_documents_indexes_path(&contract_id, doc_type_name); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + #[test] + fn test_storage_and_indexes_paths_differ_only_in_last_element() { + let contract_id: [u8; 32] = [33u8; 32]; + let doc_type_name = "record"; + let storage_path = + vote_contested_resource_contract_documents_storage_path(&contract_id, doc_type_name); + let indexes_path = + vote_contested_resource_contract_documents_indexes_path(&contract_id, doc_type_name); + // The first five elements should be identical + assert_eq!(storage_path[..5], indexes_path[..5]); + // The 6th element (tree key) should differ + assert_ne!(storage_path[5], indexes_path[5]); + assert_eq!(storage_path[5], &[CONTESTED_DOCUMENT_STORAGE_TREE_KEY]); + assert_eq!(indexes_path[5], &[CONTESTED_DOCUMENT_INDEXES_TREE_KEY]); + } + + // --------------------------------------------------------------- + // vote_contested_resource_end_date_queries_at_time_tree_path_vec + // --------------------------------------------------------------- + + #[test] + fn test_end_date_queries_at_time_tree_path_vec_structure() { + let time: TimestampMillis = 1_700_000_000_000; + let path = vote_contested_resource_end_date_queries_at_time_tree_path_vec(time); + assert_eq!(path.len(), 3); + assert_eq!(path[0], vec![VOTES_BYTE]); + assert_eq!(path[1], vec![END_DATE_QUERIES_TREE_KEY as u8]); + assert_eq!(path[2], encode_u64(time)); + } + + #[test] + fn test_end_date_queries_different_times_produce_different_paths() { + let path_a = vote_contested_resource_end_date_queries_at_time_tree_path_vec(1_000_000); + let path_b = vote_contested_resource_end_date_queries_at_time_tree_path_vec(2_000_000); + // First two elements should be identical + assert_eq!(path_a[0], path_b[0]); + assert_eq!(path_a[1], path_b[1]); + // Time-encoded element should differ + assert_ne!(path_a[2], path_b[2]); + } + + #[test] + fn test_end_date_queries_at_time_zero() { + let path = vote_contested_resource_end_date_queries_at_time_tree_path_vec(0); + assert_eq!(path.len(), 3); + assert_eq!(path[2], encode_u64(0)); + } + + #[test] + fn test_end_date_queries_at_time_max() { + let path = vote_contested_resource_end_date_queries_at_time_tree_path_vec(u64::MAX); + assert_eq!(path.len(), 3); + assert_eq!(path[2], encode_u64(u64::MAX)); + } + + // --------------------------------------------------------------- + // vote_contested_resource_identity_votes_tree_path / _vec + // --------------------------------------------------------------- + + #[test] + fn test_identity_votes_tree_path_structure() { + let path = vote_contested_resource_identity_votes_tree_path(); + assert_eq!(path.len(), 3); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + assert_eq!(path[2], &[IDENTITY_VOTES_TREE_KEY as u8]); + } + + #[test] + fn test_identity_votes_tree_path_vec_matches_slice_form() { + let path_vec = vote_contested_resource_identity_votes_tree_path_vec(); + let path = vote_contested_resource_identity_votes_tree_path(); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + // --------------------------------------------------------------- + // vote_contested_resource_identity_votes_tree_path_for_identity / _vec + // --------------------------------------------------------------- + + #[test] + fn test_identity_votes_tree_path_for_identity_includes_id() { + let identity_id: [u8; 32] = [55u8; 32]; + let path = vote_contested_resource_identity_votes_tree_path_for_identity(&identity_id); + assert_eq!(path.len(), 4); + assert_eq!(path[0], &[VOTES_BYTE]); + assert_eq!(path[1], &[CONTESTED_RESOURCE_TREE_KEY as u8]); + assert_eq!(path[2], &[IDENTITY_VOTES_TREE_KEY as u8]); + assert_eq!(path[3], &identity_id); + } + + #[test] + fn test_identity_votes_tree_path_for_identity_vec_matches_slice_form() { + let identity_id: [u8; 32] = [88u8; 32]; + let path_vec = + vote_contested_resource_identity_votes_tree_path_for_identity_vec(&identity_id); + let path = vote_contested_resource_identity_votes_tree_path_for_identity(&identity_id); + assert_eq!(path_vec.len(), path.len()); + for (vec_elem, slice_elem) in path_vec.iter().zip(path.iter()) { + assert_eq!(vec_elem.as_slice(), *slice_elem); + } + } + + #[test] + fn test_identity_votes_tree_path_for_identity_different_ids_differ() { + let id_a: [u8; 32] = [0u8; 32]; + let id_b: [u8; 32] = [255u8; 32]; + let path_a = vote_contested_resource_identity_votes_tree_path_for_identity(&id_a); + let path_b = vote_contested_resource_identity_votes_tree_path_for_identity(&id_b); + // Prefix should be the same + assert_eq!(path_a[..3], path_b[..3]); + // Identity ID should differ + assert_ne!(path_a[3], path_b[3]); + } + + // --------------------------------------------------------------- + // Constant key values + // --------------------------------------------------------------- + + #[test] + fn test_resource_stored_info_key_is_all_zeroes() { + assert_eq!(RESOURCE_STORED_INFO_KEY_U8_32, [0u8; 32]); + } + + #[test] + fn test_resource_abstain_vote_key_is_one() { + let mut expected = [0u8; 32]; + expected[31] = 1; + assert_eq!(RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32, expected); + } + + #[test] + fn test_resource_lock_vote_key_is_two() { + let mut expected = [0u8; 32]; + expected[31] = 2; + assert_eq!(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32, expected); + } + + #[test] + fn test_tree_key_constants_are_distinct() { + assert_ne!(VOTE_DECISIONS_TREE_KEY, CONTESTED_RESOURCE_TREE_KEY); + assert_ne!(VOTE_DECISIONS_TREE_KEY, END_DATE_QUERIES_TREE_KEY); + assert_ne!(CONTESTED_RESOURCE_TREE_KEY, END_DATE_QUERIES_TREE_KEY); + assert_ne!(ACTIVE_POLLS_TREE_KEY, IDENTITY_VOTES_TREE_KEY); + } + + #[test] + fn test_document_tree_keys_are_distinct() { + assert_ne!( + CONTESTED_DOCUMENT_STORAGE_TREE_KEY, + CONTESTED_DOCUMENT_INDEXES_TREE_KEY + ); + } + + // --------------------------------------------------------------- + // Paths share a common prefix where expected + // --------------------------------------------------------------- + + #[test] + fn test_active_polls_and_identity_votes_share_contested_resource_prefix() { + let active = vote_contested_resource_active_polls_tree_path(); + let identity = vote_contested_resource_identity_votes_tree_path(); + // Both share votes root + contested resource key + assert_eq!(active[0], identity[0]); + assert_eq!(active[1], identity[1]); + // But diverge at third element + assert_ne!(active[2], identity[2]); + } + + #[test] + fn test_active_polls_path_is_prefix_of_contract_path() { + let polls_path = vote_contested_resource_active_polls_tree_path(); + let contract_id: [u8; 32] = [99u8; 32]; + let contract_path = vote_contested_resource_active_polls_contract_tree_path(&contract_id); + // Contract path starts with the same elements as polls path + for (i, &elem) in polls_path.iter().enumerate() { + assert_eq!(contract_path[i], elem); + } + } +} diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/token.rs b/packages/rs-drive/src/util/batch/drive_op_batch/token.rs index dd21c08b9cf..3af55627fd2 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/token.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/token.rs @@ -310,3 +310,375 @@ impl DriveLowLevelOperationConverter for TokenOperationType { } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_token_id() -> Identifier { + Identifier::new([1u8; 32]) + } + + fn test_identity_id() -> Identifier { + Identifier::new([2u8; 32]) + } + + fn test_recipient_id() -> Identifier { + Identifier::new([3u8; 32]) + } + + // --------------------------------------------------------------- + // TokenBurn construction + // --------------------------------------------------------------- + + #[test] + fn test_token_burn_construction() { + let op = TokenOperationType::TokenBurn { + token_id: test_token_id(), + identity_balance_holder_id: test_identity_id(), + burn_amount: 500, + }; + + match op { + TokenOperationType::TokenBurn { + token_id, + identity_balance_holder_id, + burn_amount, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(identity_balance_holder_id, test_identity_id()); + assert_eq!(burn_amount, 500); + } + _ => panic!("expected TokenBurn variant"), + } + } + + // --------------------------------------------------------------- + // TokenMint construction + // --------------------------------------------------------------- + + #[test] + fn test_token_mint_construction() { + let op = TokenOperationType::TokenMint { + token_id: test_token_id(), + identity_balance_holder_id: test_identity_id(), + mint_amount: 1_000_000, + allow_first_mint: true, + allow_saturation: false, + }; + + match op { + TokenOperationType::TokenMint { + token_id, + identity_balance_holder_id, + mint_amount, + allow_first_mint, + allow_saturation, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(identity_balance_holder_id, test_identity_id()); + assert_eq!(mint_amount, 1_000_000); + assert!(allow_first_mint); + assert!(!allow_saturation); + } + _ => panic!("expected TokenMint variant"), + } + } + + #[test] + fn test_token_mint_with_saturation_enabled() { + let op = TokenOperationType::TokenMint { + token_id: test_token_id(), + identity_balance_holder_id: test_identity_id(), + mint_amount: u64::MAX, + allow_first_mint: false, + allow_saturation: true, + }; + + match op { + TokenOperationType::TokenMint { + allow_saturation, + allow_first_mint, + mint_amount, + .. + } => { + assert!(allow_saturation); + assert!(!allow_first_mint); + assert_eq!(mint_amount, u64::MAX); + } + _ => panic!("expected TokenMint variant"), + } + } + + // --------------------------------------------------------------- + // TokenMintMany construction + // --------------------------------------------------------------- + + #[test] + fn test_token_mint_many_construction() { + let recipients = vec![ + (Identifier::new([10u8; 32]), 50), + (Identifier::new([11u8; 32]), 30), + (Identifier::new([12u8; 32]), 20), + ]; + let op = TokenOperationType::TokenMintMany { + token_id: test_token_id(), + recipients: recipients.clone(), + mint_amount: 100_000, + allow_first_mint: true, + }; + + match op { + TokenOperationType::TokenMintMany { + token_id, + recipients: r, + mint_amount, + allow_first_mint, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(r.len(), 3); + assert_eq!(r[0].1, 50); + assert_eq!(r[1].1, 30); + assert_eq!(r[2].1, 20); + assert_eq!(mint_amount, 100_000); + assert!(allow_first_mint); + } + _ => panic!("expected TokenMintMany variant"), + } + } + + // --------------------------------------------------------------- + // TokenTransfer construction + // --------------------------------------------------------------- + + #[test] + fn test_token_transfer_construction() { + let sender = Identifier::new([4u8; 32]); + let recipient = Identifier::new([5u8; 32]); + let op = TokenOperationType::TokenTransfer { + token_id: test_token_id(), + sender_id: sender, + recipient_id: recipient, + amount: 250, + }; + + match op { + TokenOperationType::TokenTransfer { + token_id, + sender_id, + recipient_id, + amount, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(sender_id, Identifier::new([4u8; 32])); + assert_eq!(recipient_id, Identifier::new([5u8; 32])); + assert_eq!(amount, 250); + } + _ => panic!("expected TokenTransfer variant"), + } + } + + // --------------------------------------------------------------- + // TokenFreeze / TokenUnfreeze construction + // --------------------------------------------------------------- + + #[test] + fn test_token_freeze_construction() { + let frozen = Identifier::new([6u8; 32]); + let op = TokenOperationType::TokenFreeze { + token_id: test_token_id(), + frozen_identity_id: frozen, + }; + + match op { + TokenOperationType::TokenFreeze { + token_id, + frozen_identity_id, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(frozen_identity_id, Identifier::new([6u8; 32])); + } + _ => panic!("expected TokenFreeze variant"), + } + } + + #[test] + fn test_token_unfreeze_construction() { + let frozen = Identifier::new([7u8; 32]); + let op = TokenOperationType::TokenUnfreeze { + token_id: test_token_id(), + frozen_identity_id: frozen, + }; + + match op { + TokenOperationType::TokenUnfreeze { + token_id, + frozen_identity_id, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(frozen_identity_id, Identifier::new([7u8; 32])); + } + _ => panic!("expected TokenUnfreeze variant"), + } + } + + // --------------------------------------------------------------- + // TokenSetPriceForDirectPurchase construction + // --------------------------------------------------------------- + + #[test] + fn test_token_set_price_none() { + let op = TokenOperationType::TokenSetPriceForDirectPurchase { + token_id: test_token_id(), + price: None, + }; + + match op { + TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => { + assert_eq!(token_id, test_token_id()); + assert!(price.is_none()); + } + _ => panic!("expected TokenSetPriceForDirectPurchase variant"), + } + } + + // --------------------------------------------------------------- + // TokenMarkPreProgrammedReleaseAsDistributed construction + // --------------------------------------------------------------- + + #[test] + fn test_token_mark_pre_programmed_release_construction() { + let op = TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed { + token_id: test_token_id(), + recipient_id: test_recipient_id(), + release_time: 1_700_000_000_000, + }; + + match op { + TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed { + token_id, + recipient_id, + release_time, + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(recipient_id, test_recipient_id()); + assert_eq!(release_time, 1_700_000_000_000); + } + _ => panic!("expected TokenMarkPreProgrammedReleaseAsDistributed variant"), + } + } + + // --------------------------------------------------------------- + // Clone behavior + // --------------------------------------------------------------- + + #[test] + fn test_token_operation_clone() { + let op = TokenOperationType::TokenBurn { + token_id: test_token_id(), + identity_balance_holder_id: test_identity_id(), + burn_amount: 100, + }; + let cloned = op.clone(); + match cloned { + TokenOperationType::TokenBurn { burn_amount, .. } => { + assert_eq!(burn_amount, 100); + } + _ => panic!("clone should preserve variant"), + } + } + + // --------------------------------------------------------------- + // Debug trait + // --------------------------------------------------------------- + + #[test] + fn test_token_set_status_construction() { + use dpp::tokens::status::v0::TokenStatusV0; + let op = TokenOperationType::TokenSetStatus { + token_id: test_token_id(), + status: TokenStatus::V0(TokenStatusV0 { paused: true }), + }; + match op { + TokenOperationType::TokenSetStatus { token_id, .. } => { + assert_eq!(token_id, test_token_id()); + } + _ => panic!("expected TokenSetStatus variant"), + } + } + + #[test] + fn test_token_history_construction() { + use dpp::tokens::token_event::TokenEvent; + + let op = TokenOperationType::TokenHistory { + token_id: test_token_id(), + owner_id: test_identity_id(), + nonce: 42, + event: TokenEvent::Mint(1000, test_identity_id(), None), + }; + match op { + TokenOperationType::TokenHistory { + token_id, + owner_id, + nonce, + .. + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(owner_id, test_identity_id()); + assert_eq!(nonce, 42); + } + _ => panic!("expected TokenHistory variant"), + } + } + + #[test] + fn test_token_mark_perpetual_release_construction() { + let op = TokenOperationType::TokenMarkPerpetualReleaseAsDistributed { + token_id: test_token_id(), + recipient_id: test_recipient_id(), + cycle_start_moment: RewardDistributionMoment::BlockBasedMoment(100), + }; + match op { + TokenOperationType::TokenMarkPerpetualReleaseAsDistributed { + token_id, + recipient_id, + .. + } => { + assert_eq!(token_id, test_token_id()); + assert_eq!(recipient_id, test_recipient_id()); + } + _ => panic!("expected TokenMarkPerpetualReleaseAsDistributed variant"), + } + } + + #[test] + fn test_token_set_price_some() { + use dpp::tokens::token_pricing_schedule::TokenPricingSchedule; + + let pricing = TokenPricingSchedule::SinglePrice(5000); + let op = TokenOperationType::TokenSetPriceForDirectPurchase { + token_id: test_token_id(), + price: Some(pricing), + }; + match op { + TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => { + assert_eq!(token_id, test_token_id()); + assert!(price.is_some()); + } + _ => panic!("expected TokenSetPriceForDirectPurchase variant"), + } + } + + #[test] + fn test_token_operation_debug() { + let op = TokenOperationType::TokenBurn { + token_id: test_token_id(), + identity_balance_holder_id: test_identity_id(), + burn_amount: 42, + }; + let debug_str = format!("{:?}", op); + assert!(debug_str.contains("TokenBurn")); + assert!(debug_str.contains("42")); + } +} diff --git a/packages/rs-drive/src/util/object_size_info/document_info.rs b/packages/rs-drive/src/util/object_size_info/document_info.rs index 875565e9926..086d34ed46d 100644 --- a/packages/rs-drive/src/util/object_size_info/document_info.rs +++ b/packages/rs-drive/src/util/object_size_info/document_info.rs @@ -293,3 +293,362 @@ impl DocumentInfoV0Methods for DocumentInfo<'_> { } } } + +#[cfg(test)] +mod tests { + use super::*; + use dpp::document::DocumentV0; + use dpp::prelude::Identifier; + use std::collections::BTreeMap; + + /// Helper: build a minimal Document (V0) with a given 32-byte id. + fn make_document(id_bytes: [u8; 32]) -> Document { + Document::V0(DocumentV0 { + id: Identifier::new(id_bytes), + owner_id: Identifier::new([0xAA; 32]), + properties: BTreeMap::new(), + revision: Some(1), + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }) + } + + // --------------------------------------------------------------- + // is_document_and_serialization + // --------------------------------------------------------------- + + #[test] + fn test_is_document_and_serialization_true_for_ref_and_serialization() { + let doc = make_document([1; 32]); + let serialized = vec![1, 2, 3]; + let info = DocumentInfo::DocumentRefAndSerialization((&doc, &serialized, None)); + assert!(info.is_document_and_serialization()); + } + + #[test] + fn test_is_document_and_serialization_false_for_owned_info() { + let doc = make_document([2; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + assert!(!info.is_document_and_serialization()); + } + + #[test] + fn test_is_document_and_serialization_false_for_ref_info() { + let doc = make_document([3; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + assert!(!info.is_document_and_serialization()); + } + + #[test] + fn test_is_document_and_serialization_false_for_estimated_size() { + let info = DocumentInfo::DocumentEstimatedAverageSize(100); + assert!(!info.is_document_and_serialization()); + } + + #[test] + fn test_is_document_and_serialization_false_for_document_and_serialization() { + let doc = make_document([4; 32]); + let info = DocumentInfo::DocumentAndSerialization((doc, vec![9, 8, 7], None)); + assert!(!info.is_document_and_serialization()); + } + + // --------------------------------------------------------------- + // is_document_size + // --------------------------------------------------------------- + + #[test] + fn test_is_document_size_true_for_estimated() { + let info = DocumentInfo::DocumentEstimatedAverageSize(256); + assert!(info.is_document_size()); + } + + #[test] + fn test_is_document_size_false_for_owned_info() { + let doc = make_document([5; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + assert!(!info.is_document_size()); + } + + #[test] + fn test_is_document_size_false_for_ref_info() { + let doc = make_document([6; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + assert!(!info.is_document_size()); + } + + // --------------------------------------------------------------- + // get_borrowed_document + // --------------------------------------------------------------- + + #[test] + fn test_get_borrowed_document_from_ref_info() { + let doc = make_document([10; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + let borrowed = info.get_borrowed_document(); + assert!(borrowed.is_some()); + assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[10u8; 32]); + } + + #[test] + fn test_get_borrowed_document_from_ref_and_serialization() { + let doc = make_document([11; 32]); + let ser = vec![0u8; 5]; + let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None)); + let borrowed = info.get_borrowed_document(); + assert!(borrowed.is_some()); + assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[11u8; 32]); + } + + #[test] + fn test_get_borrowed_document_from_owned_info() { + let doc = make_document([12; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + let borrowed = info.get_borrowed_document(); + assert!(borrowed.is_some()); + assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[12u8; 32]); + } + + #[test] + fn test_get_borrowed_document_from_document_and_serialization() { + let doc = make_document([13; 32]); + let info = DocumentInfo::DocumentAndSerialization((doc, vec![1, 2], None)); + let borrowed = info.get_borrowed_document(); + assert!(borrowed.is_some()); + assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[13u8; 32]); + } + + #[test] + fn test_get_borrowed_document_none_for_estimated() { + let info = DocumentInfo::DocumentEstimatedAverageSize(500); + assert!(info.get_borrowed_document().is_none()); + } + + // --------------------------------------------------------------- + // id_key_value_info + // --------------------------------------------------------------- + + #[test] + fn test_id_key_value_info_ref_info_returns_key_ref_request() { + let doc = make_document([20; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + match info.id_key_value_info() { + KeyRefRequest(key) => { + assert_eq!(key, &[20u8; 32]); + } + _ => panic!("expected KeyRefRequest"), + } + } + + #[test] + fn test_id_key_value_info_owned_info_returns_key_ref_request() { + let doc = make_document([21; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + match info.id_key_value_info() { + KeyRefRequest(key) => { + assert_eq!(key, &[21u8; 32]); + } + _ => panic!("expected KeyRefRequest"), + } + } + + #[test] + fn test_id_key_value_info_estimated_returns_key_value_max_size() { + let info = DocumentInfo::DocumentEstimatedAverageSize(999); + match info.id_key_value_info() { + KeyValueMaxSize((key_size, doc_size)) => { + assert_eq!(key_size, 32); + assert_eq!(doc_size, 999); + } + _ => panic!("expected KeyValueMaxSize"), + } + } + + #[test] + fn test_id_key_value_info_ref_and_serialization_returns_key_ref_request() { + let doc = make_document([22; 32]); + let ser = vec![0u8; 3]; + let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None)); + match info.id_key_value_info() { + KeyRefRequest(key) => { + assert_eq!(key, &[22u8; 32]); + } + _ => panic!("expected KeyRefRequest"), + } + } + + #[test] + fn test_id_key_value_info_document_and_serialization_returns_key_ref_request() { + let doc = make_document([23; 32]); + let info = DocumentInfo::DocumentAndSerialization((doc, vec![5, 6, 7], None)); + match info.id_key_value_info() { + KeyRefRequest(key) => { + assert_eq!(key, &[23u8; 32]); + } + _ => panic!("expected KeyRefRequest"), + } + } + + // --------------------------------------------------------------- + // get_estimated_size_for_document_type (system fields) + // --------------------------------------------------------------- + + #[test] + fn test_estimated_size_for_owner_id() { + let info = DocumentInfo::DocumentEstimatedAverageSize(100); + // We cannot build a real DocumentTypeRef without a full contract, + // but for system fields the document type is not consulted. + // The implementation matches on the string key_path first. + // We use a "dummy" DocumentTypeRef -- however, DocumentTypeRef requires real data. + // Instead, let's verify the system field sizes returned by the function + // by checking the match arms directly. Since we can't create a + // DocumentTypeRef trivially, we verify the returned sizes are correct + // by calling get_estimated_size_for_document_type with a system field. + // Unfortunately, DocumentTypeRef is a reference to a real document type, + // so we can only test the specific match arms for system fields in a + // limited way without creating an entire DataContract. We will + // exercise those constant-return paths indirectly through other tests + // or verify the constants themselves. + // + // For now, verify the constants these arms return: + assert_eq!(DEFAULT_HASH_SIZE_U16, 32); + assert_eq!(U64_SIZE_U16, 8); + assert_eq!(U32_SIZE_U16, 4); + // These are the values returned for $ownerId/$id, $createdAt/$updatedAt, + // and $createdAtCoreBlockHeight etc. respectively. + drop(info); + } + + // --------------------------------------------------------------- + // get_borrowed_document_and_storage_flags + // --------------------------------------------------------------- + + #[test] + fn test_get_borrowed_document_and_storage_flags_from_ref_info_no_flags() { + let doc = make_document([30; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + let result = info.get_borrowed_document_and_storage_flags(); + assert!(result.is_some()); + let (d, flags) = result.unwrap(); + assert_eq!(d.id_ref().as_slice(), &[30u8; 32]); + assert!(flags.is_none()); + } + + #[test] + fn test_get_borrowed_document_and_storage_flags_from_owned_info_no_flags() { + let doc = make_document([31; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + let result = info.get_borrowed_document_and_storage_flags(); + assert!(result.is_some()); + let (d, flags) = result.unwrap(); + assert_eq!(d.id_ref().as_slice(), &[31u8; 32]); + assert!(flags.is_none()); + } + + #[test] + fn test_get_borrowed_document_and_storage_flags_none_for_estimated() { + let info = DocumentInfo::DocumentEstimatedAverageSize(200); + assert!(info.get_borrowed_document_and_storage_flags().is_none()); + } + + #[test] + fn test_get_borrowed_document_and_storage_flags_ref_and_serialization() { + let doc = make_document([32; 32]); + let ser = vec![7u8; 4]; + let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None)); + let result = info.get_borrowed_document_and_storage_flags(); + assert!(result.is_some()); + let (d, flags) = result.unwrap(); + assert_eq!(d.id_ref().as_slice(), &[32u8; 32]); + assert!(flags.is_none()); + } + + #[test] + fn test_get_borrowed_document_and_storage_flags_document_and_serialization() { + let doc = make_document([33; 32]); + let info = DocumentInfo::DocumentAndSerialization((doc, vec![10, 20], None)); + let result = info.get_borrowed_document_and_storage_flags(); + assert!(result.is_some()); + let (d, flags) = result.unwrap(); + assert_eq!(d.id_ref().as_slice(), &[33u8; 32]); + assert!(flags.is_none()); + } + + // --------------------------------------------------------------- + // get_storage_flags_ref + // --------------------------------------------------------------- + + #[test] + fn test_get_storage_flags_ref_none_without_flags() { + let doc = make_document([40; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + assert!(info.get_storage_flags_ref().is_none()); + } + + #[test] + fn test_get_storage_flags_ref_none_for_owned_without_flags() { + let doc = make_document([41; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + assert!(info.get_storage_flags_ref().is_none()); + } + + // --------------------------------------------------------------- + // get_document_id_as_slice + // --------------------------------------------------------------- + + #[test] + fn test_get_document_id_as_slice_from_ref_info() { + let doc = make_document([50; 32]); + let info = DocumentInfo::DocumentRefInfo((&doc, None)); + assert_eq!(info.get_document_id_as_slice(), Some([50u8; 32].as_slice())); + } + + #[test] + fn test_get_document_id_as_slice_from_owned_info() { + let doc = make_document([51; 32]); + let info = DocumentInfo::DocumentOwnedInfo((doc, None)); + assert_eq!(info.get_document_id_as_slice(), Some([51u8; 32].as_slice())); + } + + #[test] + fn test_get_document_id_as_slice_from_ref_and_serialization() { + let doc = make_document([52; 32]); + let ser = vec![0u8; 2]; + let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None)); + assert_eq!(info.get_document_id_as_slice(), Some([52u8; 32].as_slice())); + } + + #[test] + fn test_get_document_id_as_slice_from_document_and_serialization() { + let doc = make_document([53; 32]); + let info = DocumentInfo::DocumentAndSerialization((doc, vec![3, 4], None)); + assert_eq!(info.get_document_id_as_slice(), Some([53u8; 32].as_slice())); + } + + #[test] + fn test_get_document_id_as_slice_none_for_estimated() { + let info = DocumentInfo::DocumentEstimatedAverageSize(100); + assert!(info.get_document_id_as_slice().is_none()); + } + + // --------------------------------------------------------------- + // Clone behavior + // --------------------------------------------------------------- + + #[test] + fn test_estimated_average_size_clone_preserves_value() { + let info = DocumentInfo::DocumentEstimatedAverageSize(42); + let cloned = info.clone(); + match cloned { + DocumentInfo::DocumentEstimatedAverageSize(v) => assert_eq!(v, 42), + _ => panic!("clone should preserve variant"), + } + } +}