explorerd/rpc: added test coverage for invalid/missing parameters

This commit expands the test suite by adding RPC tests that cover invalid/missing parameters, unsupported data types, unparsable inputs, and invalid numeric ranges. It also includes a test case to ensure `ping_darkfid` properly handles error when invoked under a disconnected darkfid endpoint.

New test modules include:
- **rpc/mod/tests**: Tests to ensure that `ping_darkfid` correctly handles non-empty parameters and scenarios where it is invoked with a disconnected darkfid endpoint
- **rpc/blocks/tests**: Tests verifying that invalid/missing parameters for fetching blocks produce the expected error responses
- **rpc/transaction/tests**: Tests confirming that incorrect/missing parameters for retrieving transactions are properly handled
- **rpc/contracts/tests**: Tests ensuring invalid/missing parameter handling for retrieving native contracts data
- **rpc/statistics/tests**: Tests confirming that invalid/missing parameters for retrieving statistics result in correct error handling

Other updates:
- Set the default test log level to 'Off' in test_mod.rs
- Removed the `TEST_DATA_DIR` constant until the test cases are updated to run on a copy of the test `explorerdb`
- Removed the `test_data/explorerd_0` folder until the test cases are updated to use a copy of the test `explorerdb`

Running Tests:

cargo test rpc
This commit is contained in:
kalm
2025-03-24 05:39:11 -07:00
parent ba628d0d9d
commit 04209b29e2
33 changed files with 356 additions and 12 deletions

View File

@@ -55,8 +55,6 @@ mod error;
/// Test utilities used for unit and integration testing
#[cfg(test)]
#[allow(dead_code)]
// TODO: Remove when test code that utilizes the new utility functions is checked in
mod test_utils;
const CONFIG_FILE: &str = "explorerd_config.toml";

View File

@@ -156,3 +156,155 @@ impl Explorerd {
}
}
}
#[cfg(test)]
/// Test module for validating the functionality of RPC methods related to explorer blocks.
/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
/// including cases with missing values, unsupported types, invalid ranges, and unparsable inputs.
mod tests {
use tinyjson::JsonValue;
use darkfi::rpc::{
jsonrpc::{ErrorCode, JsonRequest, JsonResult},
server::RequestHandler,
};
use crate::test_utils::{
setup, validate_invalid_rpc_header_hash, validate_invalid_rpc_parameter,
};
#[test]
/// Tests the handling of invalid parameters for the `blocks.get_last_n_blocks` JSON-RPC method.
/// Verifies that missing and an invalid `num_last_blocks` value results in an appropriate error.
fn test_blocks_get_last_n_blocks_invalid_params() {
smol::block_on(async {
// Define rpc_method and parameter names
let rpc_method = "blocks.get_last_n_blocks";
let parameter_name = "num_last_blocks";
// Set up the Explorerd instance
let explorerd = setup();
// Test for missing `start` parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' at index 0 is missing", parameter_name),
)
.await;
// Test for invalid num_last_blocks parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[JsonValue::String("invalid_number".to_string())],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' is not a supported number type", parameter_name),
)
.await;
});
}
#[test]
/// Tests the handling of invalid parameters for the `blocks.get_blocks_in_heights_range`
/// JSON-RPC method. Verifies that invalid/missing `start` or `end` parameter values, or an
/// invalid range where `start` is greater than `end`, result in appropriate errors.
fn test_blocks_get_blocks_in_heights_range_invalid_params() {
smol::block_on(async {
// Define rpc_method and parameter names
let rpc_method = "blocks.get_blocks_in_heights_range";
let start_parameter_name = "start";
let end_parameter_name = "end";
// Set up the Explorerd instance
let explorerd = setup();
// Test for missing `start` parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' at index 0 is missing", start_parameter_name),
)
.await;
// Test for invalid `start` parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[JsonValue::String("invalid_number".to_string()), JsonValue::Number(10.0)],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{start_parameter_name}' is not a supported number type"),
)
.await;
// Test for invalid `end` parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[JsonValue::Number(10.0)],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' at index 1 is missing", end_parameter_name),
)
.await;
// Test for invalid `end` parameter
validate_invalid_rpc_parameter(
&explorerd,
rpc_method,
&[JsonValue::Number(10.0), JsonValue::String("invalid_number".to_string())],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{end_parameter_name}' is not a supported number type"),
)
.await;
// Test invalid range where `start` > `end`
let request = JsonRequest {
id: 1,
jsonrpc: "2.0",
method: rpc_method.to_string(),
params: JsonValue::Array(vec![JsonValue::Number(20.0), JsonValue::Number(10.0)]),
};
let response = explorerd.handle_request(request).await;
// Verify that `start > end` error is raised
match response {
JsonResult::Error(actual_error) => {
let expected_error_code = ErrorCode::InvalidParams.code();
assert_eq!(
actual_error.error.code,
expected_error_code
);
assert_eq!(
actual_error.error.message,
"Invalid range: start (20) cannot be greater than end (10)"
);
}
_ => panic!(
"Expected a JSON error response for method: {rpc_method}, but got something else",
),
}
});
}
#[test]
/// Tests the handling of invalid parameters for the `blocks.get_block_by_hash` JSON-RPC method.
/// Verifies that an invalid `header_hash` value, either a numeric type or invalid hash string,
/// results in appropriate error.
fn test_blocks_get_block_by_hash_invalid_params() {
smol::block_on(async {
// Define the RPC method name
let rpc_method = "blocks.get_block_by_hash";
// Set up the explorerd
let explorerd = setup();
// Validate when provided with an invalid tx hash
validate_invalid_rpc_header_hash(&explorerd, rpc_method);
});
}
}

