//! ## Test Suite Utilities
// Module Declarations
pub mod v1_mock_data;
pub mod v3_mock_data;
// Standard Library Imports
use std::ops::Deref;
// Third Part Imports
use anyhow::Result;
use http::uri::Scheme;
use http_types::{
headers::{AUTHORIZATION, CONTENT_TYPE},
mime::JSON,
Method as HttpMethod, StatusCode,
};
use once_cell::sync::Lazy;
use regex::Regex;
use serde_json::{from_str as json_from_str, Value};
use uuid::Uuid;
use wiremock::{Match, MockServer, Request, Respond, ResponseTemplate};
// Crate-Level Imports
use briteverify_rs::BriteVerifyClient;
//
pub static TEST_API_KEY: Lazy = Lazy::new(|| Uuid::new_v4().to_string());
pub static API_KEY_HEADER: Lazy = Lazy::new(|| format!("ApiKey: {}", TEST_API_KEY.deref()));
//
// Common "Parts"
const PAGE: &str = "(?page=[0-9]+&?)";
const DATE: &str = "(?(date=[0-9]{4}(-[0-9]{2}){2})&?)";
const STATE: &str = "(?state=[a-z_-]+&?)";
const EXT_ID: &str = r#"(?:accounts/[\S]+/)"#;
const LIST_ID: &str = r#"[0-9a-fA-F\-]{36}"#;
static BASE_LISTS: Lazy = Lazy::new(|| format!(r#"/api/v3/{EXT_ID}?lists"#));
// v1 "Single Transaction" Endpoints
pub static V1_VERIFY: Lazy =
Lazy::new(|| r#"(?i:/api/(?:public/)?v1/fullverify/?$)"#.parse::().unwrap());
// v3 "Bulk" Endpoints
pub static V3_LISTS: Lazy = Lazy::new(|| {
let query = format!(r#"(?({PAGE}|{STATE}|{DATE}){{1,3}})"#);
format!(r#"(?i:{}(\?{query})?/?$)"#, BASE_LISTS.deref())
.parse::()
.unwrap()
});
pub static V3_LIST_STATE: Lazy = Lazy::new(|| {
format!(r#"(?i:{}/{LIST_ID}/?$)"#, BASE_LISTS.deref())
.parse::()
.unwrap()
});
pub static V3_LIST_RESULTS: Lazy = Lazy::new(|| {
format!(r#"(?i:{}/{LIST_ID}/export/[0-9]+/?$)"#, BASE_LISTS.deref())
.parse::()
.unwrap()
});
//
//
//
/// Create a `BriteVerifyClient` instance pre-configured for use
/// with the supplied `wiremock::MockServer` instance
pub fn client_for_server(
server: &MockServer,
api_key: Option<&str>,
enable_retry: bool,
) -> BriteVerifyClient {
let server_addr = *server.address();
BriteVerifyClient::builder()
.https_only(false)
.retry_enabled(enable_retry)
.set_v1_url_scheme(Scheme::HTTP)
.set_v3_url_scheme(Scheme::HTTP)
.set_v1_url_port(server_addr.port())
.set_v3_url_port(server_addr.port())
.resolve_v1_url_to(server_addr)
.resolve_v3_url_to(server_addr)
.api_key(api_key.unwrap_or(TEST_API_KEY.deref()))
.build()
.unwrap()
}
/// Create a `BriteVerifyClient` instance pre-configured for use
/// with the supplied `wiremock::MockServer` instance
pub async fn client_and_server(
api_key: Option<&str>,
enable_retry: Option,
) -> (BriteVerifyClient, MockServer) {
let server = MockServer::start().await;
let client = client_for_server(&server, api_key, enable_retry.unwrap_or(false));
(client, server)
}
/// Create a [`ResponseTemplate`](ResponseTemplate) from the supplied JSON blob
pub fn official_response(from: MockRequestResponse) -> ResponseTemplate {
ResponseTemplate::new(StatusCode::Ok).set_body_raw(from.response, &JSON.to_string())
}
//
//
pub trait BriteVerifyRequest {
/// Determine if a request's `content-type` header
/// indicates that its body's MIME-type is JSON-serialized
/// text
fn has_json_content(&self) -> bool;
/// Determine if a request has the proper authorization header set
fn has_valid_api_key(&self) -> bool;
/// Determine if a request's body content
/// matches the supplied `serde_json::Value`
fn body_json_matches_value(&self, value: &Value) -> bool;
/// Check if a request is a valid BriteVerify API
/// "single-transaction / real time" verification request
fn is_v1_verification_request(&self) -> bool;
}
impl BriteVerifyRequest for Request {
fn has_json_content(&self) -> bool {
self.headers
.get(&CONTENT_TYPE)
.map_or(false, |value| value.as_str() == JSON.to_string())
}
fn has_valid_api_key(&self) -> bool {
self.headers
.get(&AUTHORIZATION)
.map_or(false, |value| value.as_str() == API_KEY_HEADER.deref())
}
fn body_json_matches_value(&self, value: &Value) -> bool {
self.body_json::().is_ok_and(|body| &body == value)
}
fn is_v1_verification_request(&self) -> bool {
self.method == HttpMethod::Post
&& V1_VERIFY.is_match(self.url.as_str())
&& self.has_json_content()
&& self.has_valid_api_key()
}
}
//
//
#[derive(Copy, Clone, Hash, Debug)]
pub struct MockRequestResponse {
pub request: &'static str,
pub response: &'static str,
}
impl MockRequestResponse {
/// Extract the specified key from the supplied [`Value`](Value)
pub fn extract(key: &str, from: Value) -> Option {
from.as_object()
.and_then(|obj| obj.get(key))
.and_then(|value| value.as_str())
.map(|value| value.to_string())
}
/// Extract the specified key from the request body
pub fn extract_from_request(&self, key: &str) -> Option {
self.request_body_json::()
.map_or(None, |value| MockRequestResponse::extract(key, value))
}
/// Extract the specified key from the request body
pub fn extract_from_response(&self, key: &str) -> Option {
self.response_body_json::()
.map_or(None, |value| MockRequestResponse::extract(key, value))
}
pub fn request_body_json<'de, T: serde::Deserialize<'de>>(&self) -> Result {
Ok(json_from_str(self.request)?)
}
pub fn response_body_json<'de, T: serde::Deserialize<'de>>(&self) -> Result {
Ok(json_from_str(self.response)?)
}
}
#[allow(unused_variables)]
impl Match for MockRequestResponse {
fn matches(&self, request: &Request) -> bool {
todo!()
}
}
#[allow(unused_variables)]
impl Respond for MockRequestResponse {
fn respond(&self, request: &Request) -> ResponseTemplate {
todo!()
}
}
//