From 0d4dd8d5fc43405c52f35f08378dbe590fc1c33a Mon Sep 17 00:00:00 2001 From: Gursharan Singh <3442979+G8XSU@users.noreply.github.com> Date: Wed, 26 Apr 2023 17:22:09 -0700 Subject: [PATCH] Add Unit tests for VssAccessor --- vss-accessor/Cargo.toml | 2 + vss-accessor/src/lib.rs | 2 +- vss-accessor/src/vss_error.rs | 12 ++ vss-accessor/tests/tests.rs | 282 ++++++++++++++++++++++++++++++++++ 4 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 vss-accessor/tests/tests.rs diff --git a/vss-accessor/Cargo.toml b/vss-accessor/Cargo.toml index 84474a3..421df1c 100644 --- a/vss-accessor/Cargo.toml +++ b/vss-accessor/Cargo.toml @@ -9,6 +9,8 @@ prost = "0.11.3" reqwest = { version = "0.11.13", features = ["rustls-tls"] } [dev-dependencies] +mockito = "0.31.1" +tokio = { version = "1.22.0", features = ["full"]} [build-dependencies] prost-build = { version = "0.11.3" } diff --git a/vss-accessor/src/lib.rs b/vss-accessor/src/lib.rs index e38a46c..3de50ee 100644 --- a/vss-accessor/src/lib.rs +++ b/vss-accessor/src/lib.rs @@ -9,7 +9,7 @@ use crate::vss::{ }; use crate::vss_error::VssError; -mod vss_error; +pub mod vss_error; pub mod vss { include!(concat!(env!("OUT_DIR"), "/org.vss.rs")); diff --git a/vss-accessor/src/vss_error.rs b/vss-accessor/src/vss_error.rs index aaa6640..a7b436a 100644 --- a/vss-accessor/src/vss_error.rs +++ b/vss-accessor/src/vss_error.rs @@ -73,3 +73,15 @@ impl From for VssError { VssError::InternalError(err.to_string()) } } + +impl PartialEq for VssError { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (VssError::InvalidRequestError(_e1), VssError::InvalidRequestError(_e2)) => true, + (VssError::ConflictError(_e1), VssError::ConflictError(_e2)) => true, + (VssError::InternalServerError(_e1), VssError::InternalServerError(_e2)) => true, + (VssError::InternalError(_m1), VssError::InternalError(_m2)) => true, + _ => false, + } + } +} diff --git a/vss-accessor/tests/tests.rs b/vss-accessor/tests/tests.rs new file mode 100644 index 0000000..267a60d --- /dev/null +++ b/vss-accessor/tests/tests.rs @@ -0,0 +1,282 @@ +#[cfg(test)] +mod tests { + use mockito::{self, Matcher}; + use prost::Message; + use vss_accessor::vss::{ + ErrorCode, ErrorResponse, GetObjectRequest, GetObjectResponse, KeyValue, ListKeyVersionsRequest, + ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse, + }; + use vss_accessor::vss_error::VssError; + use vss_accessor::VssAccessor; + + #[tokio::test] + async fn test_get() { + // Spin-up mock server with mock response for given request. + let base_url = mockito::server_url().to_string(); + let mock_get_endpoint = "/getObject"; + + // Set up the mock request/response. + let mock_request = GetObjectRequest { store_id: "store".to_string(), key: "k1".to_string() }; + let mut mock_response = GetObjectResponse::default(); + mock_response.value = Some(KeyValue { key: "k1".to_string(), version: 2, value: b"k1v2".to_vec() }); + + // Register the mock endpoint with the mockito server. + let mock_server = mockito::mock("POST", mock_get_endpoint) + .match_body(mock_request.encode_to_vec()) + .with_status(200) + .with_body(mock_response.encode_to_vec()) + .create(); + + // Create a new VssAccessor with the mock server URL. + let vss_acc = VssAccessor::new(&base_url).unwrap(); + let actual_result = vss_acc.get("store", "k1").await.unwrap(); + + let expected_result = &mock_response; + assert_eq!(&actual_result, expected_result); + + // Verify server endpoint was called exactly once. + mock_server.expect(1).assert(); + } + + #[tokio::test] + async fn test_put() { + // Spin-up mock server with mock response for given request. + let base_url = mockito::server_url().to_string(); + let mock_put_endpoint = "/putObjects"; + + // Set up the mock request/response. + let mock_request = PutObjectRequest { + store_id: "store".to_string(), + global_version: Some(4), + transaction_items: vec![KeyValue { key: "k1".to_string(), version: 2, value: b"k1v3".to_vec() }], + }; + let mock_response = PutObjectResponse::default(); + + // Register the mock endpoint with the mockito server. + let mock_server = mockito::mock("POST", mock_put_endpoint) + .match_body(mock_request.encode_to_vec()) + .with_status(200) + .with_body(mock_response.encode_to_vec()) + .create(); + + // Create a new VssAccessor with the mock server URL. + let vss_acc = VssAccessor::new(&base_url).unwrap(); + let actual_result = vss_acc.put("store", Some(4), "k1", 2, b"k1v3").await.unwrap(); + + let expected_result = &mock_response; + assert_eq!(&actual_result, expected_result); + + // Verify server endpoint was called exactly once. + mock_server.expect(1).assert(); + } + + #[tokio::test] + async fn test_put_tx() { + // Spin-up mock server with mock response for given request. + let base_url = mockito::server_url().to_string(); + let mock_put_endpoint = "/putObjects"; + + // Set up the mock request/response. + let mock_request = PutObjectRequest { + store_id: "store".to_string(), + global_version: Some(5), + transaction_items: vec![ + KeyValue { key: "k1".to_string(), version: 3, value: b"k1v4".to_vec() }, + KeyValue { key: "k2".to_string(), version: 1, value: b"k2v2".to_vec() }, + ], + }; + let mock_response = PutObjectResponse {}; + + // Register the mock endpoint with the mockito server. + let mock_server = mockito::mock("POST", mock_put_endpoint) + .match_body(mock_request.encode_to_vec()) + .with_status(200) + .with_body(mock_response.encode_to_vec()) + .create(); + + // Create a new VssAccessor with the mock server URL. + let vss_acc = VssAccessor::new(&base_url).unwrap(); + + let actual_result = vss_acc + .put_tx( + "store", + Some(5), + vec![ + KeyValue { key: "k1".to_string(), version: 3, value: b"k1v4".to_vec() }, + KeyValue { key: "k2".to_string(), version: 1, value: b"k2v2".to_vec() }, + ], + ) + .await + .unwrap(); + + let expected_result = &mock_response; + assert_eq!(&actual_result, expected_result); + + // Verify server endpoint was called exactly once. + mock_server.expect(1).assert(); + } + + #[tokio::test] + async fn test_list_key_versions() { + // Spin-up mock server with mock response for given request. + let base_url = mockito::server_url().to_string(); + let mock_list_endpoint = "/listKeyVersions"; + + // Set up the mock request/response. + let mock_request = ListKeyVersionsRequest { + store_id: "store".to_string(), + page_size: Some(5), + page_token: None, + key_prefix: Some("k".into()), + }; + let key_versions = vec![ + KeyValue { key: "k1".to_string(), version: 3, value: b"".to_vec() }, + KeyValue { key: "k2".to_string(), version: 1, value: b"".to_vec() }, + ]; + + let mock_response = + ListKeyVersionsResponse { key_versions, global_version: Some(4), next_page_token: Some("k2".into()) }; + + // Register the mock endpoint with the mockito server. + let mock_server = mockito::mock("POST", mock_list_endpoint) + .match_body(mock_request.encode_to_vec()) + .with_status(200) + .with_body(mock_response.encode_to_vec()) + .create(); + + // Create a new VssAccessor with the mock server URL. + let vss_acc = VssAccessor::new(&base_url).unwrap(); + + let actual_result = vss_acc.list_key_versions("store", "k", Some(5), None).await.unwrap(); + + let expected_result = &mock_response; + assert_eq!(&actual_result, expected_result); + + // Verify server endpoint was called exactly once. + mock_server.expect(1).assert(); + } + + #[tokio::test] + async fn test_invalid_request_err_handling() { + let base_url = mockito::server_url(); + let vss_accessor = VssAccessor::new(&base_url).unwrap(); + + // Invalid Request Error + let error_response = ErrorResponse { + error_code: ErrorCode::InvalidRequestException.into(), + message: "InvalidRequestException".to_string(), + }; + let mock_server = mockito::mock("POST", Matcher::Any) + .with_status(400) + .with_body(&error_response.encode_to_vec()) + .create(); + + let get_result = vss_accessor.get("store", "key1").await; + assert_eq!(get_result.unwrap_err(), VssError::InvalidRequestError(ErrorResponse::default())); + + let put_result = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_result.unwrap_err(), VssError::InvalidRequestError(ErrorResponse::default())); + + let list_result = vss_accessor.list_key_versions("store", "k", Some(5), None).await; + assert_eq!(list_result.unwrap_err(), VssError::InvalidRequestError(ErrorResponse::default())); + + // Verify 3 requests hit the server + mock_server.expect(3).assert(); + } + + #[tokio::test] + async fn test_conflict_err_handling() { + let base_url = mockito::server_url(); + let vss_accessor = VssAccessor::new(&base_url).unwrap(); + + // Conflict Error + let error_response = + ErrorResponse { error_code: ErrorCode::ConflictException.into(), message: "ConflictException".to_string() }; + let mock_server = mockito::mock("POST", Matcher::Any) + .with_status(409) + .with_body(&error_response.encode_to_vec()) + .create(); + + let put_result = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_result.unwrap_err(), VssError::ConflictError(ErrorResponse::default())); + + // Verify 1 requests hit the server + mock_server.expect(1).assert(); + } + + #[tokio::test] + async fn test_internal_server_err_handling() { + let base_url = mockito::server_url(); + let vss_accessor = VssAccessor::new(&base_url).unwrap(); + + // Internal Server Error + let error_response = ErrorResponse { + error_code: ErrorCode::InternalServerException.into(), + message: "InternalServerException".to_string(), + }; + let mock_server = mockito::mock("POST", Matcher::Any) + .with_status(500) + .with_body(&error_response.encode_to_vec()) + .create(); + + let get_result = vss_accessor.get("store", "key1").await; + assert_eq!(get_result.unwrap_err(), VssError::InternalServerError(ErrorResponse::default())); + + let put_result = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_result.unwrap_err(), VssError::InternalServerError(ErrorResponse::default())); + + let list_result = vss_accessor.list_key_versions("store", "k", Some(5), None).await; + assert_eq!(list_result.unwrap_err(), VssError::InternalServerError(ErrorResponse::default())); + + // Verify 3 requests hit the server + mock_server.expect(3).assert(); + } + + #[tokio::test] + async fn test_internal_err_handling() { + let base_url = mockito::server_url(); + let vss_accessor = VssAccessor::new(&base_url).unwrap(); + + let error_response = ErrorResponse { error_code: 999, message: "UnknownException".to_string() }; + let mut mock_server = mockito::mock("POST", Matcher::Any) + .with_status(999) + .with_body(&error_response.encode_to_vec()) + .create(); + + let get_result = vss_accessor.get("store", "key1").await; + assert_eq!(get_result.unwrap_err(), VssError::InternalError("InternalError".into())); + + let put_result = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_result.unwrap_err(), VssError::InternalError("InternalError".into())); + + let list_result = vss_accessor.list_key_versions("store", "k", Some(5), None).await; + assert_eq!(list_result.unwrap_err(), VssError::InternalError("InternalError".into())); + + let malformed_error_response = b"malformed"; + mock_server = mockito::mock("POST", Matcher::Any) + .with_status(409) + .with_body(&malformed_error_response) + .create(); + + let get_malformed_err_response = vss_accessor.get("store", "key1").await; + assert_eq!(get_malformed_err_response.unwrap_err(), VssError::InternalError("InternalError".into())); + + let put_malformed_err_response = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_malformed_err_response.unwrap_err(), VssError::InternalError("InternalError".into())); + + let list_malformed_err_response = vss_accessor.list_key_versions("store", "k", Some(5), None).await; + assert_eq!(list_malformed_err_response.unwrap_err(), VssError::InternalError("InternalError".into())); + + // Requests to endpoints are no longer mocked and will result in network error. + drop(mock_server); + + let get_network_err = vss_accessor.get("store", "key1").await; + assert_eq!(get_network_err.unwrap_err(), VssError::InternalError("InternalError".into())); + + let put_network_err = vss_accessor.put("store", Some(4), "k1", 2, b"k1v3").await; + assert_eq!(put_network_err.unwrap_err(), VssError::InternalError("InternalError".into())); + + let list_network_err = vss_accessor.list_key_versions("store", "k", Some(5), None).await; + assert_eq!(list_network_err.unwrap_err(), VssError::InternalError("InternalError".into())); + } +}