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 6 commits
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 @@ -16,6 +16,7 @@ tokio = { workspace = true, features = [
"time",
] }
tonic.workspace = true
futures = { workspace = true }

[dev-dependencies]
astria-grpc-mock = { path = "../astria-grpc-mock" }
Expand Down
11 changes: 9 additions & 2 deletions crates/astria-grpc-mock-test/src/generated/grpc.health.v1.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// This file is @generated by prost-build.
Copy link
Member

Choose a reason for hiding this comment

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

Was this added by you or by prost?

Copy link
Contributor Author

@ethanoroshiba ethanoroshiba Oct 18, 2024

Choose a reason for hiding this comment

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

Added by prost, I think at some point I may have bumped the dep and this is a new thing that the builder does? If I remember correctly, I believe I've noticed it elsewhere as well.

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HealthCheckRequest {
Expand All @@ -8,7 +9,10 @@ impl ::prost::Name for HealthCheckRequest {
const NAME: &'static str = "HealthCheckRequest";
const PACKAGE: &'static str = "grpc.health.v1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!("grpc.health.v1.{}", Self::NAME)
"grpc.health.v1.HealthCheckRequest".into()
}
fn type_url() -> ::prost::alloc::string::String {
"/grpc.health.v1.HealthCheckRequest".into()
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
Expand Down Expand Up @@ -67,7 +71,10 @@ impl ::prost::Name for HealthCheckResponse {
const NAME: &'static str = "HealthCheckResponse";
const PACKAGE: &'static str = "grpc.health.v1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!("grpc.health.v1.{}", Self::NAME)
"grpc.health.v1.HealthCheckResponse".into()
}
fn type_url() -> ::prost::alloc::string::String {
"/grpc.health.v1.HealthCheckResponse".into()
}
}
/// Generated client implementations.
Expand Down
271 changes: 4 additions & 267 deletions crates/astria-grpc-mock-test/tests/health/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,270 +3,7 @@
reason = "just make the tests work for now"
)]

use std::{
net::SocketAddr,
pin::Pin,
sync::Arc,
};

use astria_grpc_mock::{
matcher,
response,
Mock,
};
use astria_grpc_mock_test::health::{
health_client::HealthClient,
health_server::{
Health,
HealthServer,
},
HealthCheckRequest,
HealthCheckResponse,
};
use tokio::{
join,
task::JoinHandle,
};
use tokio_stream::{
wrappers::TcpListenerStream,
Stream,
};
use tonic::{
transport::Server,
Request,
Response,
Status,
};

struct MockServer {
_server: JoinHandle<()>,
local_addr: SocketAddr,
mocked: astria_grpc_mock::MockServer,
}

async fn start_mock_server() -> MockServer {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let local_addr = listener.local_addr().unwrap();
let mock_server = astria_grpc_mock::MockServer::new();
let server = tokio::spawn({
let mock_server = mock_server.clone();
async move {
let _ = Server::builder()
.add_service(HealthServer::new(HealthService {
mock_server,
}))
.serve_with_incoming(TcpListenerStream::new(listener))
.await;
}
});
MockServer {
_server: server,
local_addr,
mocked: mock_server,
}
}

struct HealthService {
mock_server: astria_grpc_mock::MockServer,
}

#[tonic::async_trait]
impl Health for HealthService {
type WatchStream =
Pin<Box<dyn Stream<Item = Result<HealthCheckResponse, Status>> + Send + 'static>>;

async fn check(
self: Arc<Self>,
request: Request<HealthCheckRequest>,
) -> Result<Response<HealthCheckResponse>, Status> {
self.mock_server.handle_request("check", request).await
}

async fn watch(
self: Arc<Self>,
_request: Request<HealthCheckRequest>,
) -> Result<Response<Self::WatchStream>, Status> {
unimplemented!()
}
}

#[tokio::test]
async fn default_response_works() {
let server = start_mock_server().await;
let mut 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>());
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await
.unwrap();
assert_eq!(&HealthCheckResponse::default(), rsp.get_ref());
}

#[tokio::test]
async fn constant_response_works() {
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let expected_response = HealthCheckResponse {
status: 1,
};
let mock = Mock::for_rpc_given("check", matcher::message_type::<HealthCheckRequest>())
.respond_with(response::constant_response(expected_response.clone()));
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await
.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
}

#[tokio::test]
async fn constant_response_expect_two_works() {
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let expected_response = HealthCheckResponse {
status: 1,
};
let mock = Mock::for_rpc_given("check", matcher::message_type::<HealthCheckRequest>())
.respond_with(response::constant_response(expected_response.clone()))
.expect(2);

let guard = server.mocked.register_as_scoped(mock).await;
let two_checks = async move {
let res_one = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await?;

let res_two = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await?;
Ok::<_, tonic::Status>((res_one, res_two))
};

let ((), res) = join!(guard.wait_until_satisfied(), two_checks);
let res = res.unwrap();
assert_eq!(&expected_response, res.0.get_ref());
assert_eq!(&expected_response, res.1.get_ref());
}

#[tokio::test]
async fn constant_response_guard_works() {
let server = start_mock_server().await;
let mut client = HealthClient::connect(format!("http://{}", server.local_addr))
.await
.unwrap();
let expected_response = HealthCheckResponse {
status: 1,
};
let mock = Mock::for_rpc_given("check", matcher::message_type::<HealthCheckRequest>())
.respond_with(response::constant_response(expected_response.clone()))
.expect(1);

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

let ((), check_res) = join!(guard.wait_until_satisfied(), check);
let rsp = check_res.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
}

#[tokio::test]
async fn exact_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 {
service: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
status: 1,
};
let mock = Mock::for_rpc_given("check", matcher::message_exact_pbjson(&expected_request))
.respond_with(response::constant_response(expected_response.clone()));
server.mocked.register(mock).await;
let rsp = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await
.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
}

#[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 {
service: "helloworld".to_string(),
};
let expected_response = HealthCheckResponse {
status: 1,
};
// FIXME: Right now this is equivalent to an exact check because the request only has one field.
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 {
service: "helloworld".to_string(),
})
.await
.unwrap();
assert_eq!(&expected_response, rsp.get_ref());
}

#[tokio::test]
#[should_panic]
async fn incorrect_mock_response_fails_server() {
let server = start_mock_server().await;
let mut 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::<HealthCheckRequest>());
server.mocked.register(mock).await;
let _ = client
.check(HealthCheckRequest {
service: "helloworld".to_string(),
})
.await;
}

#[tokio::test]
#[should_panic]
async fn incorrect_mock_response_fails_guard() {
let server = start_mock_server().await;
let mut 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::<HealthCheckRequest>());

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

let _ = join!(guard.wait_until_satisfied(), check);
}
mod test_matcher;
mod test_mock;
mod test_response;
mod test_utils;
Loading
Loading