From 4fe03b245eac90c676fff237da4308e92ac4e048 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:49:46 +0800 Subject: [PATCH] test: added create location with proxy pass tests --- .../nginx/proxy_host/create_location.rs | 241 ++++++++++++------ 1 file changed, 160 insertions(+), 81 deletions(-) diff --git a/apps/api/src/routes/api/restricted/nginx/proxy_host/create_location.rs b/apps/api/src/routes/api/restricted/nginx/proxy_host/create_location.rs index 99a038e..312398e 100644 --- a/apps/api/src/routes/api/restricted/nginx/proxy_host/create_location.rs +++ b/apps/api/src/routes/api/restricted/nginx/proxy_host/create_location.rs @@ -49,6 +49,87 @@ pub struct CreateLocationRequestBodyByProxyPass { pub custom_config: Option, } +impl From<(uuid::Uuid, CreateLocationRequestBody)> for CreateLocationInfo { + fn from(val: (uuid::Uuid, CreateLocationRequestBody)) -> Self { + match val.1 { + CreateLocationRequestBody::UpstreamId(body) => Self::from((val.0, body)), + CreateLocationRequestBody::ProxyPass(body) => Self::from((val.0, body)), + } + } +} + +impl From<(uuid::Uuid, CreateLocationRequestBodyByUpstreamId)> for CreateLocationInfo { + fn from((proxy_id, payload): (uuid::Uuid, CreateLocationRequestBodyByUpstreamId)) -> Self { + Self { + host_id: proxy_id, + path: payload.path, + match_type: payload.match_type, + order: payload.order, + upstream_id: Some(payload.upstream_id), + proxy_pass_protocol: None, + proxy_pass_host: None, + proxy_pass_port: None, + preserve_host_header: payload.preserve_host_header, + allowed_methods: payload.allowed_methods, + custom_config: payload.custom_config, + enabled: true, + } + } +} + +impl From<(uuid::Uuid, CreateLocationRequestBodyByProxyPass)> for CreateLocationInfo { + fn from((proxy_id, payload): (uuid::Uuid, CreateLocationRequestBodyByProxyPass)) -> Self { + Self { + host_id: proxy_id, + path: payload.path, + match_type: payload.match_type, + order: payload.order, + upstream_id: None, + proxy_pass_protocol: Some(payload.proxy_pass_protocol), + proxy_pass_host: Some(payload.proxy_pass_host), + proxy_pass_port: Some(payload.proxy_pass_port), + preserve_host_header: payload.preserve_host_header, + allowed_methods: payload.allowed_methods, + custom_config: payload.custom_config, + enabled: true, + } + } +} + +#[axum::debug_handler] +#[utoipa::path( + post, + path = "/api/nginx/proxy_hosts/{proxy_id}/locations", + request_body = CreateLocationRequestBody, + responses( + (status = 200, description = "Location created", body = LocationInfoResponse), + (status = 401, description = "Unauthorized"), + (status = 422, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = NGINX_TAG, +)] +pub async fn create_location( + _request_info: AuthenticatedRequestInfo, + axum::extract::Path(proxy_id): axum::extract::Path, + State(state): State>, + Json(payload): Json, +) -> AxumResult, ApiError> { + let svc = &state.service.nginx.get_location_service(); + let create_info: CreateLocationInfo = (proxy_id, payload).into(); + + let mut tx = state.database_connection.begin().await?; + let info = svc.create_location(create_info, Some(&mut tx)).await?; + state + .service + .nginx + .regenerate_and_apply_config(state.service.agent_client.clone(), &Some(&mut tx)) + .await?; + tx.commit().await?; + + Ok(Json(info.into())) +} + #[cfg(test)] mod tests { use std::sync::Arc; @@ -63,6 +144,7 @@ mod tests { configs::{FromConfig, ProgramSettings}, middlewares::require_auth::mock::REQUEST_AUTH_USER_INVALID_HEADER, routes::api::restricted::nginx::proxy_host::{ + create_location::CreateLocationRequestBodyByProxyPass, create_location::CreateLocationRequestBodyByUpstreamId, get_proxy_router, }, services::{agent_client::MockAgentService, get_mock_app_service}, @@ -162,6 +244,84 @@ mod tests { assert_eq!(body.id, loc_id); } + #[tokio::test] + async fn handler_create_location_proxy_pass_succeeds_returns_created() { + let ph_id = uuid::Uuid::new_v4(); + let loc_id = uuid::Uuid::new_v4(); + + let loc_model = location::Model { + id: loc_id, + host_id: ph_id, + path: "/".to_string(), + match_type: "prefix".to_string(), + order: 1, + upstream_id: None, + proxy_pass_protocol: Some("http".to_string()), + proxy_pass_host: Some("127.0.0.1".to_string()), + proxy_pass_port: Some(8080), + preserve_host_header: None, + allowed_methods: None, + custom_config: None, + enabled: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + + let up_id = uuid::Uuid::new_v4(); + let up_model = upstream::Model { + id: up_id, + name: "u1".to_string(), + protocol: "http".to_string(), + algorithm: "rr".to_string(), + sticky_session: false, + created_by: None, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + let target_model = upstream_target::Model { + id: uuid::Uuid::new_v4(), + upstream_id: up_id, + target_host: "127.0.0.1".to_string(), + target_port: 8080, + weight: 1, + is_backup: false, + enabled: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + let dummy_row: Vec = vec![]; + + let db = MockDatabase::new(DatabaseBackend::Sqlite) + .append_query_results(vec![vec![loc_model.clone()]]) + // additional query result for regenerate_and_apply_config -> generate_config + .append_query_results(vec![vec![(up_model.clone(), Some(target_model.clone()))]]) + .append_query_results(vec![dummy_row]) + .into_connection(); + + let router = get_router_with_state(db.clone()); + let server = TestServer::new(router).expect("failed to create test server"); + + let payload = CreateLocationRequestBodyByProxyPass { + path: "/".to_string(), + match_type: "prefix".to_string(), + order: 1, + proxy_pass_protocol: "http".to_string(), + proxy_pass_host: "127.0.0.1".to_string(), + proxy_pass_port: 8080, + preserve_host_header: None, + allowed_methods: None, + custom_config: None, + }; + + let res = server + .post(&format!("/proxy_hosts/{}/locations", ph_id)) + .json(&payload) + .await; + res.assert_status_ok(); + let body = res.json::(); + assert_eq!(body.id, loc_id); + } + #[tokio::test] async fn handler_create_location_invalid_payload_returns_bad_request() { let db = MockDatabase::new(DatabaseBackend::Sqlite).into_connection(); @@ -200,84 +360,3 @@ mod tests { res.assert_status(StatusCode::UNAUTHORIZED); } } - -impl From<(uuid::Uuid, CreateLocationRequestBody)> for CreateLocationInfo { - fn from(val: (uuid::Uuid, CreateLocationRequestBody)) -> Self { - match val.1 { - CreateLocationRequestBody::UpstreamId(body) => Self::from((val.0, body)), - CreateLocationRequestBody::ProxyPass(body) => Self::from((val.0, body)), - } - } -} - -impl From<(uuid::Uuid, CreateLocationRequestBodyByUpstreamId)> for CreateLocationInfo { - fn from((proxy_id, payload): (uuid::Uuid, CreateLocationRequestBodyByUpstreamId)) -> Self { - Self { - host_id: proxy_id, - path: payload.path, - match_type: payload.match_type, - order: payload.order, - upstream_id: Some(payload.upstream_id), - proxy_pass_protocol: None, - proxy_pass_host: None, - proxy_pass_port: None, - preserve_host_header: payload.preserve_host_header, - allowed_methods: payload.allowed_methods, - custom_config: payload.custom_config, - enabled: true, - } - } -} - -impl From<(uuid::Uuid, CreateLocationRequestBodyByProxyPass)> for CreateLocationInfo { - fn from((proxy_id, payload): (uuid::Uuid, CreateLocationRequestBodyByProxyPass)) -> Self { - Self { - host_id: proxy_id, - path: payload.path, - match_type: payload.match_type, - order: payload.order, - upstream_id: None, - proxy_pass_protocol: Some(payload.proxy_pass_protocol), - proxy_pass_host: Some(payload.proxy_pass_host), - proxy_pass_port: Some(payload.proxy_pass_port), - preserve_host_header: payload.preserve_host_header, - allowed_methods: payload.allowed_methods, - custom_config: payload.custom_config, - enabled: true, - } - } -} - -#[axum::debug_handler] -#[utoipa::path( - post, - path = "/api/nginx/proxy_hosts/{proxy_id}/locations", - request_body = CreateLocationRequestBody, - responses( - (status = 200, description = "Location created", body = LocationInfoResponse), - (status = 401, description = "Unauthorized"), - (status = 422, description = "Invalid request"), - (status = 500, description = "Internal server error"), - ), - tag = NGINX_TAG, -)] -pub async fn create_location( - _request_info: AuthenticatedRequestInfo, - axum::extract::Path(proxy_id): axum::extract::Path, - State(state): State>, - Json(payload): Json, -) -> AxumResult, ApiError> { - let svc = &state.service.nginx.get_location_service(); - let create_info: CreateLocationInfo = (proxy_id, payload).into(); - - let mut tx = state.database_connection.begin().await?; - let info = svc.create_location(create_info, Some(&mut tx)).await?; - state - .service - .nginx - .regenerate_and_apply_config(state.service.agent_client.clone(), &Some(&mut tx)) - .await?; - tx.commit().await?; - - Ok(Json(info.into())) -}