Skip to content

Commit

Permalink
Add tests for controller config methods
Browse files Browse the repository at this point in the history
  • Loading branch information
spencewenski committed May 22, 2024
1 parent e75d610 commit 3327a8a
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 77 deletions.
63 changes: 8 additions & 55 deletions src/config/service/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use crate::app_context::AppContext;
use crate::config::service::http::initializer::Initializer;
use crate::config::service::http::middleware::Middleware;
use crate::controller::http::build_path;
use crate::util::serde_util::default_true;
use serde_derive::{Deserialize, Serialize};
use validator::{Validate, ValidationError};
Expand Down Expand Up @@ -40,43 +39,27 @@ impl Address {
}
}

#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Validate, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[validate(schema(function = "validate_default_routes"))]
pub struct DefaultRoutes {
#[serde(default = "default_true")]
pub default_enable: bool,
#[serde(default = "DefaultRouteConfig::default_ping")]
#[serde(default)]
pub ping: DefaultRouteConfig,
#[serde(default = "DefaultRouteConfig::default_health")]
#[serde(default)]
pub health: DefaultRouteConfig,
#[cfg(feature = "open-api")]
#[serde(default = "DefaultRouteConfig::default_api_schema")]
#[serde(default)]
pub api_schema: DefaultRouteConfig,
#[cfg(feature = "open-api")]
#[serde(default = "DefaultRouteConfig::default_scalar")]
#[serde(default)]
pub scalar: DefaultRouteConfig,
#[cfg(feature = "open-api")]
#[serde(default = "DefaultRouteConfig::default_redoc")]
#[serde(default)]
pub redoc: DefaultRouteConfig,
}

impl Default for DefaultRoutes {
fn default() -> Self {
Self {
default_enable: default_true(),
ping: DefaultRouteConfig::default_ping(),
health: DefaultRouteConfig::default_health(),
#[cfg(feature = "open-api")]
api_schema: DefaultRouteConfig::default_api_schema(),
#[cfg(feature = "open-api")]
scalar: DefaultRouteConfig::default_scalar(),
#[cfg(feature = "open-api")]
redoc: DefaultRouteConfig::default_redoc(),
}
}
}

