From a06258572e25e72b65649008a36dcb1263dc9468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Roycourt?= Date: Fri, 15 Sep 2023 02:17:59 +0200 Subject: [PATCH] feat: add tests --- crates/ark-core/src/lib.rs | 2 +- crates/ark-metadata/src/get.rs | 65 --------- crates/ark-metadata/src/lib.rs | 3 +- .../cairo_string_parser.rs} | 42 +++++- .../src/metadata/metadata_models.rs | 26 ++++ crates/ark-metadata/src/metadata/mod.rs | 3 + .../normalization.rs} | 78 +++++++---- crates/ark-metadata/src/metadata_manager.rs | 132 ++++++++++-------- 8 files changed, 192 insertions(+), 159 deletions(-) delete mode 100644 crates/ark-metadata/src/get.rs rename crates/ark-metadata/src/{cairo_strings.rs => metadata/cairo_string_parser.rs} (85%) create mode 100644 crates/ark-metadata/src/metadata/metadata_models.rs create mode 100644 crates/ark-metadata/src/metadata/mod.rs rename crates/ark-metadata/src/{normalize_metadata.rs => metadata/normalization.rs} (83%) diff --git a/crates/ark-core/src/lib.rs b/crates/ark-core/src/lib.rs index b3c617c49..da8e43bf6 100644 --- a/crates/ark-core/src/lib.rs +++ b/crates/ark-core/src/lib.rs @@ -18,7 +18,7 @@ pub async fn indexer_main_loop(storage: T) -> Result<()> { init_tracing(); let rpc_provider = env::var("RPC_PROVIDER").expect("RPC_PROVIDER must be set"); - let sn_client = StarknetClientHttp::new(&rpc_provider.clone())?; + let sn_client = StarknetClientHttp::init(&rpc_provider.clone())?; let block_manager = BlockManager::new(&storage, &sn_client); let mut event_manager = EventManager::new(&storage, &sn_client); let mut token_manager = TokenManager::new(&storage, &sn_client); diff --git a/crates/ark-metadata/src/get.rs b/crates/ark-metadata/src/get.rs deleted file mode 100644 index 811c4ce04..000000000 --- a/crates/ark-metadata/src/get.rs +++ /dev/null @@ -1,65 +0,0 @@ -use aws_sdk_dynamodb::types::AttributeValue; -use log::{error, info, warn}; -use reqwest::Client as ReqwestClient; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashMap; -use std::error::Error; -use std::time::Duration; - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct MetadataAttribute { - trait_type: String, - value: String, - display_type: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct NormalizedMetadata { - pub description: String, - pub external_url: String, - pub image: String, - pub name: String, - attributes: Vec, -} - -impl From for HashMap { - fn from(metadata: NormalizedMetadata) -> Self { - let mut attributes: HashMap = HashMap::new(); - - attributes.insert( - "description".to_string(), - AttributeValue::S(metadata.description), - ); - attributes.insert( - "external_url".to_string(), - AttributeValue::S(metadata.external_url), - ); - attributes.insert("image".to_string(), AttributeValue::S(metadata.image)); - attributes.insert("name".to_string(), AttributeValue::S(metadata.name)); - - let attributes_list: Vec = metadata - .attributes - .into_iter() - .map(|attribute| { - let mut attribute_map: HashMap = HashMap::new(); - attribute_map.insert( - "trait_type".to_string(), - AttributeValue::S(attribute.trait_type), - ); - attribute_map.insert("value".to_string(), AttributeValue::S(attribute.value)); - attribute_map.insert( - "display_type".to_string(), - AttributeValue::S(attribute.display_type), - ); - AttributeValue::M(attribute_map) - }) - .collect(); - - attributes.insert("attributes".to_string(), AttributeValue::L(attributes_list)); - - attributes - } -} - - diff --git a/crates/ark-metadata/src/lib.rs b/crates/ark-metadata/src/lib.rs index f5fbd33f8..c02b16ff5 100644 --- a/crates/ark-metadata/src/lib.rs +++ b/crates/ark-metadata/src/lib.rs @@ -1,3 +1,2 @@ -mod cairo_strings; pub mod metadata_manager; -mod normalize_metadata; +pub mod metadata; diff --git a/crates/ark-metadata/src/cairo_strings.rs b/crates/ark-metadata/src/metadata/cairo_string_parser.rs similarity index 85% rename from crates/ark-metadata/src/cairo_strings.rs rename to crates/ark-metadata/src/metadata/cairo_string_parser.rs index e03e0817c..ab362f69b 100644 --- a/crates/ark-metadata/src/cairo_strings.rs +++ b/crates/ark-metadata/src/metadata/cairo_string_parser.rs @@ -1,12 +1,20 @@ use anyhow::{anyhow, Result}; use starknet::core::{types::FieldElement, utils::parse_cairo_short_string}; -pub fn parse_cairo_long_string(long_string: Vec) -> Result { - match long_string.len() { +/// Parse a Cairo "long string" represented as a Vec of FieldElements into a Rust String. +/// +/// # Arguments +/// * `field_elements`: A vector of FieldElements representing the Cairo long string. +/// +/// # Returns +/// * A `Result` which is either the parsed Rust string or an error. +pub fn parse_cairo_long_string(field_elements: Vec) -> Result { + match field_elements.len() { 0 => { return Err(anyhow!("No value found")); } - 1 => match long_string.first() { + // If the long_string contains only one FieldElement, try to parse it using the short string parser. + 1 => match field_elements.first() { Some(first_string_field_element) => { match parse_cairo_short_string(first_string_field_element) { Ok(value) => { @@ -19,11 +27,12 @@ pub fn parse_cairo_long_string(long_string: Vec) -> Result } None => return Err(anyhow!("No value found")), }, + // If the long_string has more than one FieldElement, parse each FieldElement sequentially + // and concatenate their results. _ => { - // let array_length_field_element = long_string.first().unwrap(); let mut result = String::new(); - for i in 1..long_string.len() { - let field_element = long_string[i as usize].clone(); + for i in 1..field_elements.len() { + let field_element = field_elements[i as usize].clone(); match parse_cairo_short_string(&field_element) { Ok(value) => { result.push_str(&value); @@ -43,6 +52,27 @@ mod tests { use super::parse_cairo_long_string; use starknet::core::types::FieldElement; + #[test] + fn should_return_error_for_empty_vector() { + let long_string = vec![]; + + let result = parse_cairo_long_string(long_string); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "No value found"); + } + + // This test is hypothetical and may not fit exactly as-is depending on the implementation of `parse_cairo_short_string` + #[test] + fn should_handle_generic_error_from_parse_cairo_short_string() { + let long_string = vec![ + // some value that causes `parse_cairo_short_string` to error out + ]; + + let result = parse_cairo_long_string(long_string); + assert!(result.is_err()); + // Check the error message or type here + } + #[test] fn should_parse_field_elements_with_array_length() { let long_string = vec![ diff --git a/crates/ark-metadata/src/metadata/metadata_models.rs b/crates/ark-metadata/src/metadata/metadata_models.rs new file mode 100644 index 000000000..c0a5ab83b --- /dev/null +++ b/crates/ark-metadata/src/metadata/metadata_models.rs @@ -0,0 +1,26 @@ +use serde_derive::{Serialize, Deserialize}; +use serde_json::Value; + + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum MetadataAttributeValue { + Str(String), + Int(i64), + Float(f64), + Value(Value), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct MetadataAttribute { + pub trait_type: String, + pub value: MetadataAttributeValue, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NormalizedMetadata { + pub description: String, + pub external_url: String, + pub image: String, + pub name: String, + pub attributes: Vec, +} \ No newline at end of file diff --git a/crates/ark-metadata/src/metadata/mod.rs b/crates/ark-metadata/src/metadata/mod.rs new file mode 100644 index 000000000..75044de90 --- /dev/null +++ b/crates/ark-metadata/src/metadata/mod.rs @@ -0,0 +1,3 @@ +pub mod normalization; +pub mod cairo_string_parser; +pub mod metadata_models; \ No newline at end of file diff --git a/crates/ark-metadata/src/normalize_metadata.rs b/crates/ark-metadata/src/metadata/normalization.rs similarity index 83% rename from crates/ark-metadata/src/normalize_metadata.rs rename to crates/ark-metadata/src/metadata/normalization.rs index d28216988..dd42b090a 100644 --- a/crates/ark-metadata/src/normalize_metadata.rs +++ b/crates/ark-metadata/src/metadata/normalization.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Ok, Result}; use log::warn; use serde_json::Value; -use crate::metadata_manager::{MetadataAttribute, MetadataAttributeValue, NormalizedMetadata}; +use super::metadata_models::{NormalizedMetadata, MetadataAttribute, MetadataAttributeValue}; // fn normalize_metadata_attributes_with_eip721_standard( // metadata_uri: String, @@ -35,13 +35,13 @@ use crate::metadata_manager::{MetadataAttribute, MetadataAttributeValue, Normali // } // } -struct CommonMetadataProperties { +struct BaseMetadataProperties { description: String, image: String, name: String, } -fn extract_common_metadata(metadata: &Value) -> CommonMetadataProperties { +fn extract_properties(metadata: &Value) -> BaseMetadataProperties { let description = match metadata.get("description") { Some(description) => description.as_str().unwrap().to_string(), None => String::from(""), @@ -57,7 +57,7 @@ fn extract_common_metadata(metadata: &Value) -> CommonMetadataProperties { None => String::from(""), }; - return CommonMetadataProperties { + return BaseMetadataProperties { description, image, name, @@ -77,11 +77,11 @@ pub fn normalize_metadata_attributes_with_opensea_standard( }; for attribute in items { - let trait_type = attribute - .get("trait_type") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); + let trait_type = attribute + .get("trait_type") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); match attribute.get("value") { Some(attribute_value) => match attribute_value { @@ -107,9 +107,7 @@ pub fn normalize_metadata_attributes_with_opensea_standard( _ => { attributes.push(MetadataAttribute { trait_type: String::from(trait_type), - value: MetadataAttributeValue::Value( - attribute_value.clone(), - ), + value: MetadataAttributeValue::Value(attribute_value.clone()), }); } }, @@ -123,7 +121,7 @@ pub fn normalize_metadata_attributes_with_opensea_standard( None => Vec::new(), }; - let common_properties = extract_common_metadata(raw_metadata); + let common_properties = extract_properties(raw_metadata); let external_url = match raw_metadata.get("external_url") { Some(value) => value.as_str().unwrap().to_string(), None => metadata_uri, @@ -142,7 +140,6 @@ pub fn normalize_metadata( initial_metadata_uri: String, raw_metadata: Value, ) -> Result { - match raw_metadata.get("attributes") { Some(_attributes) => { let normalized_metadata = normalize_metadata_attributes_with_opensea_standard( @@ -151,22 +148,51 @@ pub fn normalize_metadata( )?; return Ok(normalized_metadata); } - None => { - Err(anyhow!("Error with the metadata object")) - }, + None => Err(anyhow!("Error with the metadata object")), } } #[cfg(test)] mod tests { - use crate::{ - metadata_manager::MetadataAttributeValue, - normalize_metadata::{ + use crate::metadata::{normalization::{ normalize_metadata, normalize_metadata_attributes_with_opensea_standard, - }, - }; + }, metadata_models::MetadataAttributeValue}; use serde_json::json; + #[test] + fn should_use_metadata_uri_when_external_url_is_missing() { + let starknet_id_raw_metadata = json!({ + "name": "test", + "description": "test description", + "image": "test image", + "attributes": [{"trait_type": "Base", "value": "Starfish"}] + }); + + let metadata_uri = String::from("https://starknet.id/api/identicons/1"); + let result = normalize_metadata_attributes_with_opensea_standard( + metadata_uri.clone(), + &starknet_id_raw_metadata, + ); + assert!(result.is_ok()); + let normalized_metadata = result.unwrap(); + assert_eq!(normalized_metadata.external_url, metadata_uri); + } + + #[test] + fn should_return_error_when_attributes_missing() { + let metadata = json!({ + "name": "test", + "description": "test description", + "image": "test image" + }); + + let metadata_uri = String::from("https://starknet.id/api/identicons/1"); + let result = normalize_metadata(metadata_uri, metadata); + assert!(result.is_err()); + } + + + #[test] fn should_normalize_metadata() { let description = "This token represents an identity on StarkNet."; @@ -179,21 +205,13 @@ mod tests { assert!(result.is_ok()); let normalized_metadata = result.unwrap(); - - println!("\n\n==> normalized_metadata: {:?}", normalized_metadata); - assert!(normalized_metadata.external_url == metadata_uri); assert!(normalized_metadata.description == description); assert!(normalized_metadata.image == image); assert!(normalized_metadata.name == name); let first_attribute = normalized_metadata.attributes.first().unwrap(); - assert!(first_attribute.trait_type == "Subdomain"); - - // println!("\n\n==> Value: {}", first_attribute.value); - println!("==> Value: {:?}", first_attribute.value); - } #[test] diff --git a/crates/ark-metadata/src/metadata_manager.rs b/crates/ark-metadata/src/metadata_manager.rs index dbdc5458e..b92efe55c 100644 --- a/crates/ark-metadata/src/metadata_manager.rs +++ b/crates/ark-metadata/src/metadata_manager.rs @@ -1,15 +1,14 @@ -use crate::cairo_strings::parse_cairo_long_string; -use crate::normalize_metadata::normalize_metadata; +use crate::metadata::cairo_string_parser::parse_cairo_long_string; +use crate::metadata::metadata_models::NormalizedMetadata; +use crate::metadata::normalization::normalize_metadata; + use anyhow::{anyhow, Result}; use ark_starknet::client::StarknetClient; use ark_storage::storage_manager::StorageManager; -use log::info; +use log::{info, debug}; use reqwest::Client as ReqwestClient; -use serde_derive::{Deserialize, Serialize}; use serde_json::Value; -use starknet::core::types::BlockId; -use starknet::core::types::BlockTag; -use starknet::core::types::FieldElement; +use starknet::core::types::{BlockId, BlockTag, FieldElement}; use starknet::macros::selector; use std::time::Duration; @@ -19,41 +18,6 @@ pub struct MetadataManager<'a, T: StorageManager, C: StarknetClient> { request_client: ReqwestClient, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub enum MetadataAttributeValue { - Str(String), - Int(i64), - Float(f64), - Value(Value), -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct MetadataAttribute { - pub trait_type: String, - pub value: MetadataAttributeValue, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct NormalizedMetadata { - pub description: String, - pub external_url: String, - pub image: String, - pub name: String, - pub attributes: Vec, -} - -// trait NormalizedMetadata { -// fn from(metadata: NormalizedMetadata) -> Self; -// } - -// pub struct DefaultNormalizedMetadata; - -// impl NormalizedMetadata for DefaultNormalizedMetadata { -// fn from(metadata: NormalizedMetadata) -> Self { -// let _ = metadata; -// } -// } - impl<'a, T: StorageManager, C: StarknetClient> MetadataManager<'a, T, C> { pub fn new(storage: &'a T, starknet_client: &'a C) -> Self { MetadataManager { @@ -63,7 +27,7 @@ impl<'a, T: StorageManager, C: StarknetClient> MetadataManager<'a, T, C> { } } - pub async fn refresh_metadata( + pub async fn refresh_metadata_for_token( &mut self, contract_address: FieldElement, token_id_low: FieldElement, @@ -86,13 +50,17 @@ impl<'a, T: StorageManager, C: StarknetClient> MetadataManager<'a, T, C> { Ok(()) } - pub async fn get_token_uri( + pub async fn refresh_metadata_for_token_collection()-> Result<()> { + Ok(()) + } + + async fn get_token_uri( &mut self, token_id_low: FieldElement, token_id_high: FieldElement, contract_address: FieldElement, ) -> Result { - info!("get_token_id: [{:?}, {:?}]", token_id_low, token_id_high); + debug!("get_token_id: [{:?}, {:?}]", token_id_low, token_id_high); match self .get_contract_property_string( contract_address, @@ -129,7 +97,7 @@ impl<'a, T: StorageManager, C: StarknetClient> MetadataManager<'a, T, C> { } } - pub async fn fetch_metadata( + async fn fetch_metadata( &mut self, metadata_uri: &str, initial_metadata_uri: &str, @@ -156,7 +124,7 @@ impl<'a, T: StorageManager, C: StarknetClient> MetadataManager<'a, T, C> { } } - pub async fn get_contract_property_string( + async fn get_contract_property_string( &mut self, contract_address: FieldElement, selector: FieldElement, @@ -184,21 +152,72 @@ mod tests { use ark_storage::storage_manager::DefaultStorage; use mockall::predicate::*; + // #[tokio::test] + // async fn test_get_token_uri() { + // // SETUP: Mocking and Initializing + // let mut mock_client = MockStarknetClient::new(); + // let storage_manager = DefaultStorage::new(); + + // mock_client + // .expect_call_contract() + // .times(1) + // .returning(|_, _, _, _| { + // Ok(vec![ + // FieldElement::from_dec_str(&"4").unwrap(), + // FieldElement::from_hex_be("0x68").unwrap(), + // FieldElement::from_hex_be("0x74").unwrap(), + // FieldElement::from_hex_be("0x74").unwrap(), + // FieldElement::from_hex_be("0x70").unwrap(), + // ]) + // }); + + // let mut metadata_manager = MetadataManager::new(&storage_manager, &mock_client); + + // // EXECUTION: Call the function under test + // let result = metadata_manager + // .get_token_uri( + // FieldElement::ZERO, + // FieldElement::ONE, + // FieldElement::from_hex_be("0x0727a63f78ee3f1bd18f78009067411ab369c31dece1ae22e16f567906409905").unwrap()) + // .await; + + + // assert!(result.is_ok()); + // } + #[tokio::test] async fn test_get_contract_property_string() { - let mut mock = MockStarknetClient::new(); + // SETUP: Mocking and Initializing + let mut mock_client = MockStarknetClient::new(); + + let contract_address = FieldElement::ONE; + let selector_name = selector!("tokenURI"); - mock.expect_call_contract() + // Configure the mock client to expect a call to 'call_contract' and return a predefined result + mock_client + .expect_call_contract() .times(1) - .returning(|_, _, _, _| Ok(vec![])); + .with( + eq(contract_address.clone()), + eq(selector_name.clone()), + eq(vec![FieldElement::ZERO, FieldElement::ZERO]), + eq(BlockId::Tag(BlockTag::Latest)), + ) + .returning(|_, _, _, _| { + Ok(vec![ + FieldElement::from_dec_str(&"4").unwrap(), + FieldElement::from_hex_be("0x68").unwrap(), + FieldElement::from_hex_be("0x74").unwrap(), + FieldElement::from_hex_be("0x74").unwrap(), + FieldElement::from_hex_be("0x70").unwrap(), + ]) + }); let storage_manager = DefaultStorage::new(); - let mut metadata_manager = MetadataManager::new(&storage_manager, &mock); - - let contract_address = FieldElement::ONE; - let selector_name = selector!("tokenURI"); + let mut metadata_manager = MetadataManager::new(&storage_manager, &mock_client); - let value = metadata_manager + // EXECUTION: Call the function under test + let result = metadata_manager .get_contract_property_string( contract_address, selector_name, @@ -207,5 +226,8 @@ mod tests { ) .await; + // ASSERTION: Verify the outcome + let parsed_string = result.expect("Failed to get contract property string"); + assert_eq!(parsed_string, "http"); } }