From 3830bd64c1d26403010393fd65221a4216e6630e Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Tue, 30 Apr 2024 03:36:54 -0400 Subject: [PATCH] Implement Get Assets by Group Call --- src/rpc_client.rs | 17 +- src/types/types.rs | 26 +-- tests/rpc/test_get_assets_by_group.rs | 222 ++++++++++++++++++++++++++ tests/tests.rs | 1 + 4 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 tests/rpc/test_get_assets_by_group.rs diff --git a/src/rpc_client.rs b/src/rpc_client.rs index 836073e..18b314c 100644 --- a/src/rpc_client.rs +++ b/src/rpc_client.rs @@ -8,7 +8,7 @@ use crate::request_handler::RequestHandler; use crate::types::types::{RpcRequest, RpcResponse}; use crate::types::{ Asset, AssetList, AssetProof, GetAsset, GetAssetBatch, GetAssetProof, GetAssetProofBatch, GetAssetsByAuthority, - GetAssetsByCreator, GetAssetsByOwner, + GetAssetsByCreator, GetAssetsByGroup, GetAssetsByOwner, }; use reqwest::{Client, Method, Url}; @@ -70,11 +70,6 @@ impl RpcClient { self.post_rpc_request("getAssetProofBatch", request).await } - /// Gets a list of assets owned by a given address - pub async fn get_assets_by_owner(&self, request: GetAssetsByOwner) -> Result { - self.post_rpc_request("getAssetsByOwner", request).await - } - /// Gets a list of assets of a given authority pub async fn get_assets_by_authority(&self, request: GetAssetsByAuthority) -> Result { self.post_rpc_request("getAssetsByAuthority", request).await @@ -84,4 +79,14 @@ impl RpcClient { pub async fn get_assets_by_creator(&self, request: GetAssetsByCreator) -> Result { self.post_rpc_request("getAssetsByCreator", request).await } + + /// Gets a list of assets by a group key and value + pub async fn get_assets_by_group(&self, request: GetAssetsByGroup) -> Result { + self.post_rpc_request("getAssetsByGroup", request).await + } + + /// Gets a list of assets owned by a given address + pub async fn get_assets_by_owner(&self, request: GetAssetsByOwner) -> Result { + self.post_rpc_request("getAssetsByOwner", request).await + } } diff --git a/src/types/types.rs b/src/types/types.rs index da72652..0586587 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -94,6 +94,22 @@ pub struct GetAssetsByAuthority { pub cursor: Option, } +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct GetAssetsByGroup { + #[serde(rename = "groupKey")] + pub group_key: String, + #[serde(rename = "groupValue")] + pub group_value: String, + pub page: u32, + pub limit: u32, + #[serde(rename = "sortBy")] + pub sort_by: Option, + pub before: Option, + pub after: Option, + #[serde(rename = "displayOptions")] + pub display_options: Option, +} + #[derive(Serialize, Deserialize, Debug, Default)] pub struct GetAsset { pub id: String, @@ -131,16 +147,6 @@ pub struct ApiResponse { pub id: String, } -#[derive(Serialize, Deserialize, Debug, Default)] -#[serde(untagged)] -pub enum ResponseType { - #[default] - DefaultResponse, // This is a placeholder for the default response type. TODO: Replace this an appropriate type - GetAssetResponseList(AssetList), - GetAssetResponseForAsset(Asset), - Other(Value), -} - #[derive(Serialize, Deserialize, Debug, Default)] pub struct AssetList { #[serde(skip_serializing_if = "Option::is_none")] diff --git a/tests/rpc/test_get_assets_by_group.rs b/tests/rpc/test_get_assets_by_group.rs new file mode 100644 index 0000000..1f3b631 --- /dev/null +++ b/tests/rpc/test_get_assets_by_group.rs @@ -0,0 +1,222 @@ +use std::sync::Arc; + +use helius_sdk::client::Helius; +use helius_sdk::config::Config; +use helius_sdk::error::HeliusError; +use helius_sdk::rpc_client::RpcClient; +use helius_sdk::types::*; + +use mockito::{self, Server}; +use reqwest::Client; +use serde_json::Value; + +#[tokio::test] +async fn test_get_assets_by_group_success() { + let mut server: Server = Server::new_with_opts_async(mockito::ServerOpts::default()).await; + let url: String = server.url(); + + let mock_response: ApiResponse = ApiResponse { + jsonrpc: "2.0".to_string(), + result: AssetList { + grand_total: None, + total: 1, + limit: 1, + page: Some(1), + before: None, + after: None, + cursor: None, + items: vec![ + Asset { + interface: Interface::ProgrammableNFT, + id: "Obi-Wan Kenobi".to_string(), + content: Some(Content { + schema: "https://schema.metaplex.com/nft1.0.json".to_string(), + json_uri: "https://example.com/nft.json".to_string(), + files: Some(vec![File { + uri: Some("https://example.com/image.png".to_string()), + mime: Some("image/png".to_string()), + cdn_uri: Some("https://cdn.example.com/image.png".to_string()), + quality: None, + contexts: None, + }]), + metadata: Metadata { + attributes: Some(vec![Attribute { + value: Value::String("Jedi Master".to_string()), + trait_type: "Rank".to_string(), + }, Attribute { + value: Value::String("Galactic Republic".to_string()), + trait_type: "Affiliation".to_string(), + }, Attribute { + value: Value::String("Jedi Order".to_string()), + trait_type: "Affiliation".to_string(), + }]), + description: Some("Obi-Wan Kenobi was a legendary Force-sensitive human male Jedi Master who served on the Jedi High Council during the final years of the Republic Era".to_string()), + name: "Obi-Wan Kenobi".to_string(), + symbol:"Guiding Light".to_string(), + }, + links: Some(Links { + external_url: Some("https://example.com".to_string()), + image: Some("https://example.com/image.png".to_string()), + animation_url: None, + }), + }), + authorities: Some(vec![Authorities { + address: "JediCouncil".to_string(), + scopes: vec![Scope::Full], + }]), + compression: Some(Compression { + eligible: false, + compressed: false, + data_hash: "".to_string(), + creator_hash: "".to_string(), + asset_hash: "".to_string(), + tree: "".to_string(), + seq: 0, + leaf_id: 0, + }), + grouping: Some(vec![Group { + group_key: "collection".to_string(), + group_value: Some("JediCouncil".to_string()), + verified: None, + collection_metadata: None, + }]), + royalty: Some(Royalty { + royalty_model: RoyaltyModel::Creators, + target: None, + percent: 0.042, + basis_points: 420, + primary_sale_happened: true, + locked: false, + }), + creators: Some(vec![Creator { + address: "TheForce".to_string(), + share: 100, + verified: true, + }]), + ownership: Ownership { + frozen: true, + delegated: true, + delegate: Some("DelegateAddress".to_string()), + ownership_model: OwnershipModel::Single, + owner: "OwnerAddress".to_string(), + }, + uses: None, + supply: Some(Supply { + print_max_supply: None, + print_current_supply: None, + edition_nonce: None, + edition_number: None, + master_edition_mint: None, + }), + mutable: true, + burnt: false, + mint_extensions: None, + token_info: None, + group_definition: None, + plugins: None, + unknown_plugins: None, + mpl_core_info: None, + }, + ], + errors: None, + }, + id: "1".to_string(), + }; + + server + .mock("POST", "/?api-key=fake_api_key") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(serde_json::to_string(&mock_response).unwrap()) + .create(); + + let config: Arc = Arc::new(Config { + api_key: "fake_api_key".to_string(), + cluster: Cluster::Devnet, + endpoints: HeliusEndpoints { + api: url.to_string(), + rpc: url.to_string(), + }, + }); + + let client: Client = Client::new(); + let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), Arc::clone(&config)).unwrap()); + let helius: Helius = Helius { + config, + client, + rpc_client, + }; + + let sorting: AssetSorting = AssetSorting { + sort_by: AssetSortBy::Created, + sort_direction: Some(AssetSortDirection::Asc), + }; + + let request: GetAssetsByGroup = GetAssetsByGroup { + group_key: "collection".to_string(), + group_value: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w".to_string(), + page: 1, + limit: 10, + sort_by: Some(sorting), + before: None, + after: None, + display_options: None, + }; + + let response: Result = helius.rpc().get_assets_by_group(request).await; + assert!(response.is_ok(), "Expected an error due to server failure"); + + let asset: AssetList = response.unwrap(); + assert_eq!(asset.total, 1); + assert_eq!(asset.items[0].content.as_ref().unwrap().metadata.name, "Obi-Wan Kenobi"); +} + +#[tokio::test] +async fn test_get_assets_by_group_failure() { + let mut server: Server = Server::new_with_opts_async(mockito::ServerOpts::default()).await; + let url: String = server.url(); + + // Simulate an API failure with status code 500 + server + .mock("POST", "/?api-key=fake_api_key") + .with_status(500) + .with_header("content-type", "application/json") + .with_body(r#"{"error": "Internal Server Error"}"#) + .create(); + + let config: Arc = Arc::new(Config { + api_key: "fake_api_key".to_string(), + cluster: Cluster::Devnet, + endpoints: HeliusEndpoints { + api: url.to_string(), + rpc: url.to_string(), + }, + }); + + let client: Client = Client::new(); + let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), Arc::clone(&config)).unwrap()); + let helius: Helius = Helius { + config, + client, + rpc_client, + }; + + let sorting: AssetSorting = AssetSorting { + sort_by: AssetSortBy::Created, + sort_direction: Some(AssetSortDirection::Asc), + }; + + let request: GetAssetsByGroup = GetAssetsByGroup { + group_key: "collection".to_string(), + group_value: "Bad Request".to_string(), + page: 1, + limit: 10, + sort_by: Some(sorting), + before: None, + after: None, + display_options: None, + }; + + let response: Result = helius.rpc().get_assets_by_group(request).await; + assert!(response.is_err(), "Expected an error due to server failure"); +} diff --git a/tests/tests.rs b/tests/tests.rs index ea69122..9f16791 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -9,5 +9,6 @@ mod rpc { mod test_get_asset_proof_batch; mod test_get_assets_by_authority; mod test_get_assets_by_creator; + mod test_get_assets_by_group; mod test_get_assets_by_owner; }