mod.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use std::sync::{Arc, Mutex}; 20 21 use lazy_static::lazy_static; 22 use smol::Executor; 23 use tempdir::TempDir; 24 use tinyjson::JsonValue; 25 use tracing::warn; 26 use url::Url; 27 28 use darkfi::{ 29 rpc::{ 30 jsonrpc::{ErrorCode, JsonRequest, JsonResult}, 31 server::RequestHandler, 32 }, 33 util::logger::{setup_test_logger, Level}, 34 }; 35 36 use crate::Explorerd; 37 38 // Defines a global `Explorerd` instance shared across all tests 39 lazy_static! { 40 static ref EXPLORERD_INSTANCE: Mutex<Option<Arc<Explorerd>>> = Mutex::new(None); 41 } 42 43 #[cfg(test)] 44 /// Sets up the `Explorerd` instance for testing, ensuring a single instance is initialized only 45 /// once and shared among subsequent setup calls. 46 pub fn setup() -> Arc<Explorerd> { 47 let mut instance = EXPLORERD_INSTANCE.lock().expect("Failed to lock EXPLORERD_INSTANCE mutex"); 48 49 if instance.is_none() { 50 // Initialize logger for the first time 51 if setup_test_logger( 52 &["sled", "runtime", "net"], 53 false, 54 Level::Info, 55 //Level::Verbose, 56 //Level::Debug, 57 //Level::Trace 58 ) 59 .is_err() 60 { 61 warn!("Logger already initialized"); 62 } 63 64 // Prepare parameters for Explorerd::new 65 let temp_dir = TempDir::new("explorerd").expect("Failed to create temp dir"); 66 let db_path_buf = temp_dir.path().join("explorerd_0"); 67 let db_path = 68 db_path_buf.to_str().expect("Failed to convert db_path to string").to_string(); 69 let darkfid_endpoint = Url::parse("http://127.0.0.1:8240").expect("Invalid URL"); 70 let executor = Arc::new(Executor::new()); 71 72 // Block on the async function to resolve Explorerd::new 73 let explorerd = smol::block_on(Explorerd::new(db_path, darkfid_endpoint, executor)) 74 .expect("Failed to initialize Explorerd instance"); 75 76 // Store the initialized instance in the global Mutex 77 *instance = Some(Arc::new(explorerd)); 78 } 79 80 // Return a clone of the shared instance 81 Arc::clone(instance.as_ref().unwrap()) 82 } 83 84 /// Auxiliary function that validates the correct handling of an invalid JSON-RPC parameter. It 85 /// prepares a JSON-RPC request with the provided method and params. It then sends the request using 86 /// the [`Explorerd::handle_request`] function of the provided [`Explorerd`] instance. Verifies the 87 /// response is an error, matching the expected error code and message. 88 pub async fn validate_invalid_rpc_parameter( 89 explorerd: &Explorerd, 90 method_name: &str, 91 params: &[JsonValue], 92 expected_error_code: i32, 93 expected_error_message: &str, 94 ) { 95 // Prepare an invalid JSON-RPC request with the provided `params` 96 let request = JsonRequest { 97 id: 1, 98 jsonrpc: "2.0", 99 method: method_name.to_string(), 100 params: JsonValue::Array(params.to_vec()), 101 }; 102 103 // Call `handle_request` on the Explorerd instance 104 let response = explorerd.handle_request(request).await; 105 106 // Verify response is a `JsonError` with the appropriate error code and message 107 match response { 108 JsonResult::Error(actual_error) => { 109 assert_eq!(actual_error.error.message, expected_error_message); 110 assert_eq!(actual_error.error.code, expected_error_code); 111 } 112 _ => panic!( 113 "Expected a JSON error response for method: {method_name}, but got something else" 114 ), 115 } 116 } 117 118 /// Auxiliary function that validates the handling of non-empty parameters when they are supposed 119 /// to be empty for the given RPC `method`. It uses the provided [`Explorerd`] instance to ensure 120 /// that unexpected non-empty parameters result in the expected error for invalid parameters. 121 pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: &str) { 122 // Prepare a JSON-RPC request for `ping_darkfid` 123 let request = JsonRequest { 124 id: 1, 125 jsonrpc: "2.0", 126 method: method.to_string(), 127 params: JsonValue::Array(vec![JsonValue::String("non_empty_param".to_string())]), 128 }; 129 130 // Call `handle_request` on the Explorerd instance. 131 let response = explorerd.handle_request(request).await; 132 133 // Verify the response is a `JsonError` with the `PingFailed` error code 134 match response { 135 JsonResult::Error(actual_error) => { 136 let expected_error_code = ErrorCode::InvalidParams.code(); 137 let expected_error_msg = 138 "Parameters not permited, received: \"[\\\"non_empty_param\\\"]\""; 139 assert_eq!(actual_error.error.code, expected_error_code); 140 assert_eq!(actual_error.error.message, expected_error_msg); 141 } 142 _ => panic!("Expected a JSON object for the response, but got something else"), 143 } 144 } 145 146 /// Auxiliary function that validates the handling of an invalid contract ID when calling the specified 147 /// JSON-RPC method, ensuring appropriate error responses from provided [`Explorerd`]. 148 pub fn validate_invalid_rpc_contract_id(explorerd: &Explorerd, method: &str) { 149 validate_invalid_rpc_hash_parameter(explorerd, method, "contract_id", "Invalid contract ID"); 150 } 151 152 /// Auxiliary function that validates the handling of an invalid header hash when calling the specified 153 /// JSON-RPC `method`, ensuring appropriate error responses from provided [`Explorerd`]. 154 pub fn validate_invalid_rpc_header_hash(explorerd: &Explorerd, method: &str) { 155 validate_invalid_rpc_hash_parameter(explorerd, method, "header_hash", "Invalid header hash"); 156 } 157 158 /// Auxiliary function that validates the handling of an invalid tx hash when calling the specified JSON-RPC 159 /// `method`, ensuring appropriate error responses from provided [`Explorerd`]. 160 pub fn validate_invalid_rpc_tx_hash(explorerd: &Explorerd, method: &str) { 161 validate_invalid_rpc_hash_parameter(explorerd, method, "tx_hash", "Invalid tx hash"); 162 } 163 164 /// Auxiliary function that validates the correct handling of invalid hash parameters 165 /// when calling the given RPC `method` using the provided [`Explorerd`]. This includes checks for 166 /// missing parameters, incorrect parameter types, and invalid hash values, ensuring it returns 167 /// error responses matching the expected error codes and messages. 168 fn validate_invalid_rpc_hash_parameter( 169 explorerd: &Explorerd, 170 method: &str, 171 parameter_name: &str, 172 invalid_hash_value_message: &str, 173 ) { 174 smol::block_on(async { 175 // Test for missing `parameter_name` parameter 176 validate_invalid_rpc_parameter( 177 explorerd, 178 method, 179 &[], 180 ErrorCode::InvalidParams.code(), 181 &format!("Parameter '{parameter_name}' at index 0 is missing"), 182 ) 183 .await; 184 185 // Test for invalid `parameter_name` parameter type 186 validate_invalid_rpc_parameter( 187 explorerd, 188 method, 189 &[JsonValue::Number(123.0)], 190 ErrorCode::InvalidParams.code(), 191 &format!("Parameter '{parameter_name}' is not a valid string"), 192 ) 193 .await; 194 195 // Test for invalid `contract_id` value 196 validate_invalid_rpc_parameter( 197 explorerd, 198 method, 199 &[JsonValue::String("0x0222".to_string())], 200 ErrorCode::InvalidParams.code(), 201 &format!("{invalid_hash_value_message}: 0x0222"), 202 ) 203 .await; 204 }); 205 }