View File

@@ -126,3 +126,73 @@ impl Explorerd {
}
}
}
#[cfg(test)]
/// Test module for validating the functionality of RPC methods related to explorer contracts.
/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
/// including cases with missing values, unsupported types, and unparsable inputs.
mod tests {
use tinyjson::JsonValue;
use darkfi::rpc::jsonrpc::ErrorCode;
use crate::test_utils::{
setup, validate_empty_rpc_parameters, validate_invalid_rpc_contract_id,
validate_invalid_rpc_parameter,
};
#[test]
/// Tests the `contracts.get_native_contracts` method to ensure it correctly handles cases where
/// empty parameters are supplied, returning an expected result or error response.
fn test_contracts_get_native_contracts_empty_params() {
smol::block_on(async {
validate_empty_rpc_parameters(&setup(), "contracts.get_native_contracts").await;
});
}
#[test]
/// Tests the `contracts.get_contract_source_code_paths` method to ensure it correctly handles cases
/// with invalid or missing `contract_id` parameters, returning appropriate error responses.
fn test_contracts_get_contract_source_code_paths_invalid_params() {
validate_invalid_rpc_contract_id(&setup(), "contracts.get_contract_source_code_paths");
}
#[test]
/// Tests the `contracts.get_contract_source` method to ensure it correctly handles cases
/// with invalid or missing parameters, returning appropriate error responses.
fn test_contracts_get_contract_source_invalid_params() {
let test_method = "contracts.get_contract_source";
let parameter_name = "source_path";
smol::block_on(async {
// Set up the explorerd instance
let explorerd = setup();
validate_invalid_rpc_contract_id(&explorerd, test_method);
// Test for missing `source_path` parameter
validate_invalid_rpc_parameter(
&explorerd,
test_method,
&[JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string())],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' at index 1 is missing", parameter_name),
)
.await;
// Test for invalid `source_path` parameter
validate_invalid_rpc_parameter(
&explorerd,
test_method,
&[
JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string()),
JsonValue::Number(123.0), // Invalid `source_path` type
],
ErrorCode::InvalidParams.code(),
&format!("Parameter '{}' is not a valid string", parameter_name),
)
.await;
});
}
}

View File

@@ -339,3 +339,60 @@ fn log_request_failure(req_method: &str, params: &JsonValue, error: &JsonError)
// Log the error
error!(target: &log_target, "{}", error_message);
}
/// Test module for validating API functions within this `mod.rs` file. It ensures that the core API
/// functions behave as expected and that they handle invalid parameters properly.
#[cfg(test)]
mod tests {
use tinyjson::JsonValue;
use darkfi::rpc::jsonrpc::JsonRequest;
use super::*;
use crate::{
error::ERROR_CODE_PING_DARKFID_FAILED,
test_utils::{setup, validate_empty_rpc_parameters},
};
#[test]
/// Validates the failure scenario of the `ping_darkfid` JSON-RPC method by sending a request
/// to a disconnected darkfid endpoint, ensuring the response is an error with the expected
/// code and message.
fn test_ping_darkfid_failure() {
smol::block_on(async {
// Set up the Explorerd instance
let explorerd = setup();
// Prepare a JSON-RPC request for `ping_darkfid`
let request = JsonRequest {
id: 1,
jsonrpc: "2.0",
method: "ping_darkfid".to_string(),
params: JsonValue::Array(vec![]),
};
// Call `handle_request` on the Explorerd instance
let response = explorerd.handle_request(request).await;
// Verify the response is a `JsonError` with the `PingFailed` error code
match response {
JsonResult::Error(actual_error) => {
let expected_error_code = ERROR_CODE_PING_DARKFID_FAILED;
let expected_error_msg = "Ping darkfid failed: Not connected, is the explorer running in no-sync mode?";
assert_eq!(actual_error.error.code, expected_error_code);
assert_eq!(actual_error.error.message, expected_error_msg);
}
_ => panic!("Expected a JSON object for the response, but got something else"),
}
});
}
/// Tests the `ping_darkfid` method to ensure it correctly handles cases where non-empty parameters
/// are supplied, returning an expected error response.
#[test]
fn test_ping_darkfid_empty_params() {
smol::block_on(async {
validate_empty_rpc_parameters(&setup(), "ping_darkfid").await;
});
}
}

