Files
khadhroony-solana-project/crates/ksp-onchain-transport-lib/unit_tests/json_rpc.rs
2026-08-20 09:24:10 +02:00

116 lines
5.4 KiB
Rust

// file: crates/ksp-onchain-transport-lib/unit_tests/json_rpc.rs
// version: 3
#[test]
fn request_serialization_matches_json_rpc_2_0_shape() {
let request = crate::JsonRpcRequest::new(7, "getBalance", std::vec![serde_json::json!("Address111"), serde_json::json!({"commitment":"confirmed"})])
.expect("valid request must construct");
let encoded = request.to_json_string().expect("serializable request must encode");
let value: serde_json::Value = serde_json::from_str(encoded.as_str()).expect("encoded request must remain JSON");
assert_eq!(value["jsonrpc"], serde_json::json!("2.0"));
assert_eq!(value["id"], serde_json::json!(7));
assert_eq!(value["method"], serde_json::json!("getBalance"));
assert_eq!(value["params"].as_array().expect("params must be an array").len(), 2);
}
#[test]
fn request_rejects_empty_or_untrimmed_method() {
assert!(crate::JsonRpcRequest::new(1, "", std::vec![]).is_err());
assert!(crate::JsonRpcRequest::new(1, " getHealth", std::vec![]).is_err());
}
#[test]
fn response_parser_preserves_null_success_result() {
let response = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"2.0","result":null,"id":9}"#, 9).expect("null result is a valid success payload");
assert!(matches!(&response, crate::JsonRpcResponse::Success(_)), "success response must not parse as error");
if let crate::JsonRpcResponse::Success(success) = response {
assert!(success.result().is_null());
}
}
#[test]
fn response_parser_preserves_rpc_error_payload() {
let response = crate::parse_json_rpc_response_text(
r#"{"jsonrpc":"2.0","error":{"code":-32005,"message":"Node is unhealthy","data":{"numSlotsBehind":12}},"id":4}"#,
4,
)
.expect("valid JSON-RPC error envelope must parse");
assert!(matches!(&response, crate::JsonRpcResponse::Error(_)), "RPC error response must not parse as success");
if let crate::JsonRpcResponse::Error(error_response) = response {
assert_eq!(error_response.error().code(), -32005);
assert_eq!(error_response.error().message(), "Node is unhealthy");
assert_eq!(error_response.error().data(), std::option::Option::Some(&serde_json::json!({"numSlotsBehind":12})));
}
}
#[test]
fn response_parser_rejects_id_mismatch() {
let error = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"2.0","result":"ok","id":2}"#, 1).expect_err("mismatched id must fail");
assert_eq!(error.code(), crate::ERROR_CODE_JSON_RPC_PROTOCOL_INVALID);
}
#[test]
fn response_parser_rejects_wrong_protocol_version() {
let error = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"1.0","result":"ok","id":1}"#, 1).expect_err("wrong version must fail");
assert_eq!(error.code(), crate::ERROR_CODE_JSON_RPC_PROTOCOL_INVALID);
}
#[test]
fn response_parser_rejects_both_result_and_error() {
let error = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"2.0","result":"ok","error":{"code":-1,"message":"bad"},"id":1}"#, 1)
.expect_err("mutually exclusive fields must fail");
assert_eq!(error.code(), crate::ERROR_CODE_JSON_RPC_PROTOCOL_INVALID);
}
#[test]
fn response_parser_rejects_missing_result_and_error() {
let error = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"2.0","id":1}"#, 1).expect_err("missing outcome must fail");
assert_eq!(error.code(), crate::ERROR_CODE_JSON_RPC_PROTOCOL_INVALID);
}
#[test]
fn response_parser_distinguishes_invalid_json_from_protocol_error() {
let error = crate::parse_json_rpc_response_text("not-json", 1).expect_err("invalid JSON must fail decoding");
assert_eq!(error.code(), crate::ERROR_CODE_JSON_DECODE_FAILED);
}
#[test]
fn rpc_error_maps_to_shared_ksp_error_without_copying_remote_payload_into_context() {
let response = crate::parse_json_rpc_response_text(
r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"SECRET-CANARY","data":{"payload":"SECRET-DATA"}},"id":1}"#,
1,
)
.expect("valid error envelope must parse");
let error = response.into_result().expect_err("RPC application error must map to KSP error");
assert_eq!(error.code(), crate::ERROR_CODE_RPC_APPLICATION_ERROR);
let rendered = format!("{error:?}");
assert!(!rendered.contains("SECRET-CANARY"));
assert!(!rendered.contains("SECRET-DATA"));
}
#[test]
fn request_debug_omits_parameter_payloads() {
let request = crate::JsonRpcRequest::new(1, "sendTransaction", std::vec![serde_json::json!("SIGNED-TRANSACTION-SECRET-CANARY")])
.expect("test request must construct");
let rendered = format!("{request:?}");
assert!(rendered.contains("sendTransaction"));
assert!(rendered.contains("param_count"));
assert!(!rendered.contains("SIGNED-TRANSACTION-SECRET-CANARY"));
}
#[test]
fn response_debug_omits_result_and_remote_error_payloads() {
let success = crate::parse_json_rpc_response_text(r#"{"jsonrpc":"2.0","result":{"secret":"RESULT-SECRET-CANARY"},"id":1}"#, 1)
.expect("test success response must parse");
let success_rendered = format!("{success:?}");
assert!(!success_rendered.contains("RESULT-SECRET-CANARY"));
let failure = crate::parse_json_rpc_response_text(
r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"MESSAGE-SECRET-CANARY","data":{"secret":"DATA-SECRET-CANARY"}},"id":2}"#,
2,
)
.expect("test error response must parse");
let failure_rendered = format!("{failure:?}");
assert!(!failure_rendered.contains("MESSAGE-SECRET-CANARY"));
assert!(!failure_rendered.contains("DATA-SECRET-CANARY"));
}