Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(grpc-mock): restructure and add mock grpc tests #1579

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/astria-grpc-mock-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
prost.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio = { workspace = true, features = [
"macros",
"rt",
Expand Down
3 changes: 1 addition & 2 deletions crates/astria-grpc-mock-test/proto/health.proto
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to not change this file because it's taken from a well known service.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in 4b15aa0, but needed a type with more fields to adequately test message_partial_pbjson so I added a new struct MockMessage to test_utils. Let me know if this works for you!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't mean for you to implement this manually (although I am sure it was a good exercise to learn about prost and manual serde impls).

What if you you copy the echo server over (from here: https://github.com/hyperium/tonic/blob/master/examples/proto/echo/echo.proto) and just generate extra code?

That way we don't have to worry about potential breakage should we ever update prost or tonic.

Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ option java_outer_classname = "HealthProto";
option java_package = "io.grpc.health.v1";

message HealthCheckRequest {
string name = 1;
string service = 2;
string service = 1;
}

message HealthCheckResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HealthCheckRequest {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub service: ::prost::alloc::string::String,
}
impl ::prost::Name for HealthCheckRequest {
Expand Down
17 changes: 0 additions & 17 deletions crates/astria-grpc-mock-test/src/generated/grpc.health.v1.serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@ impl serde::Serialize for HealthCheckRequest {
{
use serde::ser::SerializeStruct;
let mut len = 0;
if !self.name.is_empty() {
len += 1;
}
if !self.service.is_empty() {
len += 1;
}
let mut struct_ser = serializer.serialize_struct("grpc.health.v1.HealthCheckRequest", len)?;
if !self.name.is_empty() {
struct_ser.serialize_field("name", &self.name)?;
}
if !self.service.is_empty() {
struct_ser.serialize_field("service", &self.service)?;
}
Expand All @@ -29,13 +23,11 @@ impl<'de> serde::Deserialize<'de> for HealthCheckRequest {
D: serde::Deserializer<'de>,
{
const FIELDS: &[&str] = &[
"name",
"service",
];

#[allow(clippy::enum_variant_names)]
enum GeneratedField {
Name,
Service,
}
impl<'de> serde::Deserialize<'de> for GeneratedField {
Expand All @@ -58,7 +50,6 @@ impl<'de> serde::Deserialize<'de> for HealthCheckRequest {
E: serde::de::Error,
{
match value {
"name" => Ok(GeneratedField::Name),
"service" => Ok(GeneratedField::Service),
_ => Err(serde::de::Error::unknown_field(value, FIELDS)),
}
Expand All @@ -79,16 +70,9 @@ impl<'de> serde::Deserialize<'de> for HealthCheckRequest {
where
V: serde::de::MapAccess<'de>,
{
let mut name__ = None;
let mut service__ = None;
while let Some(k) = map_.next_key()? {
match k {
GeneratedField::Name => {
if name__.is_some() {
return Err(serde::de::Error::duplicate_field("name"));
}
name__ = Some(map_.next_value()?);
}
GeneratedField::Service => {
if service__.is_some() {
return Err(serde::de::Error::duplicate_field("service"));
Expand All @@ -98,7 +82,6 @@ impl<'de> serde::Deserialize<'de> for HealthCheckRequest {
}
}
Ok(HealthCheckRequest {
name: name__.unwrap_or_default(),
service: service__.unwrap_or_default(),
})
}
Expand Down
Binary file modified crates/astria-grpc-mock-test/src/generated/grpc_health_v1.bin
Binary file not shown.
65 changes: 27 additions & 38 deletions crates/astria-grpc-mock-test/tests/health/test_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use astria_grpc_mock_test::health::{
HealthCheckResponse,
};

use crate::test_utils::start_mock_server;
use crate::test_utils::{
start_mock_server,
MockMessage,
};

#[tokio::test]
async fn exact_pbjson_match_works() {
Expand All @@ -18,7 +21,6 @@ async fn exact_pbjson_match_works() {
.await
.unwrap();
let expected_request = HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
Expand All @@ -29,7 +31,6 @@ async fn exact_pbjson_match_works() {
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await
Expand All @@ -40,24 +41,20 @@ async fn exact_pbjson_match_works() {
#[tokio::test]
async fn partial_pbjson_match_works() {
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let expected_request = HealthCheckRequest {
name: "helloworld".to_string(),
service: String::new(),
let expected_request = MockMessage {
field_one: "helloworld".to_string(),
field_two: String::new(),
};
let expected_response = HealthCheckResponse {
status: 1,
let expected_response = MockMessage {
field_one: "helloworld".to_string(),
field_two: "helloworld".to_string(),
};
let mock = Mock::for_rpc_given("check", matcher::message_partial_pbjson(&expected_request))
.respond_with(response::constant_response(expected_response.clone()));
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
let rsp = server
.mocked
.handle_request("check", tonic::Request::new(expected_response.clone()))
.await
.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
Expand All @@ -66,19 +63,17 @@ async fn partial_pbjson_match_works() {
#[tokio::test]
async fn and_combinator_works_with_partial_pbjson() {
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let expected_request_1 = HealthCheckRequest {
name: "helloworld".to_string(),
service: String::new(),
let expected_request_1 = MockMessage {
field_one: "helloworld".to_string(),
field_two: String::new(),
};
let expected_request_2 = HealthCheckRequest {
name: String::new(),
service: "helloworld".to_string(),
let expected_request_2 = MockMessage {
field_one: String::new(),
field_two: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
status: 1,
let expected_response = MockMessage {
field_one: "helloworld".to_string(),
field_two: "helloworld".to_string(),
};
let mock = Mock::for_rpc_given(
"check",
Expand All @@ -87,11 +82,9 @@ async fn and_combinator_works_with_partial_pbjson() {
.and(matcher::message_partial_pbjson(&expected_request_2))
.respond_with(response::constant_response(expected_response.clone()));
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
let rsp = server
.mocked
.handle_request("check", tonic::Request::new(expected_response.clone()))
.await
.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
Expand All @@ -104,7 +97,6 @@ async fn exact_pbjson_matcher_doesnt_match_incorrect_request() {
.await
.unwrap();
let expected_request = HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
Expand All @@ -115,7 +107,6 @@ async fn exact_pbjson_matcher_doesnt_match_incorrect_request() {
server.mocked.register(mock).await;
let err_rsp = client
.check(HealthCheckRequest {
name: "helloworld_wrong".to_string(),
service: "helloworld_wrong".to_string(),
})
.await
Expand All @@ -130,8 +121,7 @@ async fn partial_pbjson_match_doesnt_match_incorrect_request() {
.await
.unwrap();
let expected_request = HealthCheckRequest {
name: "helloworld".to_string(),
service: String::new(),
service: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
status: 1,
Expand All @@ -141,8 +131,7 @@ async fn partial_pbjson_match_doesnt_match_incorrect_request() {
server.mocked.register(mock).await;
let err_rsp = client
.check(HealthCheckRequest {
name: "helloworld_wrong".to_string(),
service: "helloworld".to_string(),
service: "hello".to_string(),
})
.await
.unwrap_err();
Expand Down
7 changes: 0 additions & 7 deletions crates/astria-grpc-mock-test/tests/health/test_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ async fn mock_expect_two_works() {
let two_checks = async move {
let res_one = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await?;

let res_two = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await?;
Expand Down Expand Up @@ -64,7 +62,6 @@ async fn response_guard_wait_until_satisfied_works() {

let guard = server.mocked.register_as_scoped(mock).await;
let check = client.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
});

Expand All @@ -88,7 +85,6 @@ async fn up_to_n_times_works_as_expected() {

let guard = server.mocked.register_as_scoped(mock).await;
let check = client.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
});

Expand All @@ -98,7 +94,6 @@ async fn up_to_n_times_works_as_expected() {

let err_rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await
Expand All @@ -118,7 +113,6 @@ async fn incorrect_mock_response_fails_server() {
server.mocked.register(mock).await;
let _ = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await;
Expand All @@ -136,7 +130,6 @@ async fn incorrect_mock_response_fails_guard() {

let guard = server.mocked.register_as_scoped(mock).await;
let check = client.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
});

Expand Down
41 changes: 18 additions & 23 deletions crates/astria-grpc-mock-test/tests/health/test_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use astria_grpc_mock_test::health::{
HealthCheckRequest,
HealthCheckResponse,
};
use futures::future::join;
use tokio::time::timeout;

use crate::test_utils::start_mock_server;
Expand All @@ -26,7 +25,6 @@ async fn default_response_works() {
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await
Expand All @@ -48,7 +46,6 @@ async fn constant_response_works() {
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
.await
Expand All @@ -71,7 +68,6 @@ async fn dynamic_response_works() {
server.mocked.register(mock).await;
let rsp_1 = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "1".to_string(),
})
.await
Expand All @@ -81,7 +77,6 @@ async fn dynamic_response_works() {
expected_response.status = 2;
let rsp_2 = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "2".to_string(),
})
.await
Expand All @@ -105,33 +100,33 @@ fn dynamic_responder(request: &HealthCheckRequest) -> HealthCheckResponse {
#[tokio::test]
async fn response_delay_works_as_expected() {
ethanoroshiba marked this conversation as resolved.
Show resolved Hide resolved
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
let mut err_client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let mut ok_client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let mock = Mock::for_rpc_given("check", matcher::message_type::<HealthCheckRequest>())
.respond_with(
response::default_response::<HealthCheckResponse>()
.set_delay(Duration::from_millis(250)),
ethanoroshiba marked this conversation as resolved.
Show resolved Hide resolved
);
let mock_guard = server.mocked.register_as_scoped(mock).await;
let rsp_fut = client.check(HealthCheckRequest {
name: "helloworld".to_string(),
mock.mount(&server.mocked).await;

let rsp_fut_expect_err = err_client.check(HealthCheckRequest {
service: "helloworld".to_string(),
});
let rsp_fut_expect_ok = ok_client.check(HealthCheckRequest {
service: "helloworld".to_string(),
});

timeout(
Duration::from_millis(250),
join(mock_guard.wait_until_satisfied(), rsp_fut),
)
.await
.unwrap_err();

let rsp = client
.check(HealthCheckRequest {
name: "helloworld".to_string(),
service: "helloworld".to_string(),
})
timeout(Duration::from_millis(200), rsp_fut_expect_err)
.await
.unwrap();
assert_eq!(&HealthCheckResponse::default(), rsp.get_ref());
.unwrap_err(); // should be error
ethanoroshiba marked this conversation as resolved.
Show resolved Hide resolved
let ok_rsp = timeout(Duration::from_millis(300), rsp_fut_expect_ok)
.await
.unwrap(); // should be ok

assert!(ok_rsp.is_ok());
assert_eq!(&HealthCheckResponse::default(), ok_rsp.unwrap().get_ref());
}
Loading
Loading