test(grpc): Add snapshots (#8277)

* add grpc snapshot tests

* replaces ScanService with MockService in snapshot tests

* removes dev-dep in zebra-grpc on zebra-scan and updates snapshots

* Apply suggestions from code review

Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2024-02-16 16:41:04 -03:00 committed by GitHub
parent f20834c373
commit fbddec3cb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 251 additions and 1 deletions

View File

@ -5815,13 +5815,18 @@ version = "0.1.0-alpha.1"
dependencies = [
"color-eyre",
"futures-util",
"insta",
"prost",
"serde",
"tokio",
"tonic 0.11.0",
"tonic-build 0.11.0",
"tower",
"zcash_primitives",
"zebra-chain",
"zebra-node-services",
"zebra-state",
"zebra-test",
]
[[package]]

View File

@ -19,6 +19,7 @@ categories = ["cryptography::cryptocurrencies"]
futures-util = "0.3.28"
tonic = "0.11.0"
prost = "0.12.3"
serde = { version = "1.0.196", features = ["serde_derive"] }
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
tower = { version = "0.4.13", features = ["util", "buffer"] }
color-eyre = "0.6.2"
@ -29,3 +30,11 @@ zebra-node-services = { path = "../zebra-node-services", version = "1.0.0-beta.3
[build-dependencies]
tonic-build = "0.11.0"
[dev-dependencies]
insta = { version = "1.33.0", features = ["redactions", "json", "ron"] }
zebra-chain = { path = "../zebra-chain", features = ["proptest-impl"] }
zebra-state = { path = "../zebra-state" }
zebra-test = { path = "../zebra-test" }

View File

@ -3,6 +3,7 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::configure()
.btree_map(["."])
.type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]")
.compile(&["proto/scanner.proto"], &[""])?;
Ok(())
}

View File

