#![allow(clippy::unit_arg, dead_code, unused_imports, unused_variables)]
//! ## Integration Tests For [`BriteVerifyClient`](briteverify_rs::BriteVerifyClient)'s
//! ## Bulk Verification Methods
// Module Declarations
pub mod utils;
// Standard Library Imports
use std::str::Split;
use std::{
collections::HashMap,
sync::{
atomic::{AtomicU8, Ordering},
Arc, Mutex,
},
};
// Third Party Imports
use anyhow::Result;
use http_types::{mime::JSON, Method as HttpMethod, StatusCode};
use once_cell::sync::Lazy;
use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
use rstest::{fixture, rstest};
use serde_json::Value;
use test_log::test as test_log;
use wiremock::{http::Url, matchers, Match, Mock, Request, Respond, ResponseTemplate};
// Crate-Level Imports
use briteverify_rs::{errors::BriteVerifyClientError, types};
use utils::{
official_response, v3_mock_data as mock_data, BriteVerifyRequest, MockRequestResponse,
V3_LISTS, V3_LIST_RESULTS, V3_LIST_STATE,
};
//
//
//
pub fn is_date_string(value: &str) -> bool {
value.chars().all(|c| c == '-' || c.is_ascii_digit())
}
//
/// Check if the supplied request matches the official
/// request specification for a "get bulk list states"
/// from the BriteVerify API's published Postman collection
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn is_get_lists_request(request: &Request) -> bool {
request.has_valid_api_key()
&& request.method == HttpMethod::Get
&& V3_LISTS.is_match(request.url.as_str())
}
/// Check if the supplied request matches the official
/// request specification for a bulk list CRUD-type
/// request from the BriteVerify API's Postman collection
/// [[ref](https://docs.briteverify.com/#38b4c9eb-31b1-4b8e-9295-a783d8043bc1)]
pub fn is_list_crud_request(request: &Request) -> bool {
request.has_valid_api_key()
&& V3_LIST_STATE.is_match(request.url.as_str())
&& [HttpMethod::Get, HttpMethod::Post, HttpMethod::Delete].contains(&request.method)
}
//
//
/// Return an empty collection of bulk verification lists
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn no_lists_found_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_NO_LISTS_FOUND)
}
/// Return a list of bulk verification lists
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn unfiltered_lists_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_GET_ALL_LISTS)
}
/// Return a list of bulk verification lists filtered by date
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn lists_by_date_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_LISTS_BY_DATE)
}
/// Return a list of bulk verification lists filtered by page
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn lists_by_page_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_LISTS_BY_PAGE)
}
/// Return a list of bulk verification lists filtered by state
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn lists_by_state_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_LISTS_BY_STATE)
}
/// Return a list of bulk verification lists filtered by external identifier
/// [[ref](https://docs.briteverify.com/#0b5a2a7a-4062-4327-ab0a-4675592e3cd6)]
pub fn lists_by_ext_id_response(_: &Request) -> ResponseTemplate {
official_response(mock_data::OFFICIAL_LISTS_BY_EXTERNAL_ID)
}
/// Return the state of the bulk verification specified by its
/// BriteVerify API-issued unique identifier. If the requested
/// list doesn't exist (read: has no corresponding data in the
/// examples from the official API docs) return the appropriate
/// "list not found" response.
/// [[ref](https://docs.briteverify.com/#b09c09dc-e11e-44a8-b53d-9f1fd9c6792d)]
pub fn list_state_by_id_response(request: &Request) -> ResponseTemplate {
request
.url
.path_segments()
.and_then(|segments| segments.last())
.and_then(|list_id| {
[
&mock_data::OFFICIAL_LIST_STATE_EXPIRED,
&mock_data::OFFICIAL_LIST_STATE_COMPLETE,
&mock_data::OFFICIAL_LIST_STATE_VERIFYING,
&mock_data::OFFICIAL_LIST_STATE_TERMINATED,
&mock_data::OFFICIAL_LIST_STATE_AUTO_TERMINATED,
&mock_data::OFFICIAL_LIST_STATE_WITH_EXTERNAL_ID,
]
.into_iter()
.find(|example| example.response.contains(list_id))
})
.map(|data| official_response(*data))
.unwrap_or(ResponseTemplate::new(StatusCode::NotFound).set_body_raw(
mock_data::ERROR_LIST_STATE_NOT_FOUND.response,
&JSON.to_string(),
))
}
/// Return the "result" of deleting the specified bulk verification
/// list. If the requested list doesn't exist (read: has no
/// corresponding data in the examples from the official API docs)
/// return the appropriate "list not found" response.
/// [[ref](https://docs.briteverify.com/#6c9b9c05-a4a0-435e-a064-af7d9476719d)]
pub fn delete_list_response(request: &Request) -> ResponseTemplate {
request
.url
.path_segments()
.and_then(|segments| segments.last())
.and_then(|list_id| {
[
&mock_data::OFFICIAL_DELETE_PREPPED_LIST,
&mock_data::OFFICIAL_DELETE_COMPLETED_LIST,
&mock_data::OFFICIAL_DELETE_DELIVERED_LIST,
&mock_data::OFFICIAL_DELETE_IMPORT_ERRORED_LIST,
]
.into_iter()
.find(|example| example.response.contains(list_id))
})
.map(|data| official_response(*data))
.unwrap_or(ResponseTemplate::new(StatusCode::NotFound).set_body_raw(
mock_data::ERROR_INVALID_LIST_STATE.response,
&JSON.to_string(),
))
}
/// Return the "result" of updating a given bulk verification list
/// [[ref](https://docs.briteverify.com/#38b4c9eb-31b1-4b8e-9295-a783d8043bc1)]
pub fn update_list_response(request: &Request) -> ResponseTemplate {
request
.url
.path_segments()
.and_then(|segments| -> Option {
match (
segments
.last()
.and_then(|list_id| list_id.parse::().ok()),
serde_json::from_slice::(&request.body),
) {
(Some(list_id), Ok(body)) => {
let mock_data = match (&body.directive, body.contacts.is_empty()) {
(&types::BulkListDirective::Start, true) => mock_data::OFFICIAL_VERIFY_LIST,
(&types::BulkListDirective::Terminate, true) => {
mock_data::OFFICIAL_TERMINATE_LIST
}
_ => {
return None;
}
};
Some(official_response(mock_data))
}
_ => None,
}
})
.unwrap_or(ResponseTemplate::new(StatusCode::BadRequest))
}
//
//
//
#[derive(Clone, Debug)]
struct StatefulPageResponder {
pub current_page: Arc,
pub page_map: HashMap,
}
impl Match for StatefulPageResponder {
fn matches(&self, request: &Request) -> bool {
is_get_lists_request(request) && {
let page = 1 + self.current_page.load(Ordering::SeqCst);
self.current_page.store(page, Ordering::SeqCst);
// request
// .url
// .query_pairs()
// .any(|(key, value)| key == "page" && value.parse::().unwrap_or(0) == page)
self.page_map.contains_key(&page)
}
}
}
impl Respond for StatefulPageResponder {
fn respond(&self, request: &Request) -> ResponseTemplate {
let page = self.current_page.load(Ordering::SeqCst);
match self.page_map.get(&page) {
Some(data) => {
ResponseTemplate::new(StatusCode::Ok).set_body_raw(data.response, &JSON.to_string())
}
None => {
self.current_page.store(0, Ordering::SeqCst);
ResponseTemplate::new(StatusCode::NotFound)
}
}
}
}
//
//
#[fixture]
/// An unregistered `Mock` that will respond to "get all bulk verification
/// lists" requests with the official "empty" response body from the
/// BriteVerify API's published Postman collection / documentation
fn mock_no_lists() -> Mock {
Mock::given(is_get_lists_request).respond_with(no_lists_found_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get all bulk verification
/// lists" requests with the official response body from the BriteVerify API's
/// published Postman collection / documentation
fn mock_all_lists() -> Mock {
Mock::given(is_get_lists_request)
.and(move |request: &Request| -> bool {
request
.url
.query()
.map_or(true, |val| val.trim().is_empty())
})
.respond_with(unfiltered_lists_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get all verification lists
/// for {DATE}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_lists_by_date() -> Mock {
Mock::given(is_get_lists_request)
.and(move |request: &Request| -> bool {
request
.url
.query_pairs()
.any(|(key, value)| key == "date" && is_date_string(value.as_ref()))
})
.respond_with(lists_by_date_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get all verification lists
/// for {PAGE}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_lists_by_page() -> Mock {
let page = Arc::new(AtomicU8::new(0));
let matcher = StatefulPageResponder {
current_page: Arc::clone(&page),
page_map: HashMap::from([
(1, mock_data::OFFICIAL_MULTIPLE_LIST_PAGES),
(2, mock_data::OFFICIAL_LISTS_BY_PAGE),
]),
};
let responder = matcher.clone();
Mock::given(matcher)
.and(move |request: &Request| -> bool {
request
.url
.query_pairs()
.any(|(key, value)| key == "page" && value.parse::().is_ok())
})
.respond_with(responder)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get all verification lists
/// for {STATE}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_lists_by_state() -> Mock {
Mock::given(is_get_lists_request)
.and(move |request: &Request| -> bool {
request.url.query_pairs().any(|(key, value)| {
key == "state" && !types::BatchState::from(value.as_ref()).is_unknown()
})
})
.respond_with(lists_by_state_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get all verification lists
/// for {EXT_ID}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_lists_by_ext_id() -> Mock {
Mock::given(is_get_lists_request)
.and(move |request: &Request| -> bool {
request
.url
.path_segments()
.is_some_and(|mut segments| segments.any(|part| part == "accounts"))
})
.respond_with(lists_by_ext_id_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "get verification list
/// state for {LIST_ID}" requests with the official response body from
/// the BriteVerify API's published Postman collection / documentation
fn mock_list_state_by_id() -> Mock {
Mock::given(is_list_crud_request).respond_with(list_state_by_id_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "create/update verification
/// list {LIST_ID}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_update_list() -> Mock {
Mock::given(is_list_crud_request).respond_with(update_list_response)
}
#[fixture]
/// An unregistered `Mock` that will respond to "delete verification list
/// {LIST_ID}" requests with the official response body from the BriteVerify
/// API's published Postman collection / documentation
fn mock_delete_list() -> Mock {
Mock::given(is_list_crud_request).respond_with(delete_list_response)
}
//
//
#[rstest]
#[test_log::test(tokio::test)]
/// Test that the [`get_lists`](briteverify_rs::BriteVerifyClient::get_lists)
/// method sends the expected request and properly handles the returned
/// response (per the official BriteVerify API Postman collection)
async fn gets_bulk_lists_without_filters(#[from(mock_all_lists)] mock: Mock) -> Result<()> {
let (client, server) = utils::client_and_server(None, None).await;
#[allow(unused_variables)]
let guard = mock.mount_as_scoped(&server).await;
let response = client.get_lists().await?;
assert_eq!(3, response.len());
let (actual_ids, expected_ids) = (
response.ids(),
vec![
"a2595a63-ae71-4dda-91d4-57bdb331aa3a",
"1433fe1c-cc4b-48fb-8989-d3ec83502c54",
"288be984-2094-4925-8790-4ebfeab7d757",
],
);
assert_eq!(expected_ids, actual_ids);
let completed = response.get_list_by_id("1433fe1c-cc4b-48fb-8989-d3ec83502c54");
Ok(assert!(
completed.is_some_and(|list| matches!(list.state, types::BatchState::Complete)),
"Expected Some(&VerificationListState) w/ list.state == 'complete', got: {:#?}",
completed
))
}
#[rstest]
#[test_log::test(tokio::test)]
/// Test that the [`get_lists_by_date`](briteverify_rs::BriteVerifyClient::get_lists_by_date)
/// method sends the expected request and properly handles the returned
/// response (per the official BriteVerify API Postman collection)
async fn gets_bulk_lists_by_date(#[from(mock_lists_by_date)] mock: Mock) -> Result<()> {
let (client, server) = utils::client_and_server(None, None).await;
#[allow(unused_variables)]
let guard = mock.mount_as_scoped(&server).await;
let filter_date = chrono::NaiveDate::from_ymd_opt(2021, 8, 10).unwrap();
let response: types::GetListStatesResponse = client.get_lists_by_date(filter_date).await?;
assert_eq!(1, response.len());
let list = response.iter().next().unwrap();
assert_str_eq!("9fda9ed3-2819-4ce0-9811-e2207d1b3da0", list.id.as_str());
Ok(assert_eq!(filter_date, list.created_at.date_naive()))
}
#[rstest]
#[test_log::test(tokio::test)]
/// Test that the [`get_lists_by_page`](briteverify_rs::BriteVerifyClient::get_lists_by_page)
/// method sends the expected request and properly handles the returned
/// response (per the official BriteVerify API Postman collection)
async fn gets_bulk_lists_by_page(#[from(mock_lists_by_page)] mock: Mock) -> Result<()> {
let (client, server) = utils::client_and_server(None, None).await;
#[allow(unused_variables)]
let guard = mock.mount_as_scoped(&server).await;
let (page_1, page_2) = (
client.get_lists_by_page(1u32).await?,
client.get_lists_by_page(2u32).await?,
);
assert_eq!(3, page_1.len());
assert_eq!(3, page_2.len());
assert_eq!((1u64, 2u64), (page_1.current_page(), page_1.total_pages()));
Ok(assert_eq!(
(2u64, 2u64),
(page_2.current_page(), page_2.total_pages())
))
}
#[rstest]
#[test_log::test(tokio::test)]
/// Test that the [`get_lists_by_state`](briteverify_rs::BriteVerifyClient::get_lists_by_state)
/// method sends the expected request and properly handles the returned
/// response (per the official BriteVerify API Postman collection)
async fn gets_bulk_lists_by_state(#[from(mock_lists_by_state)] mock: Mock) -> Result<()> {
let (client, server) = utils::client_and_server(None, None).await;
#[allow(unused_variables)]
let guard = mock.mount_as_scoped(&server).await;
let filter_state = types::BatchState::Terminated;
let response = client.get_lists_by_state(filter_state).await?;
assert_eq!(1, response.len());
let list = response.iter().next().unwrap();
assert_str_eq!("fb6c70e4-d6e9-43c1-84a4-790b4e090b00", list.id.as_str());
Ok(assert_eq!(filter_state, list.state))
}
#[rstest]
#[test_log::test(tokio::test)]
/// Test that the [`get_lists_by_state`](briteverify_rs::BriteVerifyClient::get_lists_by_state)
/// method sends the expected request and properly handles the returned
/// response (per the official BriteVerify API Postman collection)
async fn gets_bulk_lists_by_ext_id(#[from(mock_lists_by_ext_id)] mock: Mock) -> Result<()> {
let (client, server) = utils::client_and_server(None, None).await;
#[allow(unused_variables)]
let guard = mock.mount_as_scoped(&server).await;
let response = client
.get_filtered_lists(