use crate::{
Container, ContainerBuilder, InjectError, ModuleDefinition, ModuleGraph, Provider,
ProviderDefinition,
};
use axum::{
Router,
body::{Body, to_bytes},
extract::ConnectInfo,
http::{self, HeaderMap, Method, Request, StatusCode},
};
use serde::{Serialize, de::DeserializeOwned};
use std::{fmt, net::SocketAddr, time::Duration};
use tower::ServiceExt;
#[derive(Default)]
pub struct TestingModule {
modules: Vec<ModuleDefinition>,
builder: ContainerBuilder,
overrides: Vec<ProviderDefinition>,
}
impl TestingModule {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn module(mut self, module: ModuleDefinition) -> Self {
self.modules.push(module);
self
}
#[must_use]
pub fn provider(mut self, provider: ProviderDefinition) -> Self {
self.builder = self.builder.provider(provider);
self
}
#[must_use]
pub fn injectable<T: Provider>(self) -> Self {
self.provider(T::definition())
}
#[must_use]
pub fn override_value<T: Send + Sync + 'static>(self, value: T) -> Self {
self.override_provider(ProviderDefinition::value(value))
}
#[must_use]
pub fn override_provider(mut self, replacement: ProviderDefinition) -> Self {
self.overrides.push(replacement);
self
}
pub fn compile(self) -> Result<Container, InjectError> {
let mut modules = self.modules;
let extra = ModuleDefinition::new("xecute::testing::standalone");
let mut extra = extra;
for provider in self.builder.into_definitions() {
extra = extra.provider(provider);
}
modules.push(extra);
let container = ModuleGraph::new(modules).build_with_overrides(self.overrides)?;
container.initialize()?;
Ok(container)
}
}
#[derive(Debug)]
pub struct TestError(String);
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for TestError {}
#[derive(Clone)]
pub struct TestApp {
router: Router,
body_limit: usize,
timeout: Duration,
}
impl TestApp {
pub fn new(router: Router) -> Self {
Self {
router,
body_limit: 1_048_576,
timeout: Duration::from_secs(5),
}
}
#[must_use]
pub fn response_limit(mut self, bytes: usize) -> Self {
self.body_limit = bytes;
self
}
#[must_use]
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn request(&self, method: Method, uri: &str) -> TestRequest {
let mut builder = Request::builder().method(method).uri(uri);
if let Some(extensions) = builder.extensions_mut() {
extensions.insert(ConnectInfo(SocketAddr::from(([127, 0, 0, 1], 4000))));
}
TestRequest {
router: self.router.clone(),
builder,
body: Ok(Body::empty()),
limit: self.body_limit,
timeout: self.timeout,
}
}
pub fn get(&self, uri: &str) -> TestRequest {
self.request(Method::GET, uri)
}
pub fn post(&self, uri: &str) -> TestRequest {
self.request(Method::POST, uri)
}
pub fn patch(&self, uri: &str) -> TestRequest {
self.request(Method::PATCH, uri)
}
pub fn delete(&self, uri: &str) -> TestRequest {
self.request(Method::DELETE, uri)
}
}
pub struct TestRequest {
router: Router,
builder: http::request::Builder,
body: Result<Body, TestError>,
limit: usize,
timeout: Duration,
}
impl TestRequest {
#[must_use]
pub fn header(mut self, name: &str, value: &str) -> Self {
self.builder = self.builder.header(name, value);
self
}
#[must_use]
pub fn json<T: Serialize>(mut self, value: &T) -> Self {
self.builder = self
.builder
.header(http::header::CONTENT_TYPE, "application/json");
self.body = serde_json::to_vec(value)
.map(Body::from)
.map_err(|_| TestError("could not serialize test JSON".into()));
self
}
#[must_use]
pub fn body(mut self, body: impl Into<Body>) -> Self {
self.body = Ok(body.into());
self
}
#[must_use]
pub fn peer(mut self, peer: SocketAddr) -> Self {
if let Some(extensions) = self.builder.extensions_mut() {
extensions.insert(ConnectInfo(peer));
}
self
}
pub async fn send(self) -> Result<TestResponse, TestError> {
tokio::time::timeout(self.timeout, async move {
let request = self
.builder
.body(self.body?)
.map_err(|error| TestError(error.to_string()))?;
let response = self
.router
.oneshot(request)
.await
.expect("Router is infallible");
let (parts, body) = response.into_parts();
let bytes = to_bytes(body, self.limit).await.map_err(|_| {
TestError("response body failed or exceeded the configured test limit".into())
})?;
Ok(TestResponse {
status: parts.status,
headers: parts.headers,
body: bytes.to_vec(),
})
})
.await
.map_err(|_| TestError("test request exceeded its timeout".into()))?
}
}
#[derive(Debug)]
pub struct TestResponse {
pub status: StatusCode,
pub headers: HeaderMap,
pub body: Vec<u8>,
}
impl TestResponse {
pub fn json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
serde_json::from_slice(&self.body)
}
#[must_use]
pub fn assert_status(self, expected: StatusCode) -> Self {
assert_eq!(self.status, expected);
self
}
#[must_use]
pub fn assert_json(self, expected: &serde_json::Value) -> Self {
assert_eq!(
&self
.json::<serde_json::Value>()
.expect("response must be JSON"),
expected
);
self
}
}