Files
codex-proxy-rs/backend/crates/gateway-api/tests/admin/errors.rs
T
2026-08-29 20:22:14 +08:00

321 lines
9.0 KiB
Rust

use axum::{
Router,
body::{Body, to_bytes},
http::{Method, Request, StatusCode, header},
};
use gateway_api::admin;
use serde_json::{Value, json};
use tower::ServiceExt as _;
use super::{AdminTestFixture, AdminTestState};
const SESSION_COOKIE: &str = "cpr_admin_session=valid-session";
fn app(state: AdminTestState) -> Router {
admin::router::<AdminTestState>().with_state(state)
}
fn request(method: Method, uri: &str, body: Body) -> Request<Body> {
Request::builder()
.method(method)
.uri(uri)
.header("x-request-id", "req_admin_errors")
.body(body)
.expect("build admin error request")
}
async fn response_json(response: axum::response::Response) -> (StatusCode, String, Value) {
let status = response.status();
let content_type = response
.headers()
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_owned();
let body = to_bytes(response.into_body(), 64 * 1024)
.await
.expect("read admin error response body");
let body = serde_json::from_slice(&body).expect("parse admin error response JSON");
(status, content_type, body)
}
#[tokio::test]
async fn malformed_login_json_should_use_the_admin_error_envelope() {
let fixture = AdminTestFixture::new().await;
let mut request = request(
Method::POST,
"/api/admin/auth/login",
Body::from(r#"{"password":"secret""#),
);
request
.headers_mut()
.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
let response = app(fixture.state())
.oneshot(request)
.await
.expect("malformed JSON response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::BAD_REQUEST,
"application/json".to_owned(),
json!({
"code": 40000,
"message": "请求体不是合法 JSON",
"data": null
})
)
);
}
#[tokio::test]
async fn invalid_login_json_data_should_not_echo_the_submitted_value() {
let fixture = AdminTestFixture::new().await;
let submitted = "password-must-not-leak";
let mut request = request(
Method::POST,
"/api/admin/auth/login",
Body::from(json!({ "password": submitted, "rememberMe": true }).to_string()),
);
request
.headers_mut()
.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
let response = app(fixture.state())
.oneshot(request)
.await
.expect("invalid JSON data response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::UNPROCESSABLE_ENTITY,
"application/json".to_owned(),
json!({
"code": 40001,
"message": "请求字段不合法",
"data": null
})
)
);
assert!(!actual.2.to_string().contains(submitted));
}
#[tokio::test]
async fn login_without_json_content_type_should_keep_415_with_an_admin_envelope() {
let fixture = AdminTestFixture::new().await;
let response = app(fixture.state())
.oneshot(request(
Method::POST,
"/api/admin/auth/login",
Body::from(json!({ "password": "secret" }).to_string()),
))
.await
.expect("missing JSON content type response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::UNSUPPORTED_MEDIA_TYPE,
"application/json".to_owned(),
json!({
"code": 40001,
"message": "请求必须使用 application/json",
"data": null
})
)
);
}
#[tokio::test]
async fn malformed_admin_query_should_use_a_safe_bad_request_envelope() {
let fixture = AdminTestFixture::new().await;
fixture.auth.insert_session("valid-session");
let mut request = request(
Method::GET,
"/api/admin/usage/records?page=not-a-number",
Body::empty(),
);
request
.headers_mut()
.insert(header::COOKIE, SESSION_COOKIE.parse().unwrap());
let response = app(fixture.state())
.oneshot(request)
.await
.expect("invalid query response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::BAD_REQUEST,
"application/json".to_owned(),
json!({
"code": 40001,
"message": "请求参数不合法",
"data": null
})
)
);
}
#[tokio::test]
async fn invalid_time_range_should_use_its_published_business_code() {
let fixture = AdminTestFixture::new().await;
fixture.auth.insert_session("valid-session");
let mut request = request(
Method::GET,
"/api/admin/dashboard/summary?startTime=not-a-time",
Body::empty(),
);
request
.headers_mut()
.insert(header::COOKIE, SESSION_COOKIE.parse().unwrap());
let response = app(fixture.state())
.oneshot(request)
.await
.expect("invalid time range response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::BAD_REQUEST,
"application/json".to_owned(),
json!({
"code": 40002,
"message": "时间范围不合法",
"data": null
})
)
);
}
#[tokio::test]
async fn unknown_admin_path_should_not_fall_through_to_the_spa() {
let fixture = AdminTestFixture::new().await;
let response = app(fixture.state())
.oneshot(request(
Method::GET,
"/api/admin/not-a-real-route",
Body::empty(),
))
.await
.expect("unknown admin path response");
let actual = response_json(response).await;
assert_eq!(
actual,
(
StatusCode::NOT_FOUND,
"application/json".to_owned(),
json!({
"code": 40401,
"message": "管理接口不存在",
"data": null
})
)
);
}
#[tokio::test]
async fn unsupported_admin_method_should_keep_allow_and_return_an_envelope() {
let fixture = AdminTestFixture::new().await;
let response = app(fixture.state())
.oneshot(request(
Method::POST,
"/api/admin/auth/status",
Body::empty(),
))
.await
.expect("unsupported admin method response");
let allow = response
.headers()
.get(header::ALLOW)
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_owned();
let actual = response_json(response).await;
assert_eq!(
(allow, actual),
(
"GET,HEAD".to_owned(),
(
StatusCode::METHOD_NOT_ALLOWED,
"application/json".to_owned(),
json!({
"code": 40001,
"message": "请求方法不受支持",
"data": null
})
)
)
);
}
#[tokio::test]
async fn admin_auth_failures_should_use_stable_chinese_contracts() {
let fixture = AdminTestFixture::new().await;
let unauthenticated = app(fixture.state())
.clone()
.oneshot(request(Method::GET, "/api/admin/accounts", Body::empty()))
.await
.expect("missing session response");
let mut invalid_api_key = request(Method::GET, "/api/admin/accounts", Body::empty());
invalid_api_key
.headers_mut()
.insert("x-api-key", "admin-invalid".parse().unwrap());
let invalid_api_key = app(fixture.state())
.clone()
.oneshot(invalid_api_key)
.await
.expect("invalid API key response");
let mut invalid_login = request(
Method::POST,
"/api/admin/auth/login",
Body::from(json!({ "password": "wrong-password" }).to_string()),
);
invalid_login
.headers_mut()
.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
let invalid_login = app(fixture.state())
.oneshot(invalid_login)
.await
.expect("invalid credentials response");
let actual = [
response_json(unauthenticated).await,
response_json(invalid_login).await,
response_json(invalid_api_key).await,
];
assert_eq!(
actual.map(|(status, _, body)| (status, body["code"].clone(), body["message"].clone())),
[
(
StatusCode::UNAUTHORIZED,
json!(40101),
json!("需要管理员登录")
),
(
StatusCode::UNAUTHORIZED,
json!(40102),
json!("管理员用户名或密码错误")
),
(
StatusCode::UNAUTHORIZED,
json!(40103),
json!("管理 API Key 无效")
),
]
);
}