fn validate_default_routes(
// This parameter isn't used for some feature flag combinations
#[allow(unused)] default_routes: &DefaultRoutes,
Expand All @@ -103,44 +86,14 @@ fn validate_default_routes(
Ok(())
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DefaultRouteConfig {
pub enable: Option<bool>,
pub route: String,
pub route: Option<String>,
}

impl DefaultRouteConfig {
fn new(route: &str) -> Self {
Self {
enable: None,
route: build_path("", route),
}
}

fn default_ping() -> Self {
DefaultRouteConfig::new("_ping")
}

fn default_health() -> Self {
DefaultRouteConfig::new("_health")
}

#[cfg(feature = "open-api")]
fn default_api_schema() -> Self {
DefaultRouteConfig::new("_docs/api.json")
}

#[cfg(feature = "open-api")]
fn default_scalar() -> Self {
DefaultRouteConfig::new("_docs")
}

#[cfg(feature = "open-api")]
fn default_redoc() -> Self {
DefaultRouteConfig::new("_docs/redoc")
}

pub fn enabled<S>(&self, context: &AppContext<S>) -> bool {
self.enable.unwrap_or(
context
Expand Down
143 changes: 131 additions & 12 deletions src/controller/http/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::app_context::AppContext;
use std::ops::Deref;
use std::sync::Arc;

use crate::config::app_config::AppConfig;
use crate::controller::http::build_path;
use aide::axum::routing::get_with;
use aide::axum::{ApiRouter, IntoApiResponse};
Expand All @@ -12,14 +13,16 @@ use aide::scalar::Scalar;
use axum::response::IntoResponse;
use axum::{Extension, Json};

const BASE: &str = "_docs";
const TAG: &str = "Docs";

/// This API is only available when using Aide.
pub fn routes<S>(parent: &str, context: &AppContext<S>) -> ApiRouter<AppContext<S>>
where
S: Clone + Send + Sync + 'static,
{
let open_api_schema_path = build_path(parent, api_schema_route(context));
let parent = build_path(parent, BASE);
let open_api_schema_path = build_path(&parent, &api_schema_route(context));

let router = ApiRouter::new();
if !api_schema_enabled(context) {
Expand All @@ -33,7 +36,7 @@ where

let router = if scalar_enabled(context) {
router.api_route_with(
&build_path(parent, scalar_route(context)),
&build_path(&parent, &scalar_route(context)),
get_with(
Scalar::new(&open_api_schema_path)
.with_title(&context.config().app.name)
Expand All @@ -48,7 +51,7 @@ where

let router = if redoc_enabled(context) {
router.api_route_with(
&build_path(parent, redoc_route(context)),
&build_path(&parent, &redoc_route(context)),
get_with(
Redoc::new(&open_api_schema_path)
.with_title(&context.config().app.name)
Expand Down Expand Up @@ -79,15 +82,17 @@ fn scalar_enabled<S>(context: &AppContext<S>) -> bool {
.enabled(context)
}

fn scalar_route<S>(context: &AppContext<S>) -> &str {
&context
.config()
fn scalar_route<S>(context: &AppContext<S>) -> String {
let config: &AppConfig = context.config();
config
.service
.http
.custom
.default_routes
.scalar
.route
.clone()
.unwrap_or_else(|| "/".to_string())
}

fn redoc_enabled<S>(context: &AppContext<S>) -> bool {
Expand All @@ -101,15 +106,17 @@ fn redoc_enabled<S>(context: &AppContext<S>) -> bool {
.enabled(context)
}

fn redoc_route<S>(context: &AppContext<S>) -> &str {
&context
.config()
fn redoc_route<S>(context: &AppContext<S>) -> String {
let config: &AppConfig = context.config();
config
.service
.http
.custom
.default_routes
.redoc
.route
.clone()
.unwrap_or_else(|| "redoc".to_string())
}

fn api_schema_enabled<S>(context: &AppContext<S>) -> bool {
Expand All @@ -123,13 +130,125 @@ fn api_schema_enabled<S>(context: &AppContext<S>) -> bool {
.enabled(context)
}

fn api_schema_route<S>(context: &AppContext<S>) -> &str {
&context
.config()
fn api_schema_route<S>(context: &AppContext<S>) -> String {
let config: &AppConfig = context.config();
config
.service
.http
.custom
.default_routes
.api_schema
.route
.clone()
.unwrap_or_else(|| "api.json".to_string())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::app::MockTestApp;
use crate::app_context::MockAppContext;
use crate::config::app_config::AppConfig;
use rstest::rstest;

// Todo: Is there a better way to structure these tests (and the ones in `health` and `ping`)
// to reduce duplication?
#[rstest]
#[case(false, None, None, false)]
#[case(false, Some(false), None, false)]
#[case(true, None, Some("/foo".to_string()), true)]
#[case(false, Some(true), None, true)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn scalar(
#[case] default_enable: bool,
#[case] enable: Option<bool>,
#[case] route: Option<String>,
#[case] enabled: bool,
) {
let mut config = AppConfig::empty(None).unwrap();
config.service.http.custom.default_routes.default_enable = default_enable;
config.service.http.custom.default_routes.scalar.enable = enable;
config
.service
.http
.custom
.default_routes
.scalar
.route
.clone_from(&route);
let mut context = MockAppContext::<MockTestApp>::default();
context.expect_config().return_const(config);

assert_eq!(scalar_enabled(&context), enabled);
assert_eq!(
scalar_route(&context),
route.unwrap_or_else(|| "/".to_string())
);
}

#[rstest]
#[case(false, None, None, false)]
#[case(false, Some(false), None, false)]
#[case(true, None, Some("/foo".to_string()), true)]
#[case(false, Some(true), None, true)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn redoc(
#[case] default_enable: bool,
#[case] enable: Option<bool>,
#[case] route: Option<String>,
#[case] enabled: bool,
) {
let mut config = AppConfig::empty(None).unwrap();
config.service.http.custom.default_routes.default_enable = default_enable;
config.service.http.custom.default_routes.redoc.enable = enable;
config
.service
.http
.custom
.default_routes
.redoc
.route
.clone_from(&route);
let mut context = MockAppContext::<MockTestApp>::default();
context.expect_config().return_const(config);

assert_eq!(redoc_enabled(&context), enabled);
assert_eq!(
redoc_route(&context),
route.unwrap_or_else(|| "redoc".to_string())
);
}

#[rstest]
#[case(false, None, None, false)]
#[case(false, Some(false), None, false)]
#[case(true, None, Some("/foo".to_string()), true)]
#[case(false, Some(true), None, true)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn api_schema(
#[case] default_enable: bool,
#[case] enable: Option<bool>,
#[case] route: Option<String>,
#[case] enabled: bool,
) {
let mut config = AppConfig::empty(None).unwrap();
config.service.http.custom.default_routes.default_enable = default_enable;
config.service.http.custom.default_routes.api_schema.enable = enable;
config
.service
.http
.custom
.default_routes
.api_schema
.route
.clone_from(&route);
let mut context = MockAppContext::<MockTestApp>::default();
context.expect_config().return_const(config);

assert_eq!(api_schema_enabled(&context), enabled);
assert_eq!(
api_schema_route(&context),
route.unwrap_or_else(|| "api.json".to_string())
);
}
}
Loading

0 comments on commit 3327a8a

Please sign in to comment.