|
| 1 | +use async_trait::async_trait; |
| 2 | +use loco_openapi::prelude::routes; |
| 3 | +use loco_openapi::{ |
| 4 | + auth::{set_jwt_location, SecurityAddon}, |
| 5 | + prelude::openapi, // Make sure openapi macro is imported |
| 6 | +}; |
| 7 | +use loco_rs::{ |
| 8 | + app::{AppContext, Hooks, Initializer}, |
| 9 | + boot::{create_app, BootResult, StartMode}, |
| 10 | + config::Config, |
| 11 | + controller::AppRoutes, |
| 12 | + environment::Environment, |
| 13 | + prelude::*, |
| 14 | + task::Tasks, |
| 15 | +}; |
| 16 | +use serde::Serialize; // Added import for Album |
| 17 | +use serde_json::{json, Value}; |
| 18 | +use std::collections::BTreeMap; |
| 19 | +use utoipa::{OpenApi, ToSchema}; // Added ToSchema |
| 20 | + // Define a minimal TestApp |
| 21 | +use insta::assert_snapshot; |
| 22 | +struct TestApp; |
| 23 | + |
| 24 | +// --- Start: Embedded Album Controller --- |
| 25 | +mod album { |
| 26 | + use super::*; // Allow using imports from parent module |
| 27 | + use axum::debug_handler; |
| 28 | + use axum::routing::get; |
| 29 | + |
| 30 | + #[derive(Serialize, Debug, ToSchema)] |
| 31 | + pub struct Album { |
| 32 | + title: String, |
| 33 | + rating: u32, |
| 34 | + } |
| 35 | + |
| 36 | + /// Get album |
| 37 | + /// |
| 38 | + /// Returns a title and rating |
| 39 | + #[utoipa::path( |
| 40 | + get, |
| 41 | + path = "/api/album/get_album", |
| 42 | + tags = ["album"], |
| 43 | + responses( |
| 44 | + (status = 200, description = "Album found", body = Album), |
| 45 | + ), |
| 46 | + )] |
| 47 | + #[debug_handler] |
| 48 | + pub async fn get_album(State(_ctx): State<AppContext>) -> Result<Response> { |
| 49 | + format::json(Album { |
| 50 | + title: "VH II".to_string(), |
| 51 | + rating: 10, |
| 52 | + }) |
| 53 | + } |
| 54 | + |
| 55 | + pub fn routes() -> Routes { |
| 56 | + Routes::new() |
| 57 | + .prefix("api/album") |
| 58 | + .add("/get_album", openapi(get(get_album), routes!(get_album))) |
| 59 | + } |
| 60 | +} |
| 61 | +// --- End: Embedded Album Controller --- |
| 62 | + |
| 63 | +// Helper to create test configuration |
| 64 | +fn config_test() -> Config { |
| 65 | + let mut config = loco_rs::tests_cfg::config::test_config(); |
| 66 | + let mut initializers = BTreeMap::new(); |
| 67 | + let mut openapi_conf = serde_json::Map::new(); |
| 68 | + |
| 69 | + // Configure endpoints to match test requests |
| 70 | + openapi_conf.insert( |
| 71 | + "redoc".to_string(), |
| 72 | + json!({ |
| 73 | + "redoc": { |
| 74 | + "url": "/redoc" |
| 75 | + } |
| 76 | + }), |
| 77 | + ); |
| 78 | + openapi_conf.insert( |
| 79 | + "scalar".to_string(), |
| 80 | + json!({ |
| 81 | + "scalar": { |
| 82 | + "url": "/scalar" |
| 83 | + } |
| 84 | + }), |
| 85 | + ); |
| 86 | + openapi_conf.insert( |
| 87 | + "swagger".to_string(), |
| 88 | + json!({ |
| 89 | + "swagger": { |
| 90 | + "url": "/swagger-ui", // Ensure this matches the test URL |
| 91 | + "spec_json_url": "/api-docs/openapi.json" // Required for swagger |
| 92 | + } |
| 93 | + }), |
| 94 | + ); |
| 95 | + |
| 96 | + initializers.insert("openapi".to_string(), Value::Object(openapi_conf)); |
| 97 | + config.initializers = Some(initializers); |
| 98 | + config |
| 99 | +} |
| 100 | + |
| 101 | +// Implement Hooks for TestApp |
| 102 | +#[async_trait] |
| 103 | +impl Hooks for TestApp { |
| 104 | + fn app_name() -> &'static str { |
| 105 | + "loco-openapi-test" |
| 106 | + } |
| 107 | + fn app_version() -> String { |
| 108 | + env!("CARGO_PKG_VERSION").to_string() |
| 109 | + } |
| 110 | + |
| 111 | + fn routes(_ctx: &AppContext) -> AppRoutes { |
| 112 | + AppRoutes::with_default_routes().add_route(album::routes()) // Add album routes |
| 113 | + } |
| 114 | + |
| 115 | + async fn load_config(_environment: &Environment) -> Result<Config> { |
| 116 | + Ok(config_test()) |
| 117 | + } |
| 118 | + |
| 119 | + async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> { |
| 120 | + Ok(vec![Box::new( |
| 121 | + loco_openapi::OpenapiInitializerWithSetup::new( |
| 122 | + |ctx| { |
| 123 | + #[derive(OpenApi)] |
| 124 | + #[openapi( |
| 125 | + modifiers(&SecurityAddon), |
| 126 | + paths(album::get_album), // Add album path to OpenAPI spec |
| 127 | + components(schemas(album::Album)), // Add album schema |
| 128 | + info( |
| 129 | + title = "Loco Demo Test", |
| 130 | + description = "Test OpenAPI spec for loco-openapi" |
| 131 | + ) |
| 132 | + )] |
| 133 | + struct ApiDoc; |
| 134 | + set_jwt_location(ctx.into()); |
| 135 | + |
| 136 | + ApiDoc::openapi() |
| 137 | + }, |
| 138 | + None, |
| 139 | + ), |
| 140 | + )]) |
| 141 | + } |
| 142 | + |
| 143 | + async fn boot( |
| 144 | + mode: StartMode, |
| 145 | + environment: &Environment, |
| 146 | + config: Config, |
| 147 | + ) -> Result<BootResult> { |
| 148 | + // Assuming Migrator is not needed as per previous iteration |
| 149 | + create_app::<Self>(mode, environment, config).await |
| 150 | + } |
| 151 | + |
| 152 | + async fn connect_workers(_ctx: &AppContext, _queue: &Queue) -> Result<()> { |
| 153 | + Ok(()) |
| 154 | + } |
| 155 | + |
| 156 | + fn register_tasks(_tasks: &mut Tasks) {} |
| 157 | + |
| 158 | + // Removed truncate and seed as they are not part of the Hooks trait |
| 159 | +} |
| 160 | + |
| 161 | +// Test for OpenAPI UI Endpoints |
| 162 | +#[tokio::test] |
| 163 | +async fn test_openapi_ui_endpoints() { |
| 164 | + loco_rs::testing::request::request::<TestApp, _, _>(|rq, _ctx| async move { |
| 165 | + // Test Redoc endpoint |
| 166 | + let res_redoc = rq.get("/redoc").await; |
| 167 | + assert_eq!( |
| 168 | + res_redoc.status_code(), |
| 169 | + 200, |
| 170 | + "Expected /redoc to return 200 OK: {}", |
| 171 | + res_redoc.text() |
| 172 | + ); |
| 173 | + |
| 174 | + assert_snapshot!("redoc", res_redoc.text()); |
| 175 | + |
| 176 | + // Test Scalar endpoint |
| 177 | + let res_scalar = rq.get("/scalar").await; |
| 178 | + assert_eq!( |
| 179 | + res_scalar.status_code(), |
| 180 | + 200, |
| 181 | + "Expected /scalar to return 200 OK: {}", |
| 182 | + res_scalar.text() |
| 183 | + ); |
| 184 | + |
| 185 | + assert_snapshot!("scalar", res_scalar.text()); |
| 186 | + |
| 187 | + // Test Swagger UI endpoint (kept commented as per your change) |
| 188 | + // let res_swagger = rq.get("/swagger-ui").await; |
| 189 | + // assert_eq!( |
| 190 | + // res_swagger.status_code(), |
| 191 | + // 200, |
| 192 | + // "Expected /swagger-ui to return 200 OK: {}", |
| 193 | + // res_swagger.text().await // Added .await |
| 194 | + // ); |
| 195 | + |
| 196 | + // assert_snapshot!("swagger", res_swagger.text()); |
| 197 | + }) |
| 198 | + .await; |
| 199 | +} |
0 commit comments