mod.rs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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. use std::sync::{Arc, Mutex};
  19. use lazy_static::lazy_static;
  20. use smol::Executor;
  21. use tempdir::TempDir;
  22. use tinyjson::JsonValue;
  23. use url::Url;
  24. use darkfi::rpc::{
  25. jsonrpc::{ErrorCode, JsonRequest, JsonResult},
  26. server::RequestHandler,
  27. };
  28. use crate::Explorerd;
  29. // Defines a global `Explorerd` instance shared across all tests
  30. lazy_static! {
  31. static ref EXPLORERD_INSTANCE: Mutex<Option<Arc<Explorerd>>> = Mutex::new(None);
  32. }
  33. /// Initializes logging for test cases, which is useful for debugging issues encountered during testing.
  34. /// The logger is configured based on the provided list of targets to ignore and the desired log level.
  35. #[cfg(test)]
  36. pub fn init_logger(log_level: simplelog::LevelFilter, ignore_targets: Vec<&str>) {
  37. let mut cfg = simplelog::ConfigBuilder::new();
  38. // Add targets to ignore
  39. for target in ignore_targets {
  40. cfg.add_filter_ignore(target.to_string());
  41. }
  42. // Set log level
  43. cfg.set_target_level(log_level);
  44. // initialize the logger
  45. if simplelog::TermLogger::init(
  46. log_level,
  47. cfg.build(),
  48. simplelog::TerminalMode::Mixed,
  49. simplelog::ColorChoice::Auto,
  50. )
  51. .is_err()
  52. {
  53. // Print an error message if logger failed to initialize
  54. eprintln!("Logger failed to initialize");
  55. }
  56. }
  57. #[cfg(test)]
  58. /// Sets up the `Explorerd` instance for testing, ensuring a single instance is initialized only
  59. /// once and shared among subsequent setup calls.
  60. pub fn setup() -> Arc<Explorerd> {
  61. let mut instance = EXPLORERD_INSTANCE.lock().expect("Failed to lock EXPLORERD_INSTANCE mutex");
  62. if instance.is_none() {
  63. // Initialize logger for the first time
  64. init_logger(simplelog::LevelFilter::Off, vec!["sled", "runtime", "net"]);
  65. // Prepare parameters for Explorerd::new
  66. let temp_dir = TempDir::new("explorerd").expect("Failed to create temp dir");
  67. let db_path_buf = temp_dir.path().join("explorerd_0");
  68. let db_path =
  69. db_path_buf.to_str().expect("Failed to convert db_path to string").to_string();
  70. let darkfid_endpoint = Url::parse("http://127.0.0.1:8240").expect("Invalid URL");
  71. let executor = Arc::new(Executor::new());
  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. // Store the initialized instance in the global Mutex
  76. *instance = Some(Arc::new(explorerd));
  77. }
  78. // Return a clone of the shared instance
  79. Arc::clone(instance.as_ref().unwrap())
  80. }
  81. /// Auxiliary function that validates the correct handling of an invalid JSON-RPC parameter. It
  82. /// prepares a JSON-RPC request with the provided method and params. It then sends the request using
  83. /// the [`Explorerd::handle_request`] function of the provided [`Explorerd`] instance. Verifies the
  84. /// response is an error, matching the expected error code and message.
  85. pub async fn validate_invalid_rpc_parameter(
  86. explorerd: &Explorerd,
  87. method_name: &str,
  88. params: &[JsonValue],
  89. expected_error_code: i32,
  90. expected_error_message: &str,
  91. ) {
  92. // Prepare an invalid JSON-RPC request with the provided `params`
  93. let request = JsonRequest {
  94. id: 1,
  95. jsonrpc: "2.0",
  96. method: method_name.to_string(),
  97. params: JsonValue::Array(params.to_vec()),
  98. };
  99. // Call `handle_request` on the Explorerd instance
  100. let response = explorerd.handle_request(request).await;
  101. // Verify response is a `JsonError` with the appropriate error code and message
  102. match response {
  103. JsonResult::Error(actual_error) => {
  104. assert_eq!(actual_error.error.message, expected_error_message);
  105. assert_eq!(actual_error.error.code, expected_error_code);
  106. }
  107. _ => panic!(
  108. "Expected a JSON error response for method: {method_name}, but got something else"
  109. ),
  110. }
  111. }
  112. /// Auxiliary function that validates the handling of non-empty parameters when they are supposed
  113. /// to be empty for the given RPC `method`. It uses the provided [`Explorerd`] instance to ensure
  114. /// that unexpected non-empty parameters result in the expected error for invalid parameters.
  115. pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: &str) {
  116. // Prepare a JSON-RPC request for `ping_darkfid`
  117. let request = JsonRequest {
  118. id: 1,
  119. jsonrpc: "2.0",
  120. method: method.to_string(),
  121. params: JsonValue::Array(vec![JsonValue::String("non_empty_param".to_string())]),
  122. };
  123. // Call `handle_request` on the Explorerd instance.
  124. let response = explorerd.handle_request(request).await;
  125. // Verify the response is a `JsonError` with the `PingFailed` error code
  126. match response {
  127. JsonResult::Error(actual_error) => {
  128. let expected_error_code = ErrorCode::InvalidParams.code();
  129. let expected_error_msg =
  130. "Parameters not permited, received: \"[\\\"non_empty_param\\\"]\"";
  131. assert_eq!(actual_error.error.code, expected_error_code);
  132. assert_eq!(actual_error.error.message, expected_error_msg);
  133. }
  134. _ => panic!("Expected a JSON object for the response, but got something else"),
  135. }
  136. }
  137. /// Auxiliary function that validates the handling of an invalid contract ID when calling the specified
  138. /// JSON-RPC method, ensuring appropriate error responses from provided [`Explorerd`].
  139. pub fn validate_invalid_rpc_contract_id(explorerd: &Explorerd, method: &str) {
  140. validate_invalid_rpc_hash_parameter(explorerd, method, "contract_id", "Invalid contract ID");
  141. }
  142. /// Auxiliary function that validates the handling of an invalid header hash when calling the specified
  143. /// JSON-RPC `method`, ensuring appropriate error responses from provided [`Explorerd`].
  144. pub fn validate_invalid_rpc_header_hash(explorerd: &Explorerd, method: &str) {
  145. validate_invalid_rpc_hash_parameter(explorerd, method, "header_hash", "Invalid header hash");
  146. }
  147. /// Auxiliary function that validates the handling of an invalid tx hash when calling the specified JSON-RPC
  148. /// `method`, ensuring appropriate error responses from provided [`Explorerd`].
  149. pub fn validate_invalid_rpc_tx_hash(explorerd: &Explorerd, method: &str) {
  150. validate_invalid_rpc_hash_parameter(explorerd, method, "tx_hash", "Invalid tx hash");
  151. }
  152. /// Auxiliary function that validates the correct handling of invalid hash parameters
  153. /// when calling the given RPC `method` using the provided [`Explorerd`]. This includes checks for
  154. /// missing parameters, incorrect parameter types, and invalid hash values, ensuring it returns
  155. /// error responses matching the expected error codes and messages.
  156. fn validate_invalid_rpc_hash_parameter(
  157. explorerd: &Explorerd,
  158. method: &str,
  159. parameter_name: &str,
  160. invalid_hash_value_message: &str,
  161. ) {
  162. smol::block_on(async {
  163. // Test for missing `parameter_name` parameter
  164. validate_invalid_rpc_parameter(
  165. explorerd,
  166. method,
  167. &[],
  168. ErrorCode::InvalidParams.code(),
  169. &format!("Parameter '{parameter_name}' at index 0 is missing"),
  170. )
  171. .await;
  172. // Test for invalid `parameter_name` parameter type
  173. validate_invalid_rpc_parameter(
  174. explorerd,
  175. method,
  176. &[JsonValue::Number(123.0)],
  177. ErrorCode::InvalidParams.code(),
  178. &format!("Parameter '{parameter_name}' is not a valid string"),
  179. )
  180. .await;
  181. // Test for invalid `contract_id` value
  182. validate_invalid_rpc_parameter(
  183. explorerd,
  184. method,
  185. &[JsonValue::String("0x0222".to_string())],
  186. ErrorCode::InvalidParams.code(),
  187. &format!("{invalid_hash_value_message}: 0x0222"),
  188. )
  189. .await;
  190. });
  191. }