use super::client::MurTestClient;
use crate::server::aliases::MurFuture;
use crate::server::aliases::MurRouteHandler;
use crate::server::controller::MurController;
use crate::server::guard::MurGuard;
use crate::server::http::MurRequestContext;
use crate::server::interceptor::MurInterceptor;
use crate::server::middleware::MurMiddleware;
use crate::server::middleware::MurNext;
use crate::server::module::MurModule;
use crate::server::router::MurRouter;
use crate::server::service::MurInjectable;
use crate::server::service::MurInjects;
use crate::server::service::MurService;
use crate::server::service::MurServiceContainer;
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MurTestConfig {
pub verbose: bool,
pub request_timeout_ms: u64,
pub base_path: Option<String>,
pub auto_json: bool,
pub include_stack_traces: bool,
pub env_vars: HashMap<String, String>,
}
impl Default for MurTestConfig {
fn default() -> Self {
Self {
verbose: false,
request_timeout_ms: 5000,
base_path: None,
auto_json: true,
include_stack_traces: true,
env_vars: HashMap::new(),
}
}
}
impl MurTestConfig {
pub fn new() -> Self {
Self::default()
}
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
pub fn timeout(mut self, timeout_ms: u64) -> Self {
self.request_timeout_ms = timeout_ms;
self
}
pub fn base_path(mut self, path: impl Into<String>) -> Self {
self.base_path = Some(path.into());
self
}
pub fn no_auto_json(mut self) -> Self {
self.auto_json = false;
self
}
pub fn no_stack_traces(mut self) -> Self {
self.include_stack_traces = false;
self
}
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env_vars.insert(key.into(), value.into());
self
}
pub fn envs(mut self, vars: impl IntoIterator<Item = (String, String)>) -> Self {
self.env_vars.extend(vars);
self
}
}
pub struct MurTestApp {
client: MurTestClient,
container: Arc<MurServiceContainer>,
router: Arc<MurRouter>,
config: MurTestConfig,
}
impl MurTestApp {
pub fn builder() -> MurTestAppBuilder {
MurTestAppBuilder::new()
}
pub fn new(
router: Arc<MurRouter>,
container: Arc<MurServiceContainer>,
config: MurTestConfig,
) -> Self {
let client = MurTestClient::new(Arc::clone(&router), Arc::clone(&container));
Self {
client,
container,
router,
config,
}
}
pub fn client(&self) -> &MurTestClient {
&self.client
}
pub fn container(&self) -> &Arc<MurServiceContainer> {
&self.container
}
pub fn service<T: MurService>(&self) -> Option<Arc<T>> {
self.container.get::<T>()
}
pub fn router(&self) -> &Arc<MurRouter> {
&self.router
}
pub fn config(&self) -> &MurTestConfig {
&self.config
}
pub fn get(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.get(&self.resolve_path(path))
}
pub fn post(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.post(&self.resolve_path(path))
}
pub fn put(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.put(&self.resolve_path(path))
}
pub fn patch(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.patch(&self.resolve_path(path))
}
pub fn delete(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.delete(&self.resolve_path(path))
}
pub fn head(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.head(&self.resolve_path(path))
}
pub fn options(&self, path: &str) -> super::client::MurTestRequestBuilder {
self.client.options(&self.resolve_path(path))
}
pub fn request(
&self,
method: http::Method,
path: &str,
) -> super::client::MurTestRequestBuilder {
self.client.request(method, &self.resolve_path(path))
}
fn resolve_path(&self, path: &str) -> String {
match &self.config.base_path {
Some(base) => {
let base = base.trim_end_matches('/');
let path = path.trim_start_matches('/');
format!("{}/{}", base, path)
}
None => path.to_string(),
}
}
}
pub struct MurTestAppBuilder {
config: MurTestConfig,
modules: Vec<Arc<dyn MurModule>>,
controllers: Vec<Arc<dyn MurController>>,
services: Vec<(TypeId, Arc<dyn MurService>)>,
injects: MurInjects,
guards: Vec<Arc<dyn MurGuard>>,
interceptors: Vec<Arc<dyn MurInterceptor>>,
middleware: Vec<Arc<dyn MurMiddleware>>,
routes: Vec<(String, String, MurRouteHandler)>,
}
impl MurTestAppBuilder {
pub fn new() -> Self {
Self {
config: MurTestConfig::default(),
modules: Vec::new(),
controllers: Vec::new(),
services: Vec::new(),
injects: MurInjects::new(),
guards: Vec::new(),
interceptors: Vec::new(),
middleware: Vec::new(),
routes: Vec::new(),
}
}
pub fn config(mut self, config: MurTestConfig) -> Self {
self.config = config;
self
}
pub fn configure<F>(mut self, f: F) -> Self
where
F: FnOnce(MurTestConfig) -> MurTestConfig,
{
self.config = f(self.config);
self
}
pub fn module<M: MurModule>(mut self, module: M) -> Self {
self.modules.push(Arc::new(module));
self
}
pub fn modules<I, M>(mut self, modules: I) -> Self
where
I: IntoIterator<Item = M>,
M: MurModule,
{
for module in modules {
self.modules.push(Arc::new(module));
}
self
}
pub fn controller<C: MurController>(mut self, controller: C) -> Self {
self.controllers.push(Arc::new(controller));
self
}
pub fn service<S: MurService>(mut self, service: S) -> Self {
self.services
.push((TypeId::of::<S>(), Arc::new(service) as Arc<dyn MurService>));
self
}
pub fn service_for<T: 'static, S: MurService>(mut self, service: S) -> Self {
self.services
.push((TypeId::of::<T>(), Arc::new(service) as Arc<dyn MurService>));
self
}
pub fn service_arc<S: MurService>(mut self, service: Arc<S>) -> Self {
self.services
.push((TypeId::of::<S>(), service as Arc<dyn MurService>));
self
}
pub fn inject<I: MurInjectable>(mut self, inject: I) -> Self {
self.injects.register(inject);
self
}
pub fn inject_arc<I: MurInjectable>(mut self, inject: Arc<I>) -> Self {
self.injects.register_arc(inject);
self
}
pub fn guard<G: MurGuard>(mut self, guard: G) -> Self {
self.guards.push(Arc::new(guard));
self
}
pub fn interceptor<I: MurInterceptor>(mut self, interceptor: I) -> Self {
self.interceptors.push(Arc::new(interceptor));
self
}
pub fn middleware<M: MurMiddleware>(mut self, middleware: M) -> Self {
self.middleware.push(Arc::new(middleware));
self
}
pub fn route(
mut self,
method: impl Into<String>,
path: impl Into<String>,
handler: MurRouteHandler,
) -> Self {
self.routes.push((method.into(), path.into(), handler));
self
}
pub fn build(self) -> MurTestApp {
let mut container = MurServiceContainer::new();
for (_type_id, service) in self.services {
container.register_dyn_service(service);
}
let container = Arc::new(container);
let mut router = MurRouter::new(Arc::clone(&container));
for controller in &self.controllers {
for r in controller.clone().routes(Arc::clone(&container)) {
router.route(&r.method, &r.path, r.handler);
}
}
for (method, path, handler) in self.routes {
router.route(&method, &path, handler);
}
let router = Arc::new(router);
let mut client = MurTestClient::new(Arc::clone(&router), Arc::clone(&container));
for mw in self.middleware {
client = client.with_middleware(MiddlewareWrapper(mw));
}
MurTestApp {
client,
container,
router,
config: self.config,
}
}
}
impl Default for MurTestAppBuilder {
fn default() -> Self {
Self::new()
}
}
struct MiddlewareWrapper(Arc<dyn MurMiddleware>);
impl MurMiddleware for MiddlewareWrapper {
fn handle(&self, ctx: MurRequestContext, next: MurNext) -> MurFuture {
self.0.handle(ctx, next)
}
fn name(&self) -> &str {
self.0.name()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_defaults() {
let config = MurTestConfig::default();
assert!(!config.verbose);
assert_eq!(config.request_timeout_ms, 5000);
assert!(config.base_path.is_none());
assert!(config.auto_json);
assert!(config.include_stack_traces);
assert!(config.env_vars.is_empty());
}
#[test]
fn test_config_builder() {
let config = MurTestConfig::new()
.verbose()
.timeout(10000)
.base_path("/api/v1")
.no_auto_json()
.no_stack_traces()
.env("TEST_VAR", "test_value");
assert!(config.verbose);
assert_eq!(config.request_timeout_ms, 10000);
assert_eq!(config.base_path, Some("/api/v1".to_string()));
assert!(!config.auto_json);
assert!(!config.include_stack_traces);
assert_eq!(
config.env_vars.get("TEST_VAR"),
Some(&"test_value".to_string())
);
}
#[test]
fn test_app_builder_new() {
let builder = MurTestAppBuilder::new();
assert!(builder.modules.is_empty());
assert!(builder.controllers.is_empty());
assert!(builder.services.is_empty());
}
#[test]
fn test_resolve_path_without_base() {
let container = Arc::new(MurServiceContainer::new());
let app = MurTestApp::new(
Arc::new(MurRouter::new(Arc::clone(&container))),
container,
MurTestConfig::new(),
);
assert_eq!(app.resolve_path("/users"), "/users");
assert_eq!(app.resolve_path("users"), "users");
}
#[test]
fn test_resolve_path_with_base() {
let config = MurTestConfig::new().base_path("/api/v1");
let container = Arc::new(MurServiceContainer::new());
let app = MurTestApp::new(
Arc::new(MurRouter::new(Arc::clone(&container))),
container,
config,
);
assert_eq!(app.resolve_path("/users"), "/api/v1/users");
assert_eq!(app.resolve_path("users"), "/api/v1/users");
}
#[test]
fn test_resolve_path_with_trailing_slash_base() {
let config = MurTestConfig::new().base_path("/api/v1/");
let container = Arc::new(MurServiceContainer::new());
let app = MurTestApp::new(
Arc::new(MurRouter::new(Arc::clone(&container))),
container,
config,
);
assert_eq!(app.resolve_path("/users"), "/api/v1/users");
}
}