feature/upstream-service #13

Merged
GW_MC merged 43 commits from feature/upstream-service into master 2026-01-01 10:49:32 +08:00
7 changed files with 555 additions and 35 deletions
Showing only changes of commit f4db47daf2 - Show all commits

View File

@@ -3,6 +3,8 @@ pub mod create_upstream_target;
pub mod get_upstream; pub mod get_upstream;
pub mod get_upstream_target; pub mod get_upstream_target;
pub mod info; pub mod info;
pub mod update_upstream;
pub mod update_upstream_target;
use std::sync::Arc; use std::sync::Arc;
@@ -19,14 +21,18 @@ pub fn get_upstream_router(state: Arc<AppState>) -> Router {
"/upstreams", "/upstreams",
get(get_upstream::get_upstream_list).post(create_upstream::create_upstream), get(get_upstream::get_upstream_list).post(create_upstream::create_upstream),
) )
.route("/upstreams/{upstream_id}", get(get_upstream::get_upstream)) .route(
"/upstreams/{upstream_id}",
get(get_upstream::get_upstream).patch(update_upstream::update_upstream),
)
.route( .route(
"/upstreams/{upstream_id}/targets", "/upstreams/{upstream_id}/targets",
post(create_upstream_target::add_upstream_target), post(create_upstream_target::add_upstream_target),
) )
.route( .route(
"/upstream_targets/{upstream_target_id}", "/upstream_targets/{upstream_target_id}",
get(get_upstream_target::get_upstream_target), get(get_upstream_target::get_upstream_target)
.patch(update_upstream_target::update_upstream_target),
) )
.with_state(state) .with_state(state)
} }

View File

@@ -162,3 +162,71 @@ impl From<crate::services::nginx::info::upstream_target::UpstreamTargetInfo>
} }
} }
} }
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct UpdateUpstreamInfoResponse {
pub id: uuid::Uuid,
pub name: String,
pub protocol: String,
pub algorithm: String,
pub sticky_session: bool,
pub created_by: Option<uuid::Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
//
pub upstream_targets: Vec<UpstreamTargetBasicInfo>,
}
impl From<crate::services::nginx::info::upstream::UpstreamInfo> for UpdateUpstreamInfoResponse {
fn from(info: crate::services::nginx::info::upstream::UpstreamInfo) -> Self {
Self {
id: info.id,
name: info.name,
protocol: info.protocol,
algorithm: info.algorithm,
sticky_session: info.sticky_session,
created_by: info.created_by,
created_at: info.created_at,
updated_at: info.updated_at,
upstream_targets: info
.upstream_targets
.into_iter()
.map(|t| t.into())
.collect(),
}
}
}
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct UpdateUpstreamTargetInfoResponse {
pub id: uuid::Uuid,
pub host: String,
pub port: i64,
pub enabled: bool,
pub is_backup: bool,
pub weight: i32,
//
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
//
pub upstream_id: Uuid,
}
impl From<crate::services::nginx::info::upstream_target::UpstreamTargetInfo>
for UpdateUpstreamTargetInfoResponse
{
fn from(info: crate::services::nginx::info::upstream_target::UpstreamTargetInfo) -> Self {
Self {
id: info.id,
host: info.target_host,
port: info.target_port,
enabled: info.enabled,
is_backup: info.is_backup,
weight: info.weight as i32,
//
created_at: info.created_at,
updated_at: info.updated_at,
upstream_id: info.upstream_id,
}
}
}

View File

