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

Add Unit tests for VssAccessor #3

Merged
merged 1 commit into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ edition = "2021"
build = "build.rs"

[dependencies]
prost = "0.11.9"
prost = "0.11.6"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  • to keep msrv low
  • will eventually start enforcing it

reqwest = { version = "0.11.13", features = ["rustls-tls"] }

[dev-dependencies]

[build-dependencies]
prost-build = { version = "0.11.3" }
reqwest = { version = "0.11.13", features = ["blocking"] }

[dev-dependencies]
mockito = "0.31.1"
tokio = { version = "1.22.0", features = ["macros"]}

[features]
genproto = []
291 changes: 291 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#[cfg(test)]
mod tests {
use mockito::{self, Matcher};
use prost::Message;
use vss_client::client::VssClient;
use vss_client::error::VssError;

use vss_client::types::{
ErrorCode, ErrorResponse, GetObjectRequest, GetObjectResponse, KeyValue, ListKeyVersionsRequest,
ListKeyVersionsResponse, PutObjectRequest, PutObjectResponse,
};

const GET_OBJECT_ENDPOINT: &'static str = "/getObject";
const PUT_OBJECT_ENDPOINT: &'static str = "/putObjects";
const LIST_KEY_VERSIONS_ENDPOINT: &'static str = "/listKeyVersions";

#[tokio::test]
async fn test_get() {
// Spin-up mock server with mock response for given request.
let base_url = mockito::server_url().to_string();

// Set up the mock request/response.
let get_request = GetObjectRequest { store_id: "store".to_string(), key: "k1".to_string() };
let mock_response = GetObjectResponse {
value: Some(KeyValue { key: "k1".to_string(), version: 2, value: b"k1v2".to_vec() }),
..Default::default()
};

// Register the mock endpoint with the mockito server.
let mock_server = mockito::mock("POST", GET_OBJECT_ENDPOINT)
.match_body(get_request.encode_to_vec())
.with_status(200)
.with_body(mock_response.encode_to_vec())
.create();

// Create a new VssClient with the mock server URL.
let client = VssClient::new(&base_url);

let actual_result = client.get_object(&get_request).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();

// Set up the mock request/response.
let 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", PUT_OBJECT_ENDPOINT)
.match_body(request.encode_to_vec())
.with_status(200)
.with_body(mock_response.encode_to_vec())
.create();

// Create a new VssClient with the mock server URL.
let vss_client = VssClient::new(&base_url);
let actual_result = vss_client.put_object(&request).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();

// Set up the mock request/response.
let request = ListKeyVersionsRequest {
store_id: "store".to_string(),
page_size: Some(5),
page_token: None,
key_prefix: Some("k".into()),
};

let mock_response = ListKeyVersionsResponse {
key_versions: vec![
KeyValue { key: "k1".to_string(), version: 3, value: vec![] },
KeyValue { key: "k2".to_string(), version: 1, value: vec![] },
],
global_version: Some(4),
next_page_token: Some("k2".into()),
};

// Register the mock endpoint with the mockito server.
let mock_server = mockito::mock("POST", LIST_KEY_VERSIONS_ENDPOINT)
.match_body(request.encode_to_vec())
.with_status(200)
.with_body(mock_response.encode_to_vec())
.create();

// Create a new VssClient with the mock server URL.
let client = VssClient::new(&base_url);

let actual_result = client.list_key_versions(&request).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_client = VssClient::new(&base_url);

// 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_client
.get_object(&GetObjectRequest { store_id: "store".to_string(), key: "k1".to_string() })
.await;
assert!(matches!(get_result.unwrap_err(), VssError::InvalidRequestError { .. }));

let put_result = vss_client
.put_object(&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() }],
})
.await;
assert!(matches!(put_result.unwrap_err(), VssError::InvalidRequestError { .. }));

let list_result = vss_client
.list_key_versions(&ListKeyVersionsRequest {
store_id: "store".to_string(),
page_size: Some(5),
page_token: None,
key_prefix: Some("k".into()),
})
.await;
assert!(matches!(list_result.unwrap_err(), VssError::InvalidRequestError { .. }));

// 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_client = VssClient::new(&base_url);

// 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_client
.put_object(&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() }],
})
.await;
assert!(matches!(put_result.unwrap_err(), VssError::ConflictError { .. }));

// 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_client = VssClient::new(&base_url);

// 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_client
.get_object(&GetObjectRequest { store_id: "store".to_string(), key: "k1".to_string() })
.await;
assert!(matches!(get_result.unwrap_err(), VssError::InternalServerError { .. }));

let put_result = vss_client
.put_object(&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() }],
})
.await;
assert!(matches!(put_result.unwrap_err(), VssError::InternalServerError { .. }));

let list_result = vss_client
.list_key_versions(&ListKeyVersionsRequest {
store_id: "store".to_string(),
page_size: Some(5),
page_token: None,
key_prefix: Some("k".into()),
})
.await;
assert!(matches!(list_result.unwrap_err(), VssError::InternalServerError { .. }));

// 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_client = VssClient::new(&base_url);

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_request = GetObjectRequest { store_id: "store".to_string(), key: "k1".to_string() };
let get_result = vss_client.get_object(&get_request).await;
assert!(matches!(get_result.unwrap_err(), VssError::InternalError { .. }));

let put_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 put_result = vss_client.put_object(&put_request).await;
assert!(matches!(put_result.unwrap_err(), VssError::InternalError { .. }));

let list_request = ListKeyVersionsRequest {
store_id: "store".to_string(),
page_size: Some(5),
page_token: None,
key_prefix: Some("k".into()),
};
let list_result = vss_client.list_key_versions(&list_request).await;
assert!(matches!(list_result.unwrap_err(), VssError::InternalError { .. }));

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_client.get_object(&get_request).await;
assert!(matches!(get_malformed_err_response.unwrap_err(), VssError::InternalError { .. }));

let put_malformed_err_response = vss_client.put_object(&put_request).await;
assert!(matches!(put_malformed_err_response.unwrap_err(), VssError::InternalError { .. }));

let list_malformed_err_response = vss_client.list_key_versions(&list_request).await;
assert!(matches!(list_malformed_err_response.unwrap_err(), VssError::InternalError { .. }));

// Requests to endpoints are no longer mocked and will result in network error.
drop(_mock_server);

let get_network_err = vss_client.get_object(&get_request).await;
assert!(matches!(get_network_err.unwrap_err(), VssError::InternalError { .. }));

let put_network_err = vss_client.put_object(&put_request).await;
assert!(matches!(put_network_err.unwrap_err(), VssError::InternalError { .. }));

let list_network_err = vss_client.list_key_versions(&list_request).await;
assert!(matches!(list_network_err.unwrap_err(), VssError::InternalError { .. }));
Comment on lines +279 to +289
Copy link

Choose a reason for hiding this comment

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

Wasn't clear to me why this results in an InternalError. In a separate commit, could you add documentation to the enum variants and configure the library as following?

#![deny(missing_docs)]
#![deny(unsafe_code)]

Copy link
Collaborator Author

@G8XSU G8XSU Aug 8, 2023

Choose a reason for hiding this comment

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

Sounds Good, it is already on my todo list 👍

}
}