feat: add tests for upstream and upstream target handlers
This commit is contained in:
@@ -153,3 +153,202 @@ pub async fn get_upstream(
|
||||
//
|
||||
Ok(Json(upstream_info.into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use axum_test::TestServer;
|
||||
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase};
|
||||
|
||||
use database::generated::entities::{upstream, upstream_target};
|
||||
|
||||
use crate::configs::{FromConfig, ProgramSettings};
|
||||
|
||||
use crate::routes::api::restricted::nginx::upstream::get_upstream_router;
|
||||
use crate::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)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_list_returns_list() {
|
||||
let u1 = upstream::Model {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
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 u2 = upstream::Model {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
name: "u2".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![u1.clone(), u2.clone()]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let res = server.get("/upstreams").await;
|
||||
res.assert_status_ok();
|
||||
let body = res.json::<UpstreamListResponse>();
|
||||
assert_eq!(body.items.len(), 2);
|
||||
assert_eq!(body.pagination.current_page, 1u32);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_with_targets_returns_targets() {
|
||||
let up_id = uuid::Uuid::new_v4();
|
||||
|
||||
let up_model = upstream::Model {
|
||||
id: up_id,
|
||||
name: "with_targets".to_string(),
|
||||
protocol: "http".to_string(),
|
||||
algorithm: "least_conn".to_string(),
|
||||
sticky_session: true,
|
||||
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 db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
// find_by_id -> returns upstream model
|
||||
.append_query_results(vec![vec![up_model.clone()]])
|
||||
// find targets -> returns the target(s)
|
||||
.append_query_results(vec![vec![target_model.clone()]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let url = format!("/upstreams/{}?include_targets=true", up_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status_ok();
|
||||
let body = res.json::<UpstreamInfoResponse>();
|
||||
assert_eq!(body.id, up_id);
|
||||
assert_eq!(body.upstream_targets.len(), 1);
|
||||
assert_eq!(body.upstream_targets[0].target_host, "127.0.0.1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extractor_pagination_validation_rejects_bad_values() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![Vec::<sea_orm::MockRow>::new()])
|
||||
.into_connection();
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
// page = 0 should be rejected
|
||||
let res = server.get("/upstreams?page=0&per_page=10").await;
|
||||
res.assert_status(StatusCode::BAD_REQUEST);
|
||||
|
||||
// per_page out of range should be rejected
|
||||
let res = server.get("/upstreams?page=1&per_page=0").await;
|
||||
res.assert_status(StatusCode::BAD_REQUEST);
|
||||
|
||||
// valid values accepted
|
||||
let res = server.get("/upstreams?page=2&per_page=5").await;
|
||||
res.assert_status_ok();
|
||||
let body = res.json::<UpstreamListResponse>();
|
||||
assert_eq!(body.pagination.current_page, 2u32);
|
||||
assert_eq!(body.pagination.per_page, 5u32);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_not_found_returns_service_error() {
|
||||
let up_id = uuid::Uuid::new_v4();
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![Vec::<sea_orm::MockRow>::new()])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let url = format!("/upstreams/{}?include_targets=false", up_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_without_targets_returns_info() {
|
||||
let up_id = uuid::Uuid::new_v4();
|
||||
|
||||
let up_model = upstream::Model {
|
||||
id: up_id,
|
||||
name: "simple_up".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)
|
||||
// find_by_id -> returns upstream model
|
||||
.append_query_results(vec![vec![up_model.clone()]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
// include_targets omitted -> should not include targets
|
||||
let url = format!("/upstreams/{}", up_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status_ok();
|
||||
let body = res.json::<UpstreamInfoResponse>();
|
||||
assert_eq!(body.id, up_id);
|
||||
assert!(body.upstream_targets.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_list_empty_returns_empty_items() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![Vec::<sea_orm::MockRow>::new()])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let res = server.get("/upstreams?page=3&per_page=10").await;
|
||||
res.assert_status_ok();
|
||||
let body = res.json::<UpstreamListResponse>();
|
||||
assert_eq!(body.items.len(), 0);
|
||||
assert_eq!(body.pagination.current_page, 3u32);
|
||||
assert_eq!(body.pagination.per_page, 10u32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,3 +97,127 @@ pub async fn get_upstream_target(
|
||||
.await?;
|
||||
Ok(Json(upstream_target_info.into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use axum_test::TestServer;
|
||||
use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase};
|
||||
|
||||
use database::generated::entities::{upstream, upstream_target};
|
||||
|
||||
use crate::configs::{FromConfig, ProgramSettings};
|
||||
|
||||
use crate::routes::api::restricted::nginx::upstream::get_upstream_router;
|
||||
use crate::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)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_target_with_upstream_returns_upstream() {
|
||||
let up_id = uuid::Uuid::new_v4();
|
||||
|
||||
let up_model = upstream::Model {
|
||||
id: up_id,
|
||||
name: "with_targets".to_string(),
|
||||
protocol: "http".to_string(),
|
||||
algorithm: "least_conn".to_string(),
|
||||
sticky_session: true,
|
||||
created_by: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let target_id = uuid::Uuid::new_v4();
|
||||
let target_model = upstream_target::Model {
|
||||
id: target_id,
|
||||
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 db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
// query returns joined (upstream_target, upstream)
|
||||
.append_query_results(vec![vec![(target_model.clone(), Some(up_model.clone()))]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let url = format!("/upstream_targets/{}?include_upstream=true", target_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status_ok();
|
||||
let text = res.text();
|
||||
println!("response body: {}", text);
|
||||
let body: UpstreamTargetInfo = serde_json::from_str(&text).expect("failed to parse json");
|
||||
assert_eq!(body.upstream_id, up_id);
|
||||
assert!(body.upstream.is_some());
|
||||
let upstream = body.upstream.expect("upstream to be present");
|
||||
assert_eq!(upstream.id, up_id);
|
||||
assert_eq!(upstream.name, "with_targets");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_target_without_upstream_returns_info() {
|
||||
let target_id = uuid::Uuid::new_v4();
|
||||
|
||||
let target_model = upstream_target::Model {
|
||||
id: target_id,
|
||||
upstream_id: uuid::Uuid::new_v4(),
|
||||
target_host: "10.0.0.1".to_string(),
|
||||
target_port: 9090,
|
||||
weight: 5,
|
||||
is_backup: false,
|
||||
enabled: true,
|
||||
created_at: chrono::Utc::now(),
|
||||
updated_at: chrono::Utc::now(),
|
||||
};
|
||||
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![target_model.clone()]])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let url = format!("/upstream_targets/{}", target_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status_ok();
|
||||
let text = res.text();
|
||||
let body: UpstreamTargetInfo = serde_json::from_str(&text).expect("failed to parse json");
|
||||
assert_eq!(body.id, target_id);
|
||||
assert!(body.upstream.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handler_get_upstream_target_not_found_returns_service_error() {
|
||||
let target_id = uuid::Uuid::new_v4();
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![Vec::<sea_orm::MockRow>::new()])
|
||||
.into_connection();
|
||||
|
||||
let router = get_router_with_state(db.clone());
|
||||
let server = TestServer::new(router).expect("failed to create test server");
|
||||
|
||||
let url = format!("/upstream_targets/{}?include_upstream=false", target_id);
|
||||
let res = server.get(&url).await;
|
||||
res.assert_status(StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user