use crate::programming::{FakeProfuseContractBoundary, ProfuseGwDispatchError};
use std::future::Future;
use std::pin::Pin;
pub const PROFUSEGW_METHOD: &str = saddle_boundary::ingress::METHOD;
pub const PROFUSEGW_PATH: &str = saddle_boundary::ingress::PATH;
pub const PROFUSEGW_MEDIA_TYPE: &str = saddle_boundary::ingress::MEDIA_TYPE;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TestIngressIdentity {
request_id: String,
call_id: String,
deadline_unix_ms: i64,
}
impl TestIngressIdentity {
pub fn new(
request_id: impl Into<String>,
call_id: impl Into<String>,
deadline_unix_ms: i64,
) -> Result<Self, ProfuseGwTestError> {
let boundary =
saddle_boundary::ingress::IngressIdentity::new(request_id, call_id, deadline_unix_ms)
.map_err(ProfuseGwTestError::from_codec)?;
Ok(Self {
request_id: boundary.request_id,
call_id: boundary.call_id,
deadline_unix_ms: boundary.deadline_unix_ms,
})
}
pub fn request_id(&self) -> &str {
&self.request_id
}
pub fn call_id(&self) -> &str {
&self.call_id
}
pub const fn deadline_unix_ms(&self) -> i64 {
self.deadline_unix_ms
}
fn into_boundary(self) -> saddle_boundary::ingress::IngressIdentity {
saddle_boundary::ingress::IngressIdentity::new(
self.request_id,
self.call_id,
self.deadline_unix_ms,
)
.expect("facade test identity was validated at construction")
}
}
pub struct ProfuseGwTestRequest {
method: String,
path: String,
media_type: String,
identity: TestIngressIdentity,
json_body: String,
}
impl ProfuseGwTestRequest {
pub fn new(
method: impl Into<String>,
path: impl Into<String>,
media_type: impl Into<String>,
identity: TestIngressIdentity,
json_body: impl Into<String>,
) -> Self {
Self {
method: method.into(),
path: path.into(),
media_type: media_type.into(),
identity,
json_body: json_body.into(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProfuseGwTestAttempt {
count: usize,
request_id: String,
call_id: String,
user_id: String,
trace_id: String,
rpc_id: String,
zone: String,
idc: String,
env: String,
function: String,
deadline_unix_ms: i64,
}
impl ProfuseGwTestAttempt {
pub const fn count(&self) -> usize {
self.count
}
pub fn request_id(&self) -> &str {
&self.request_id
}
pub fn call_id(&self) -> &str {
&self.call_id
}
pub fn user_id(&self) -> &str {
&self.user_id
}
pub fn function(&self) -> &str {
&self.function
}
pub fn trace_id(&self) -> &str {
&self.trace_id
}
pub fn rpc_id(&self) -> &str {
&self.rpc_id
}
pub fn zone(&self) -> &str {
&self.zone
}
pub fn idc(&self) -> &str {
&self.idc
}
pub fn env(&self) -> &str {
&self.env
}
pub const fn deadline_unix_ms(&self) -> i64 {
self.deadline_unix_ms
}
}
pub struct ProfuseGwTestExecution<Response> {
response: Response,
attempts: Vec<ProfuseGwTestAttempt>,
}
impl<Response> ProfuseGwTestExecution<Response> {
pub fn response(&self) -> &Response {
&self.response
}
pub fn into_response(self) -> Response {
self.response
}
pub fn attempts(&self) -> &[ProfuseGwTestAttempt] {
&self.attempts
}
pub fn response_json(&self) -> Result<String, ProfuseGwTestError>
where
Response: serde::Serialize,
{
serde_json::to_string(&self.response).map_err(|_| ProfuseGwTestError {
code: "TEST_RESPONSE_JSON_INVALID",
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProfuseGwTestError {
code: &'static str,
}
impl ProfuseGwTestError {
pub const fn code(&self) -> &'static str {
self.code
}
fn from_codec(error: saddle_boundary::ingress::CodecError) -> Self {
Self { code: error.code }
}
fn from_dispatch(error: ProfuseGwDispatchError) -> Self {
let code = match error {
ProfuseGwDispatchError::InterfaceNotFound => "INTERFACE_NOT_FOUND",
ProfuseGwDispatchError::RequestDataInvalid => "REQUEST_DATA_INVALID",
ProfuseGwDispatchError::ContextInvalid => "CONTEXT_INVALID",
ProfuseGwDispatchError::IdentityMismatch => "IDENTITY_MISMATCH",
};
Self { code }
}
}
type TestFuture<Response> = Pin<
Box<dyn Future<Output = Result<ProfuseGwTestExecution<Response>, ProfuseGwTestError>> + Send>,
>;
type TestExecutor<Response> = Box<dyn FnOnce(ProfuseGwTestRequest) -> TestFuture<Response> + Send>;
pub struct ProfuseGwTestHarness<Response> {
executor: TestExecutor<Response>,
}
impl<Response> ProfuseGwTestHarness<Response> {
#[doc(hidden)]
pub fn from_executor(
executor: impl FnOnce(ProfuseGwTestRequest) -> TestFuture<Response> + Send + 'static,
) -> Self {
Self {
executor: Box::new(executor),
}
}
pub async fn execute(
self,
request: ProfuseGwTestRequest,
) -> Result<ProfuseGwTestExecution<Response>, ProfuseGwTestError> {
(self.executor)(request).await
}
pub fn run(
self,
request: ProfuseGwTestRequest,
) -> Result<ProfuseGwTestExecution<Response>, ProfuseGwTestError> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|_| ProfuseGwTestError {
code: "TEST_RUNTIME_UNAVAILABLE",
})?;
runtime.block_on(self.execute(request))
}
}
#[doc(hidden)]
pub fn accept_test_request(
adapter: &saddle_boundary::ingress::ProfuseGwListenerAdapter,
request: ProfuseGwTestRequest,
) -> Result<saddle_boundary::ingress::AcceptedIngress, ProfuseGwTestError> {
adapter
.accept(
&request.method,
&request.path,
&request.media_type,
request.identity.into_boundary(),
request.json_body.as_bytes(),
)
.map_err(ProfuseGwTestError::from_codec)
}
#[doc(hidden)]
pub fn dispatch_error(error: ProfuseGwDispatchError) -> ProfuseGwTestError {
ProfuseGwTestError::from_dispatch(error)
}
#[doc(hidden)]
pub fn execution<Response>(
response: Response,
fake: &FakeProfuseContractBoundary,
) -> ProfuseGwTestExecution<Response> {
let attempts = fake
.attempts()
.into_iter()
.map(|attempt| ProfuseGwTestAttempt {
count: attempt.count,
request_id: attempt.request_id,
call_id: attempt.call_id,
user_id: attempt.user_id,
trace_id: attempt.trace_id,
rpc_id: attempt.rpc_id,
zone: attempt.zone,
idc: attempt.idc,
env: attempt.env,
function: attempt.function,
deadline_unix_ms: attempt.deadline_unix_ms,
})
.collect();
ProfuseGwTestExecution { response, attempts }
}