@@ -0,0 +1,203 @@
use std::sync::Arc;
use axum::{
Json,
extract::{Path, State},
response::Result as AxumResult,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::api_error::ApiError,
middlewares::request_info::AuthenticatedRequestInfo,
routes::{
AppState, api::restricted::nginx::upstream::info::response::UpdateUpstreamInfoResponse,
},
services::nginx::info::upstream::UpdateUpstreamInfo,
};
#[derive(Deserialize, utoipa::ToSchema, Serialize)]
pub struct UpstreamTargetBasicUpdateInfo {
pub id: i64,
pub enabled: bool,
}
#[derive(Deserialize, utoipa::ToSchema, Serialize)]
pub struct UpdateUpstreamRequestBody {
pub name: Option<String>,
pub protocol: Option<String>,
pub algorithm: Option<String>,
pub sticky_session: Option<bool>,
// only updates upstream targets' enabled status for now
pub upstream_targets: Option<Vec<UpstreamTargetBasicUpdateInfo>>,
}
impl From<UpdateUpstreamRequestBody> for UpdateUpstreamInfo {
fn from(val: UpdateUpstreamRequestBody) -> Self {
Self {
name: val.name,
protocol: val.protocol,
algorithm: val.algorithm,
sticky_session: val.sticky_session,
//
upstream_targets: None,
}
}
}
pub async fn update_upstream(
_request_info: AuthenticatedRequestInfo,
Path(upstream_id): Path<Uuid>,
State(state): State<Arc<AppState>>,
Json(payload): Json<UpdateUpstreamRequestBody>,
) -> AxumResult<Json<UpdateUpstreamInfoResponse>, ApiError> {
let upstream_service = &state.service.nginx.get_upstream_service();
let update_info: UpdateUpstreamInfo = payload.into();
let r = upstream_service
.update_upstream(upstream_id, update_info, None)
.await?;
Ok(Json(r.into()))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use axum::http::StatusCode;
use axum_test::TestServer;
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase};
use database::generated::entities::upstream;
use crate::{
configs::{FromConfig, ProgramSettings},
middlewares::require_auth::mock::REQUEST_AUTH_USER_INVALID_HEADER,
routes::api::restricted::nginx::upstream::get_upstream_router,
services::get_app_service,
};
use super::UpdateUpstreamRequestBody;
fn get_router_with_state(db: DatabaseConnection) -> axum::Router {
let program_settings = ProgramSettings::mock();
let app_service = get_app_service(&Arc::new(db.clone()), &program_settings);
let state = Arc::new(crate::routes::AppState {
database_connection: Arc::new(db),
service: Arc::new(app_service),
config: Arc::new(program_settings),
});
get_upstream_router(state).layer(axum::middleware::from_fn(
crate::middlewares::require_auth::mock::mock_require_auth,
))
}
#[tokio::test]
async fn handler_update_upstream_succeeds_returns_ok() {
let up_id = uuid::Uuid::new_v4();
let current_model = upstream::Model {
id: up_id,
name: "old_upstream".to_string(),
protocol: "http".to_string(),
algorithm: "round_robin".to_string(),
sticky_session: false,
created_by: Some(uuid::Uuid::new_v4()),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
let updated_model = upstream::Model {
id: up_id,
name: "updated_upstream".to_string(),
protocol: "http".to_string(),
algorithm: "round_robin".to_string(),
sticky_session: false,
created_by: Some(uuid::Uuid::new_v4()),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
// first find_by_id, then update returns updated model
let first: Vec<Vec<upstream::Model>> = vec![vec![current_model.clone()]];
let second: Vec<Vec<upstream::Model>> = vec![vec![updated_model.clone()]];
let db = MockDatabase::new(DatabaseBackend::Sqlite)
.append_query_results(first)
.append_query_results(second)
.into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamRequestBody {
name: Some("updated_upstream".to_string()),
protocol: None,
algorithm: None,
sticky_session: None,
upstream_targets: None,
};
let res = server
.patch(&format!("/upstreams/{}", up_id))
.json(&payload)
.await;
res.assert_status_ok();
let text = res.text();
let body: crate::routes::api::restricted::nginx::upstream::info::response::UpdateUpstreamInfoResponse =
serde_json::from_str(&text).expect("failed to parse json");
assert_eq!(body.id, up_id);
assert_eq!(body.name, "updated_upstream");
}
#[tokio::test]
async fn handler_update_upstream_unauthenticated_returns_unauthorized() {
let db = MockDatabase::new(DatabaseBackend::Sqlite).into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamRequestBody {
name: Some("updated_upstream".to_string()),
protocol: None,
algorithm: None,
sticky_session: None,
upstream_targets: None,
};
let res = server
.patch(&format!("/upstreams/{}", uuid::Uuid::new_v4()))
.add_header(REQUEST_AUTH_USER_INVALID_HEADER, "true")
.json(&payload)
.await;
res.assert_status(StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn handler_update_upstream_not_found_returns_not_found() {
let empty_results: Vec<Vec<upstream::Model>> = vec![Vec::<upstream::Model>::new()];
let db = MockDatabase::new(DatabaseBackend::Sqlite)
.append_query_results(empty_results)
.into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamRequestBody {
name: Some("updated_upstream".to_string()),
protocol: None,
algorithm: None,
sticky_session: None,
upstream_targets: None,
};
let res = server
.patch(&format!("/upstreams/{}", uuid::Uuid::new_v4()))
.json(&payload)
.await;
res.assert_status(StatusCode::NOT_FOUND);
}
}

View File

@@ -0,0 +1,206 @@
use std::sync::Arc;
use axum::{
Json,
extract::{Path, State},
response::Result as AxumResult,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::api_error::ApiError,
middlewares::request_info::AuthenticatedRequestInfo,
routes::{
AppState,
api::restricted::nginx::upstream::info::response::UpdateUpstreamTargetInfoResponse,
},
services::nginx::info::upstream_target::UpdateUpstreamTargetInfo,
};
#[derive(Deserialize, utoipa::ToSchema, Serialize)]
pub struct UpdateUpstreamTargetRequestBody {
pub host: Option<String>,
pub port: Option<i64>,
pub enabled: Option<bool>,
pub is_backup: Option<bool>,
pub weight: Option<i32>,
}
impl From<UpdateUpstreamTargetRequestBody> for UpdateUpstreamTargetInfo {
fn from(val: UpdateUpstreamTargetRequestBody) -> Self {
Self {
target_host: val.host,
target_port: val.port,
enabled: val.enabled,
is_backup: val.is_backup,
weight: val.weight.map(|w| w as i64),
}
}
}
pub async fn update_upstream_target(
_request_info: AuthenticatedRequestInfo,
Path(upstream_target_id): Path<Uuid>,
State(state): State<Arc<AppState>>,
Json(payload): Json<UpdateUpstreamTargetRequestBody>,
) -> AxumResult<Json<UpdateUpstreamTargetInfoResponse>, ApiError> {
let upstream_service = &state.service.nginx.get_upstream_service();
let update_info: UpdateUpstreamTargetInfo = payload.into();
let r = upstream_service
.update_upstream_target(upstream_target_id, update_info, None)
.await?;
Ok(Json(r.into()))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use axum::http::StatusCode;
use axum::routing::patch;
use axum_test::TestServer;
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase};
use database::generated::entities::upstream_target;
use super::UpdateUpstreamTargetRequestBody;
use crate::{
configs::{FromConfig, ProgramSettings},
middlewares::require_auth::mock::REQUEST_AUTH_USER_INVALID_HEADER,
services::get_app_service,
};
fn get_router_with_state(db: DatabaseConnection) -> axum::Router {
let program_settings = ProgramSettings::mock();
let app_service = get_app_service(&Arc::new(db.clone()), &program_settings);
let state = Arc::new(crate::routes::AppState {
database_connection: Arc::new(db),
service: Arc::new(app_service),
config: Arc::new(program_settings),
});
axum::Router::new()
.route(
"/upstream_targets/{upstream_target_id}",
patch(crate::routes::api::restricted::nginx::upstream::update_upstream_target::update_upstream_target),
)
.with_state(state)
.layer(axum::middleware::from_fn(
crate::middlewares::require_auth::mock::mock_require_auth,
))
}
#[tokio::test]
async fn handler_update_upstream_target_succeeds_returns_ok() {
let target_id = uuid::Uuid::new_v4();
let current_model = upstream_target::Model {
id: target_id,
upstream_id: uuid::Uuid::new_v4(),
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 updated_model = upstream_target::Model {
id: target_id,
upstream_id: current_model.upstream_id,
target_host: "127.0.0.1".to_string(),
target_port: 8081,
weight: 2,
is_backup: false,
enabled: false,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
let first: Vec<Vec<upstream_target::Model>> = vec![vec![current_model.clone()]];
let second: Vec<Vec<upstream_target::Model>> = vec![vec![updated_model.clone()]];
let db = MockDatabase::new(DatabaseBackend::Sqlite)
.append_query_results(first)
.append_query_results(second)
.into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamTargetRequestBody {
host: None,
port: Some(8081),
enabled: Some(false),
is_backup: None,
weight: Some(2),
};
let res = server
.patch(&format!("/upstream_targets/{}", target_id))
.json(&payload)
.await;
res.assert_status_ok();
let text = res.text();
let body: crate::routes::api::restricted::nginx::upstream::info::response::UpdateUpstreamTargetInfoResponse =
serde_json::from_str(&text).expect("failed to parse json");
assert_eq!(body.id, target_id);
assert_eq!(body.port, 8081);
assert!(!body.enabled);
}
#[tokio::test]
async fn handler_update_upstream_target_unauthenticated_returns_unauthorized() {
let db = MockDatabase::new(DatabaseBackend::Sqlite).into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamTargetRequestBody {
host: None,
port: Some(8081),
enabled: Some(false),
is_backup: None,
weight: Some(2),
};
let res = server
.patch(&format!("/upstream_targets/{}", uuid::Uuid::new_v4()))
.add_header(REQUEST_AUTH_USER_INVALID_HEADER, "true")
.json(&payload)
.await;
res.assert_status(StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn handler_update_upstream_target_not_found_returns_not_found() {
let empty_results: Vec<Vec<upstream_target::Model>> =
vec![Vec::<upstream_target::Model>::new()];
let db = MockDatabase::new(DatabaseBackend::Sqlite)
.append_query_results(empty_results)
.into_connection();
let router = get_router_with_state(db.clone());
let server = TestServer::new(router).expect("failed to create test server");
let payload = UpdateUpstreamTargetRequestBody {
host: None,
port: Some(8081),
enabled: Some(false),
is_backup: None,
weight: Some(2),
};
let res = server
.patch(&format!("/upstream_targets/{}", uuid::Uuid::new_v4()))
.json(&payload)
.await;
res.assert_status(StatusCode::NOT_FOUND);
}
}

View File

@@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
use optfield::optfield; use optfield::optfield;
use database::generated::entities::{upstream, upstream_target}; use database::generated::entities::{upstream, upstream_target};
use sea_orm::ActiveValue::{Set, Unchanged};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@@ -13,15 +14,14 @@ use crate::{
set_if_some, set_if_some,
}; };
#[optfield(pub UpdateUpstreamInfo)]
#[derive(Clone)] #[derive(Clone)]
pub struct UpstreamInfo { pub struct UpstreamInfo {
pub id: uuid::Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub protocol: String, pub protocol: String,
pub algorithm: String, pub algorithm: String,
pub sticky_session: bool, pub sticky_session: bool,
pub created_by: Option<uuid::Uuid>, pub created_by: Option<Uuid>,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
// //
@@ -33,11 +33,21 @@ pub struct UpstreamCreateInfo {
pub protocol: String, pub protocol: String,
pub algorithm: String, pub algorithm: String,
pub sticky_session: bool, pub sticky_session: bool,
pub created_by: Option<uuid::Uuid>, pub created_by: Option<Uuid>,
// //
pub upstream_targets: Vec<upstream_target_info::UpstreamTargetCreateInfo>, pub upstream_targets: Vec<upstream_target_info::UpstreamTargetCreateInfo>,
} }
#[derive(Clone)]
pub struct UpdateUpstreamInfo {
pub name: Option<String>,
pub protocol: Option<String>,
pub algorithm: Option<String>,
pub sticky_session: Option<bool>,
//
pub upstream_targets: Option<Vec<(Uuid, bool)>>,
}
impl NginxConfigProvider for UpstreamInfo { impl NginxConfigProvider for UpstreamInfo {
fn to_nginx_config(&self, indent: Option<usize>) -> String { fn to_nginx_config(&self, indent: Option<usize>) -> String {
let targets_config: Vec<String> = self let targets_config: Vec<String> = self
@@ -142,18 +152,14 @@ impl From<UpstreamInfo> for (upstream::ActiveModel, Vec<upstream_target::ActiveM
impl UpdateUpstreamInfo { impl UpdateUpstreamInfo {
pub fn apply_to_model(self, current_model: upstream::Model) -> upstream::ActiveModel { pub fn apply_to_model(self, current_model: upstream::Model) -> upstream::ActiveModel {
upstream::ActiveModel { upstream::ActiveModel {
id: sea_orm::ActiveValue::Unchanged(current_model.id), id: Unchanged(current_model.id),
name: set_if_some!(self.name), name: set_if_some!(self.name),
protocol: set_if_some!(self.protocol), protocol: set_if_some!(self.protocol),
algorithm: set_if_some!(self.algorithm), algorithm: set_if_some!(self.algorithm),
sticky_session: set_if_some!(self.sticky_session), sticky_session: set_if_some!(self.sticky_session),
created_by: set_if_some!(if self.created_by.is_some() { created_by: Unchanged(current_model.created_by),
Some(self.created_by) created_at: Unchanged(current_model.created_at),
} else { updated_at: Set(chrono::Utc::now()),
None
}),
created_at: set_if_some!(self.created_at),
updated_at: set_if_some!(self.updated_at),
} }
} }
} }

View File

@@ -1,5 +1,4 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use optfield::optfield;
use sea_orm::ActiveValue::{Set, Unchanged}; use sea_orm::ActiveValue::{Set, Unchanged};
use uuid::Uuid; use uuid::Uuid;
@@ -11,7 +10,6 @@ use crate::{
set_if_some, set_if_some,
}; };
#[optfield(pub UpdateUpstreamTargetInfo)]
#[derive(Clone)] #[derive(Clone)]
pub struct UpstreamTargetInfo { pub struct UpstreamTargetInfo {
pub id: uuid::Uuid, pub id: uuid::Uuid,
@@ -27,6 +25,15 @@ pub struct UpstreamTargetInfo {
pub upstream: Option<UpstreamBasicInfo>, pub upstream: Option<UpstreamBasicInfo>,
} }
#[derive(Clone)]
pub struct UpdateUpstreamTargetInfo {
pub target_host: Option<String>,
pub target_port: Option<i64>,
pub weight: Option<i64>,
pub is_backup: Option<bool>,
pub enabled: Option<bool>,
}
#[derive(Clone)] #[derive(Clone)]
pub struct UpstreamBasicInfo { pub struct UpstreamBasicInfo {
pub id: uuid::Uuid, pub id: uuid::Uuid,
@@ -146,9 +153,9 @@ impl UpdateUpstreamTargetInfo {
weight: set_if_some!(self.weight), weight: set_if_some!(self.weight),
is_backup: set_if_some!(self.is_backup), is_backup: set_if_some!(self.is_backup),
enabled: set_if_some!(self.enabled), enabled: set_if_some!(self.enabled),
created_at: set_if_some!(self.created_at), created_at: Unchanged(current_model.created_at),
updated_at: set_if_some!(self.updated_at), updated_at: Set(chrono::Utc::now()),
upstream_id: set_if_some!(self.upstream_id), upstream_id: Unchanged(current_model.upstream_id),
} }
} }
} }

View File

@@ -142,7 +142,16 @@ impl UpstreamService {
upstream: UpdateUpstreamInfo, upstream: UpdateUpstreamInfo,
tx: Option<&mut DatabaseTransaction>, tx: Option<&mut DatabaseTransaction>,
) -> Result<UpstreamInfo, ServiceError> { ) -> Result<UpstreamInfo, ServiceError> {
let current_model = with_conn!(&*self.connection, tx, conn, { // If a transaction was provided use it, otherwise create and own one here.
let mut maybe_owned_tx: Option<DatabaseTransaction> = None;
let tx_ref: Option<&mut DatabaseTransaction> = if let Some(tx) = tx {
Some(tx)
} else {
maybe_owned_tx = Some(self.connection.begin().await?);
maybe_owned_tx.as_mut()
};
let current_model = with_conn!(&*self.connection, tx_ref, conn, {
upstream::Entity::find_by_id(id) upstream::Entity::find_by_id(id)
.one(*conn) .one(*conn)
.await? .await?
@@ -151,9 +160,36 @@ impl UpstreamService {
id id
)))? )))?
}); });
let active_model = upstream.apply_to_model(current_model); let upstream_active_model = upstream.clone().apply_to_model(current_model);
let r = active_model.update(&*self.connection).await?; let r = with_conn!(&*self.connection, tx_ref, conn, {
let updated_upstream_model = upstream_active_model.update(*conn).await?;
// update upstream targets if any
if let Some(targets) = upstream.upstream_targets {
for (target_id, enabled) in targets.into_iter() {
let target_model = upstream_target::Entity::find_by_id(target_id)
.one(*conn)
.await?
.ok_or(ServiceError::NotFound(format!(
"Upstream target with id {} not found",
target_id
)))?;
let mut target_active_model: upstream_target::ActiveModel = target_model.into();
target_active_model.enabled = sea_orm::ActiveValue::Set(enabled);
target_active_model.update(*conn).await?;
Ok::<(), ServiceError>(())?;
}
}
updated_upstream_model
});
// Commit
if let Some(t) = maybe_owned_tx.take() {
t.commit().await?;
}
Ok(r.into()) Ok(r.into())
} }
@@ -494,14 +530,10 @@ mod tests {
let svc = UpstreamService::new(Arc::new(db)); let svc = UpstreamService::new(Arc::new(db));
let update_info = crate::services::nginx::info::upstream::UpdateUpstreamInfo { let update_info = crate::services::nginx::info::upstream::UpdateUpstreamInfo {
id: None,
name: None, name: None,
protocol: None, protocol: None,
algorithm: None, algorithm: None,
sticky_session: None, sticky_session: None,
created_by: None,
created_at: None,
updated_at: None,
upstream_targets: None, upstream_targets: None,
}; };
let res = svc.update_upstream(id, update_info, None).await; let res = svc.update_upstream(id, update_info, None).await;
@@ -522,14 +554,11 @@ mod tests {
.update_upstream( .update_upstream(
uuid::Uuid::new_v4(), uuid::Uuid::new_v4(),
crate::services::nginx::info::upstream::UpdateUpstreamInfo { crate::services::nginx::info::upstream::UpdateUpstreamInfo {
id: None,
name: None, name: None,
protocol: None, protocol: None,
algorithm: None, algorithm: None,
sticky_session: None, sticky_session: None,
created_by: None,
created_at: None,
updated_at: None,
upstream_targets: None, upstream_targets: None,
}, },
None, None,
@@ -650,16 +679,11 @@ mod tests {
let svc = UpstreamService::new(Arc::new(db)); let svc = UpstreamService::new(Arc::new(db));
let update_info = crate::services::nginx::info::upstream_target::UpdateUpstreamTargetInfo { let update_info = crate::services::nginx::info::upstream_target::UpdateUpstreamTargetInfo {
id: None,
target_host: None, target_host: None,
target_port: None, target_port: None,
weight: None, weight: None,
is_backup: None, is_backup: None,
enabled: None, enabled: None,
created_at: None,
updated_at: None,
upstream_id: None,
upstream: None,
}; };
let res = svc.update_upstream_target(id, update_info, None).await; let res = svc.update_upstream_target(id, update_info, None).await;
assert!(res.is_ok()); assert!(res.is_ok());