feat: add remove upstream and remove upstream target handlers
This commit is contained in:
@@ -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 remove_upstream;
|
||||||
|
pub mod remove_upstream_target;
|
||||||
pub mod update_upstream;
|
pub mod update_upstream;
|
||||||
pub mod update_upstream_target;
|
pub mod update_upstream_target;
|
||||||
|
|
||||||
@@ -23,7 +25,9 @@ pub fn get_upstream_router(state: Arc<AppState>) -> Router {
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/upstreams/{upstream_id}",
|
"/upstreams/{upstream_id}",
|
||||||
get(get_upstream::get_upstream).patch(update_upstream::update_upstream),
|
get(get_upstream::get_upstream)
|
||||||
|
.patch(update_upstream::update_upstream)
|
||||||
|
.delete(remove_upstream::remove_upstream),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/upstreams/{upstream_id}/targets",
|
"/upstreams/{upstream_id}/targets",
|
||||||
@@ -32,7 +36,8 @@ pub fn get_upstream_router(state: Arc<AppState>) -> Router {
|
|||||||
.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),
|
.patch(update_upstream_target::update_upstream_target)
|
||||||
|
.delete(remove_upstream_target::remove_upstream_target),
|
||||||
)
|
)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
extract::{Path, State},
|
||||||
|
response::Result as AxumResult,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::api_error::ApiError, middlewares::request_info::AuthenticatedRequestInfo,
|
||||||
|
routes::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn remove_upstream(
|
||||||
|
_request_info: AuthenticatedRequestInfo,
|
||||||
|
Path(upstream_id): Path<Uuid>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> AxumResult<Json<()>, ApiError> {
|
||||||
|
let upstream_service = &state.service.nginx.get_upstream_service();
|
||||||
|
|
||||||
|
upstream_service.delete_upstream(upstream_id, None).await?;
|
||||||
|
|
||||||
|
Ok(Json(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_test::TestServer;
|
||||||
|
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase, MockExecResult};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_remove_upstream_succeeds_returns_ok() {
|
||||||
|
let up_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
|
let existing = upstream::Model {
|
||||||
|
id: up_id,
|
||||||
|
name: "todelete".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 db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||||
|
.append_query_results(vec![vec![existing.clone()]])
|
||||||
|
.append_exec_results(vec![
|
||||||
|
MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
last_insert_id: 0,
|
||||||
|
},
|
||||||
|
MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
last_insert_id: 0,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
let router = get_router_with_state(db.clone());
|
||||||
|
let server = TestServer::new(router).expect("failed to create test server");
|
||||||
|
|
||||||
|
let res = server.delete(&format!("/upstreams/{}", up_id)).await;
|
||||||
|
|
||||||
|
res.assert_status_ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn handler_remove_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 res = server
|
||||||
|
.delete(&format!("/upstreams/{}", uuid::Uuid::new_v4()))
|
||||||
|
.add_header(REQUEST_AUTH_USER_INVALID_HEADER, "true")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(StatusCode::UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn handler_remove_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 res = server
|
||||||
|
.delete(&format!("/upstreams/{}", uuid::Uuid::new_v4()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
extract::{Path, State},
|
||||||
|
response::Result as AxumResult,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::api_error::ApiError, middlewares::request_info::AuthenticatedRequestInfo,
|
||||||
|
routes::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn remove_upstream_target(
|
||||||
|
_request_info: AuthenticatedRequestInfo,
|
||||||
|
Path(upstream_target_id): Path<Uuid>,
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> AxumResult<Json<()>, ApiError> {
|
||||||
|
let upstream_service = &state.service.nginx.get_upstream_service();
|
||||||
|
|
||||||
|
upstream_service
|
||||||
|
.delete_upstream_target(upstream_target_id, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum_test::TestServer;
|
||||||
|
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase, MockExecResult};
|
||||||
|
|
||||||
|
use database::generated::entities::upstream_target;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_remove_upstream_target_succeeds_returns_ok() {
|
||||||
|
let ut_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
|
let current_model = upstream_target::Model {
|
||||||
|
id: ut_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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// first find_by_id, then delete (delete typically doesn't return models)
|
||||||
|
let first: Vec<Vec<upstream_target::Model>> = vec![vec![current_model.clone()]];
|
||||||
|
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||||
|
.append_query_results(first)
|
||||||
|
.append_exec_results(vec![MockExecResult {
|
||||||
|
rows_affected: 1,
|
||||||
|
last_insert_id: 0,
|
||||||
|
}])
|
||||||
|
.into_connection();
|
||||||
|
|
||||||
|
let router = get_router_with_state(db.clone());
|
||||||
|
let server = TestServer::new(router).expect("failed to create test server");
|
||||||
|
|
||||||
|
let res = server.delete(&format!("/upstream_targets/{}", ut_id)).await;
|
||||||
|
|
||||||
|
res.assert_status_ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn handler_remove_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 res = server
|
||||||
|
.delete(&format!("/upstream_targets/{}", uuid::Uuid::new_v4()))
|
||||||
|
.add_header(REQUEST_AUTH_USER_INVALID_HEADER, "true")
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(StatusCode::UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn handler_remove_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 res = server
|
||||||
|
.delete(&format!("/upstream_targets/{}", uuid::Uuid::new_v4()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
res.assert_status(StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,6 +208,11 @@ impl UpstreamService {
|
|||||||
)))?
|
)))?
|
||||||
});
|
});
|
||||||
with_conn!(&*self.connection, tx, conn, {
|
with_conn!(&*self.connection, tx, conn, {
|
||||||
|
// delete all targets belonging to the upstream
|
||||||
|
upstream_target::Entity::delete_many()
|
||||||
|
.filter(upstream_target::Column::UpstreamId.eq(upstream_id))
|
||||||
|
.exec(*conn)
|
||||||
|
.await?;
|
||||||
model.delete(*conn).await?;
|
model.delete(*conn).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user