View File

@@ -103,3 +103,30 @@ impl Explorerd {
Ok(statistics.to_json_array())
}
}
#[cfg(test)]
/// Test module for validating the functionality of RPC methods related to explorer statistics.
/// Focuses on ensuring proper error handling for invalid parameters across several use cases.
mod tests {
use crate::test_utils::{setup, validate_empty_rpc_parameters};
/// Tests all RPC-related statistics calls when provided with empty parameters, ensuring they
/// handle the input correctly and return appropriate validation responses.
#[test]
fn test_statistics_rpc_calls_for_empty_parameters() {
smol::block_on(async {
let explorerd = setup();
let rpc_methods = [
"statistics.get_latest_metric_statistics",
"statistics.get_metric_statistics",
"statistics.get_basic_statistics",
];
for rpc_method in rpc_methods.iter() {
validate_empty_rpc_parameters(&explorerd, rpc_method).await;
}
});
}
}

View File

@@ -83,3 +83,45 @@ impl Explorerd {
}
}
}
#[cfg(test)]
/// Test module for validating the functionality of RPC methods related to explorer transactions.
/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
/// including cases with missing values, unsupported types, and unparsable inputs.
mod tests {
use crate::test_utils::{
setup, validate_invalid_rpc_header_hash, validate_invalid_rpc_tx_hash,
};
#[test]
/// Tests the handling of invalid parameters for the `transactions.get_transactions_by_header_hash` JSON-RPC method.
/// Verifies that missing and an invalid `header_hash` value results in an appropriate error.
fn test_transactions_get_transactions_by_header_hash() {
smol::block_on(async {
// Define the RPC method name
let rpc_method = "transactions.get_transactions_by_header_hash";
// Set up the explorerd
let explorerd = setup();
// Validate when provided with an invalid header hash
validate_invalid_rpc_header_hash(&explorerd, rpc_method);
});
}
#[test]
/// Tests the handling of invalid parameters for the `transactions.get_transaction_by_hash` JSON-RPC method.
/// Verifies that missing and an invalid `tx_hash` value results in an appropriate error.
fn test_transactions_get_transaction_by_hash() {
smol::block_on(async {
// Define the RPC method name
let rpc_method = "transactions.get_transaction_by_hash";
// Set up the explorerd
let explorerd = setup();
// Validate when provided with an invalid tx hash
validate_invalid_rpc_tx_hash(&explorerd, rpc_method);
});
}
}

View File

@@ -20,6 +20,7 @@ use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
use smol::Executor;
use tempdir::TempDir;
use tinyjson::JsonValue;
use url::Url;
@@ -30,8 +31,6 @@ use darkfi::rpc::{
use crate::Explorerd;
const TEST_DATA_DIR: &str = "src/test_utils/test_data";
// Defines a global `Explorerd` instance shared across all tests
lazy_static! {
static ref EXPLORERD_INSTANCE: Mutex<Option<Arc<Explorerd>>> = Mutex::new(None);
@@ -73,10 +72,13 @@ pub fn setup() -> Arc<Explorerd> {
if instance.is_none() {
// Initialize logger for the first time
init_logger(simplelog::LevelFilter::Info, vec!["sled", "runtime", "net"]);
init_logger(simplelog::LevelFilter::Off, vec!["sled", "runtime", "net"]);
// Prepare parameters for Explorerd::new
let db_path = format!("{}/explorerd_0", TEST_DATA_DIR);
let temp_dir = TempDir::new("explorerd").expect("Failed to create temp dir");
let db_path_buf = temp_dir.path().join("explorerd_0");
let db_path =
db_path_buf.to_str().expect("Failed to convert db_path to string").to_string();
let darkfid_endpoint = Url::parse("http://127.0.0.1:8240").expect("Invalid URL");
let executor = Arc::new(Executor::new());
@@ -130,12 +132,12 @@ pub async fn validate_invalid_rpc_parameter(
/// Auxiliary function that validates the handling of non-empty parameters when they are supposed
/// to be empty for the given RPC `method`. It uses the provided [`Explorerd`] instance to ensure
/// that unexpected non-empty parameters result in the expected error for invalid parameters.
pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: String) {
pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: &str) {
// Prepare a JSON-RPC request for `ping_darkfid`
let request = JsonRequest {
id: 1,
jsonrpc: "2.0",
method,
method: method.to_string(),
params: JsonValue::Array(vec![JsonValue::String("non_empty_param".to_string())]),
};

View File

@@ -1,4 +0,0 @@
segment_size: 524288
use_compression: false
version: 0.34
vQ<>