//! Tests and utilities.
#![allow(
clippy::unreadable_literal,
clippy::manual_string_new,
clippy::struct_field_names
)]
//---------------------------------------------------------------------------------------------------- Use
use std::borrow::Cow;
use pretty_assertions::assert_eq;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{to_string, to_string_pretty, to_value, Value};
use crate::{Id, Request, Response};
//---------------------------------------------------------------------------------------------------- Body
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) struct Body
{
pub(crate) method: Cow<'static, str>,
pub(crate) params: P,
}
//---------------------------------------------------------------------------------------------------- Free functions
/// Assert input and output of serialization are the same.
pub(crate) fn assert_ser(t: &T, expected_value: &Value)
where
T: Serialize + std::fmt::Debug + Clone + PartialEq,
{
let value = to_value(t).unwrap();
assert_eq!(value, *expected_value);
}
/// Assert input and output of string serialization are the same.
pub(crate) fn assert_ser_string(t: &T, expected_string: &str)
where
T: Serialize + std::fmt::Debug + Clone + PartialEq,
{
let string = to_string(t).unwrap();
assert_eq!(string, expected_string);
}
/// Assert input and output of (pretty) string serialization are the same.
pub(crate) fn assert_ser_string_pretty(t: &T, expected_string: &str)
where
T: Serialize + std::fmt::Debug + Clone + PartialEq,
{
let string = to_string_pretty(t).unwrap();
assert_eq!(string, expected_string);
}
/// Tests an input JSON string matches an expected type `T`.
#[expect(clippy::needless_pass_by_value, reason = "serde signature")]
fn assert_de(json: &'static str, expected: T)
where
T: DeserializeOwned + std::fmt::Debug + Clone + PartialEq,
{
let t = serde_json::from_str::(json).unwrap();
assert_eq!(t, expected);
}
//---------------------------------------------------------------------------------------------------- Types
// Parameter type.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct GetBlock {
height: u64,
}
// Method enum containing all params.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
enum Methods {
GetBlock(GetBlock),
GetBlockCount,
}
//---------------------------------------------------------------------------------------------------- TESTS
/// Tests that Monero's `get_block` request and response
/// in JSON string form gets correctly (de)serialized.
#[test]
fn monero_jsonrpc_get_block() {
//--- Request
const REQUEST: &str =
r#"{"jsonrpc":"2.0","id":"0","method":"get_block","params":{"height":123}}"#;
let request = Request::new_with_id(
Id::Str("0".into()),
Methods::GetBlock(GetBlock { height: 123 }),
);
assert_ser_string(&request, REQUEST);
assert_de(REQUEST, request);
//--- Response
const RESPONSE: &str = r#"{
"jsonrpc": "2.0",
"id": "0",
"result": {
"blob": "01008cb1c49a0572244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5aaa8f2ba01b70101ff7b08ebcc2202f917ac2dc38c0e0735f2c97df4a307a445b32abaf0ad528c385ae11a7e767d3880897a0215a39af4cf4c67136ecc048d9296b4cb7a6be61275a0ef207eb4cbb427cc216380dac40902dabddeaada9f4ed2512f9b9613a7ced79d3996ad5050ca542f31032bd638193380c2d72f02965651ab4a26264253bb8a4ccb9b33afbc8c8b4f3e331baf50537b8ee80364038088aca3cf020235a7367536243629560b8a40f104352c89a2d4719e86f54175c2e4e3ecfec9938090cad2c60e023b623a01eace71e9b37d2bfac84f9aafc85dbf62a0f452446c5de0ca50cf910580e08d84ddcb010258c370ee02069943e5440294aeafae29656f9782b0a565d26065bb7af07a6af980c0caf384a30202899a53eeb05852a912bcbc6fa78e4c85f0b059726b0b8f0753e7aa54fc9d7ce82101351af203765d1679e2a9458ab6737d289e18c49766d41fc31a2bf0fe32dd196200",
"block_header": {
"block_size": 345,
"block_weight": 345,
"cumulative_difficulty": 4646953,
"cumulative_difficulty_top64": 0,
"depth": 3166236,
"difficulty": 51263,
"difficulty_top64": 0,
"hash": "ff617b489e91f5db76f6f2cc9b3f03236e09fb191b4238f4a1e64185a6c28019",
"height": 123,
"long_term_weight": 345,
"major_version": 1,
"miner_tx_hash": "054ba33024e72dfe8dafabc8af7c0e070e9aca3a9df44569cfa3c96669f9542f",
"minor_version": 0,
"nonce": 3136465066,
"num_txes": 0,
"orphan_status": false,
"pow_hash": "",
"prev_hash": "72244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5",
"reward": 17590122566891,
"timestamp": 1397823628,
"wide_cumulative_difficulty": "0x46e829",
"wide_difficulty": "0xc83f"
},
"credits": 0,
"json": "{\n \"major_version\": 1, \n \"minor_version\": 0, \n \"timestamp\": 1397823628, \n \"prev_id\": \"72244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5\", \n \"nonce\": 3136465066, \n \"miner_tx\": {\n \"version\": 1, \n \"unlock_time\": 183, \n \"vin\": [ {\n \"gen\": {\n \"height\": 123\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 566891, \n \"target\": {\n \"key\": \"f917ac2dc38c0e0735f2c97df4a307a445b32abaf0ad528c385ae11a7e767d38\"\n }\n }, {\n \"amount\": 2000000, \n \"target\": {\n \"key\": \"15a39af4cf4c67136ecc048d9296b4cb7a6be61275a0ef207eb4cbb427cc2163\"\n }\n }, {\n \"amount\": 20000000, \n \"target\": {\n \"key\": \"dabddeaada9f4ed2512f9b9613a7ced79d3996ad5050ca542f31032bd6381933\"\n }\n }, {\n \"amount\": 100000000, \n \"target\": {\n \"key\": \"965651ab4a26264253bb8a4ccb9b33afbc8c8b4f3e331baf50537b8ee8036403\"\n }\n }, {\n \"amount\": 90000000000, \n \"target\": {\n \"key\": \"35a7367536243629560b8a40f104352c89a2d4719e86f54175c2e4e3ecfec993\"\n }\n }, {\n \"amount\": 500000000000, \n \"target\": {\n \"key\": \"3b623a01eace71e9b37d2bfac84f9aafc85dbf62a0f452446c5de0ca50cf9105\"\n }\n }, {\n \"amount\": 7000000000000, \n \"target\": {\n \"key\": \"58c370ee02069943e5440294aeafae29656f9782b0a565d26065bb7af07a6af9\"\n }\n }, {\n \"amount\": 10000000000000, \n \"target\": {\n \"key\": \"899a53eeb05852a912bcbc6fa78e4c85f0b059726b0b8f0753e7aa54fc9d7ce8\"\n }\n }\n ], \n \"extra\": [ 1, 53, 26, 242, 3, 118, 93, 22, 121, 226, 169, 69, 138, 182, 115, 125, 40, 158, 24, 196, 151, 102, 212, 31, 195, 26, 43, 240, 254, 50, 221, 25, 98\n ], \n \"signatures\": [ ]\n }, \n \"tx_hashes\": [ ]\n}",
"miner_tx_hash": "054ba33024e72dfe8dafabc8af7c0e070e9aca3a9df44569cfa3c96669f9542f",
"status": "OK",
"top_hash": "",
"untrusted": false
}
}"#;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct Json {
blob: String,
block_header: BlockHeader,
credits: u64,
json: String,
miner_tx_hash: String,
status: String,
top_hash: String,
untrusted: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct BlockHeader {
block_size: u64,
block_weight: u64,
cumulative_difficulty: u64,
cumulative_difficulty_top64: u64,
depth: u64,
difficulty: u64,
difficulty_top64: u64,
hash: String,
height: u64,
long_term_weight: u64,
major_version: u64,
miner_tx_hash: String,
minor_version: u64,
nonce: u64,
num_txes: u64,
orphan_status: bool,
pow_hash: String,
prev_hash: String,
reward: u64,
timestamp: u64,
wide_cumulative_difficulty: String,
wide_difficulty: String,
}
let payload = Json {
blob: "01008cb1c49a0572244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5aaa8f2ba01b70101ff7b08ebcc2202f917ac2dc38c0e0735f2c97df4a307a445b32abaf0ad528c385ae11a7e767d3880897a0215a39af4cf4c67136ecc048d9296b4cb7a6be61275a0ef207eb4cbb427cc216380dac40902dabddeaada9f4ed2512f9b9613a7ced79d3996ad5050ca542f31032bd638193380c2d72f02965651ab4a26264253bb8a4ccb9b33afbc8c8b4f3e331baf50537b8ee80364038088aca3cf020235a7367536243629560b8a40f104352c89a2d4719e86f54175c2e4e3ecfec9938090cad2c60e023b623a01eace71e9b37d2bfac84f9aafc85dbf62a0f452446c5de0ca50cf910580e08d84ddcb010258c370ee02069943e5440294aeafae29656f9782b0a565d26065bb7af07a6af980c0caf384a30202899a53eeb05852a912bcbc6fa78e4c85f0b059726b0b8f0753e7aa54fc9d7ce82101351af203765d1679e2a9458ab6737d289e18c49766d41fc31a2bf0fe32dd196200".into(),
block_header: BlockHeader {
block_size: 345,
block_weight: 345,
cumulative_difficulty: 4646953,
cumulative_difficulty_top64: 0,
depth: 3166236,
difficulty: 51263,
difficulty_top64: 0,
hash: "ff617b489e91f5db76f6f2cc9b3f03236e09fb191b4238f4a1e64185a6c28019".into(),
height: 123,
long_term_weight: 345,
major_version: 1,
miner_tx_hash: "054ba33024e72dfe8dafabc8af7c0e070e9aca3a9df44569cfa3c96669f9542f".into(),
minor_version: 0,
nonce: 3136465066,
num_txes: 0,
orphan_status: false,
pow_hash: "".into(),
prev_hash: "72244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5".into(),
reward: 17590122566891,
timestamp: 1397823628,
wide_cumulative_difficulty: "0x46e829".into(),
wide_difficulty: "0xc83f".into(),
},
credits: 0,
json: "{\n \"major_version\": 1, \n \"minor_version\": 0, \n \"timestamp\": 1397823628, \n \"prev_id\": \"72244e0c8b2b8b99236e10c03eba53685b346aab525eb20b59a459b5935cd5a5\", \n \"nonce\": 3136465066, \n \"miner_tx\": {\n \"version\": 1, \n \"unlock_time\": 183, \n \"vin\": [ {\n \"gen\": {\n \"height\": 123\n }\n }\n ], \n \"vout\": [ {\n \"amount\": 566891, \n \"target\": {\n \"key\": \"f917ac2dc38c0e0735f2c97df4a307a445b32abaf0ad528c385ae11a7e767d38\"\n }\n }, {\n \"amount\": 2000000, \n \"target\": {\n \"key\": \"15a39af4cf4c67136ecc048d9296b4cb7a6be61275a0ef207eb4cbb427cc2163\"\n }\n }, {\n \"amount\": 20000000, \n \"target\": {\n \"key\": \"dabddeaada9f4ed2512f9b9613a7ced79d3996ad5050ca542f31032bd6381933\"\n }\n }, {\n \"amount\": 100000000, \n \"target\": {\n \"key\": \"965651ab4a26264253bb8a4ccb9b33afbc8c8b4f3e331baf50537b8ee8036403\"\n }\n }, {\n \"amount\": 90000000000, \n \"target\": {\n \"key\": \"35a7367536243629560b8a40f104352c89a2d4719e86f54175c2e4e3ecfec993\"\n }\n }, {\n \"amount\": 500000000000, \n \"target\": {\n \"key\": \"3b623a01eace71e9b37d2bfac84f9aafc85dbf62a0f452446c5de0ca50cf9105\"\n }\n }, {\n \"amount\": 7000000000000, \n \"target\": {\n \"key\": \"58c370ee02069943e5440294aeafae29656f9782b0a565d26065bb7af07a6af9\"\n }\n }, {\n \"amount\": 10000000000000, \n \"target\": {\n \"key\": \"899a53eeb05852a912bcbc6fa78e4c85f0b059726b0b8f0753e7aa54fc9d7ce8\"\n }\n }\n ], \n \"extra\": [ 1, 53, 26, 242, 3, 118, 93, 22, 121, 226, 169, 69, 138, 182, 115, 125, 40, 158, 24, 196, 151, 102, 212, 31, 195, 26, 43, 240, 254, 50, 221, 25, 98\n ], \n \"signatures\": [ ]\n }, \n \"tx_hashes\": [ ]\n}".into(),
miner_tx_hash: "054ba33024e72dfe8dafabc8af7c0e070e9aca3a9df44569cfa3c96669f9542f".into(),
status: "OK".into(),
top_hash: "".into(),
untrusted: false
};
let response = Response::ok(Id::Str("0".into()), payload);
assert_ser_string_pretty(&response, RESPONSE);
assert_de(RESPONSE, response);
}
/// Tests that Monero's `get_block_count` request and response
/// in JSON string form gets correctly (de)serialized.
#[test]
fn monero_jsonrpc_get_block_count() {
//--- Request
const REQUEST: &str = r#"{"jsonrpc":"2.0","id":0,"method":"get_block_count"}"#;
let request = Request::new_with_id(Id::Num(0), Methods::GetBlockCount);
assert_ser_string(&request, REQUEST);
assert_de(REQUEST, request);
//--- Response
const RESPONSE: &str = r#"{
"jsonrpc": "2.0",
"id": 0,
"result": {
"count": 3166375,
"status": "OK",
"untrusted": false
}
}"#;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct Json {
count: u64,
status: String,
untrusted: bool,
}
let payload = Json {
count: 3166375,
status: "OK".into(),
untrusted: false,
};
let response = Response::ok(Id::Num(0), payload);
assert_ser_string_pretty(&response, RESPONSE);
assert_de(RESPONSE, response);
}