116 lines
5.4 KiB
Rust
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"));
|
|
}
|