@ -6,6 +6,9 @@
pub mod server;
#[cfg(test)]
mod tests;
/// The generated scanner proto
pub mod scanner {
tonic::include_proto!("scanner");

1
zebra-grpc/src/tests.rs Normal file
View File

@ -0,0 +1 @@
mod snapshot;

View File

@ -0,0 +1,151 @@
//! Snapshot tests for Zebra Scan gRPC responses.
//!
//! Currently we snapshot the `get_info` and `get_results` responses for both mainnet and testnet with a
//! mocked scanner database. Calls that return `Empty` responses are not snapshoted in this suite.
//!
//! To update these snapshots, run:
//! ```sh
//! cargo insta test --review --delete-unreferenced-snapshots
//! ```
use std::{collections::BTreeMap, thread::sleep, time::Duration};
use zebra_chain::{block::Height, parameters::Network, transaction};
use zebra_test::mock_service::MockService;
use zebra_node_services::scan_service::{
request::Request as ScanRequest, response::Response as ScanResponse,
};
use crate::{
scanner::{
scanner_client::ScannerClient, Empty, GetResultsRequest, GetResultsResponse, InfoReply,
},
server::init,
};
/// The extended Sapling viewing key of [ZECpages](https://zecpages.com/boardinfo)
pub const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz";
#[tokio::test(flavor = "multi_thread")]
async fn test_grpc_response_data() {
let _init_guard = zebra_test::init();
tokio::join!(
test_mocked_rpc_response_data_for_network(
Network::Mainnet,
zebra_test::net::random_known_port()
),
test_mocked_rpc_response_data_for_network(
Network::Testnet,
zebra_test::net::random_known_port()
),
);
}
async fn test_mocked_rpc_response_data_for_network(network: Network, random_port: u16) {
// get a mocked scan service
let mock_scan_service = MockService::build().for_unit_tests();
// start the gRPC server
let listen_addr: std::net::SocketAddr = format!("127.0.0.1:{random_port}")
.parse()
.expect("hard-coded IP and u16 port should parse successfully");
{
let mock_scan_service = mock_scan_service.clone();
tokio::spawn(async move {
init(listen_addr, mock_scan_service)
.await
.expect("Possible port conflict");
});
}
// wait for the server to start
sleep(Duration::from_secs(1));
// connect to the gRPC server
let client = ScannerClient::connect(format!("http://127.0.0.1:{random_port}"))
.await
.expect("server should receive connection");
// insta settings
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(network.lowercase_name());
// snapshot the get_info grpc call
let get_info_response_fut = {
let mut client = client.clone();
let get_info_request = tonic::Request::new(Empty {});
tokio::spawn(async move { client.get_info(get_info_request).await })
};
{
let mut mock_scan_service = mock_scan_service.clone();
tokio::spawn(async move {
mock_scan_service
.expect_request_that(|req| matches!(req, ScanRequest::Info))
.await
.respond(ScanResponse::Info {
min_sapling_birthday_height: network.sapling_activation_height(),
})
});
}
// snapshot the get_info grpc call
let get_info_response = get_info_response_fut
.await
.expect("tokio task should join successfully")
.expect("get_info request should succeed");
snapshot_rpc_getinfo(get_info_response.into_inner(), &settings);
// snapshot the get_results grpc call
let get_results_response_fut = {
let mut client = client.clone();
let get_results_request = tonic::Request::new(GetResultsRequest {
keys: vec![ZECPAGES_SAPLING_VIEWING_KEY.to_string()],
});
tokio::spawn(async move { client.get_results(get_results_request).await })
};
{
let mut mock_scan_service = mock_scan_service.clone();
tokio::spawn(async move {
let zec_pages_sapling_efvk = ZECPAGES_SAPLING_VIEWING_KEY.to_string();
let mut fake_results = BTreeMap::new();
for fake_result_height in [Height::MIN, Height(1), Height::MAX] {
fake_results.insert(
fake_result_height,
[transaction::Hash::from([0; 32])].repeat(3),
);
}
let mut fake_results_response = BTreeMap::new();
fake_results_response.insert(zec_pages_sapling_efvk, fake_results);
mock_scan_service
.expect_request_that(|req| matches!(req, ScanRequest::Results(_)))
.await
.respond(ScanResponse::Results(fake_results_response))
});
}
let get_results_response = get_results_response_fut
.await
.expect("tokio task should join successfully")
.expect("get_results request should succeed");
snapshot_rpc_getresults(get_results_response.into_inner(), &settings);
}
/// Snapshot `getinfo` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getinfo(info: InfoReply, settings: &insta::Settings) {
settings.bind(|| insta::assert_json_snapshot!("get_info", info));
}
/// Snapshot `getresults` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getresults(results: GetResultsResponse, settings: &insta::Settings) {
settings.bind(|| insta::assert_json_snapshot!("get_results", results));
}

View File

@ -0,0 +1,7 @@
---
source: zebra-grpc/src/tests/snapshot.rs
expression: info
---
{
"min_sapling_birthday_height": 419200
}

View File

@ -0,0 +1,7 @@
---
source: zebra-grpc/src/tests/snapshot.rs
expression: info
---
{
"min_sapling_birthday_height": 280000
}

View File

@ -0,0 +1,33 @@
---
source: zebra-grpc/src/tests/snapshot.rs
expression: results
---
{
"results": {
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": {
"transactions": {
"0": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
},
"1": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
},
"2147483647": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
}
}
}
}
}

View File

@ -0,0 +1,33 @@
---
source: zebra-grpc/src/tests/snapshot.rs
expression: results
---
{
"results": {
"zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz": {
"transactions": {
"0": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
},
"1": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
},
"2147483647": {
"hash": [
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
]
}
}
}
}
}

View File

@ -159,7 +159,7 @@ impl Storage {
/// Inserts a batch of scanned sapling result for a key and height.
/// If a result already exists for that key, height, and index, it is replaced.
pub(crate) fn insert_sapling_results(
pub fn insert_sapling_results(
&mut self,
sapling_key: &SaplingScanningKey,
height: Height,