test: cover account connection tests

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-06-27 04:21:37 +08:00
co-authored by Codex
parent 82db09fa34
commit fcf058f4d8
3 changed files with 188 additions and 45 deletions
+1
View File
@@ -36,6 +36,7 @@ mod import_export;
mod lifecycle;
mod list;
mod quota;
mod testing;
async fn seed_admin_session(pool: &SqlitePool, session_id: &str) {
sqlx::query(
+182
View File
@@ -0,0 +1,182 @@
use super::*;
use codex_proxy_rs::upstream::protocol::sse::{encode_sse_event, parse_sse_events};
use wiremock::{
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
};
async fn seed_test_account(pool: &SqlitePool, secret_box: SecretBox) {
seed_encrypted_account(
pool,
secret_box,
NewAccount {
id: "acct_test".to_string(),
email: Some("test@example.com".to_string()),
account_id: Some("chatgpt-test".to_string()),
user_id: None,
label: None,
plan_type: Some("plus".to_string()),
access_token: SecretString::new("access-test".to_string().into()),
refresh_token: None,
access_token_expires_at: None,
status: AccountStatus::Active,
added_at: None,
},
)
.await;
}
fn test_events(body: &str) -> Vec<Value> {
parse_sse_events(body)
.unwrap()
.into_iter()
.map(|event| serde_json::from_str::<Value>(&event.data).unwrap())
.collect()
}
#[tokio::test]
async fn account_test_models_should_return_upstream_models_only() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/backend-api/codex/models"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"models": [
{
"slug": "gpt-5.5",
"display_name": "GPT 5.5"
},
{
"id": "gpt-5.4",
"title": "GPT 5.4"
}
]
})))
.mount(&server)
.await;
let (app, _state, pool, _dir, secret_box) = admin_accounts_test_app_with_api_base_url(
"admin-account-test-models.sqlite",
91,
format!("{}/backend-api", server.uri()),
)
.await;
seed_test_account(&pool, secret_box).await;
let response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/admin/accounts/acct_test/test-models")
.header("cookie", "cpr_admin_session=session_1")
.header("x-request-id", "req_account_test_models")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
let requests = server.received_requests().await.unwrap();
let upstream = requests
.iter()
.find(|request| request.url.path() == "/backend-api/codex/models")
.expect("models request should hit upstream");
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
upstream
.headers
.get("authorization")
.and_then(|value| value.to_str().ok()),
Some("Bearer access-test")
);
assert_eq!(
upstream
.headers
.get("chatgpt-account-id")
.and_then(|value| value.to_str().ok()),
Some("chatgpt-test")
);
let body = response_json(response).await;
assert_eq!(body["data"]["models"][0]["id"], "gpt-5.5");
assert_eq!(body["data"]["models"][0]["label"], "GPT 5.5");
assert_eq!(body["data"]["models"][1]["id"], "gpt-5.4");
assert_eq!(body["data"]["models"][1]["label"], "GPT 5.4");
}
#[tokio::test]
async fn account_test_stream_should_translate_upstream_responses_sse() {
let server = MockServer::start().await;
let upstream_sse = [
encode_sse_event(
"",
&json!({
"type": "response.output_text.delta",
"delta": "ok"
})
.to_string(),
),
encode_sse_event("", &json!({ "type": "response.completed" }).to_string()),
]
.join("");
Mock::given(method("POST"))
.and(path("/backend-api/codex/responses"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_string(upstream_sse),
)
.mount(&server)
.await;
let (app, _state, pool, _dir, secret_box) = admin_accounts_test_app_with_api_base_url(
"admin-account-test-stream.sqlite",
92,
format!("{}/backend-api", server.uri()),
)
.await;
seed_test_account(&pool, secret_box).await;
let response = app
.oneshot(
Request::builder()
.method("POST")
.uri("/api/admin/accounts/acct_test/test")
.header("content-type", "application/json")
.header("cookie", "cpr_admin_session=session_1")
.header("x-request-id", "req_account_test_stream")
.body(Body::from(json!({ "modelId": "gpt-5.5" }).to_string()))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response
.headers()
.get("content-type")
.and_then(|value| value.to_str().ok()),
Some("text/event-stream")
);
let body = String::from_utf8(
to_bytes(response.into_body(), usize::MAX)
.await
.unwrap()
.to_vec(),
)
.unwrap();
let events = test_events(&body);
let requests = server.received_requests().await.unwrap();
let upstream = requests
.iter()
.find(|request| request.url.path() == "/backend-api/codex/responses")
.expect("test request should hit responses upstream");
let upstream_body: Value = serde_json::from_slice(&upstream.body).unwrap();
assert_eq!(events[0]["type"], "test_start");
assert_eq!(events[0]["model"], "gpt-5.5");
assert_eq!(events[1]["type"], "content");
assert_eq!(events[1]["text"], "ok");
assert_eq!(events[2]["type"], "test_complete");
assert_eq!(events[2]["success"], true);
assert_eq!(upstream_body["model"], "gpt-5.5");
assert_eq!(upstream_body["stream"], true);
assert_eq!(upstream_body["store"], false);
}
+5 -45
View File
@@ -1,74 +1,34 @@
pub mod routes;
use std::collections::BTreeMap;
use codex_proxy_rs::config::settings::{
AdminQuotaWarningThresholds, AdminSettings, AdminSettingsPatch, SettingsService,
};
use codex_proxy_rs::config::settings::{AdminSettings, AdminSettingsPatch, SettingsService};
#[test]
fn settings_service_should_apply_retained_settings_patch() {
let mut current = AdminSettings {
default_model: "gpt-5.5".to_string(),
default_reasoning_effort: Some("high".to_string()),
service_tier: Some("flex".to_string()),
model_aliases: BTreeMap::from([("codex-fast".to_string(), "gpt-5.5".to_string())]),
refresh_enabled: true,
refresh_margin_seconds: 240,
refresh_concurrency: 2,
max_concurrent_per_account: 4,
request_interval_ms: 50,
rotation_strategy: "least_used".to_string(),
tier_priority: vec!["team".to_string(), "plus".to_string()],
quota_refresh_interval_minutes: 5,
quota_warning_thresholds: AdminQuotaWarningThresholds {
primary: vec![80, 90],
secondary: vec![70, 95],
},
quota_skip_exhausted: true,
logs_enabled: true,
logs_capacity: 2_000,
logs_capture_body: false,
usage_history_retention_days: Some(30),
..AdminSettings::default()
};
SettingsService::apply_patch(
&mut current,
AdminSettingsPatch {
default_model: Some(" gpt-6 ".to_string()),
default_reasoning_effort: Some("medium".to_string()),
service_tier: Some("priority".to_string()),
model_aliases: Some(BTreeMap::from([(
"fast".to_string(),
"gpt-6-fast".to_string(),
)])),
refresh_enabled: Some(false),
refresh_margin_seconds: Some(180),
refresh_concurrency: Some(3),
max_concurrent_per_account: Some(5),
request_interval_ms: Some(125),
rotation_strategy: Some("round_robin".to_string()),
tier_priority: Some(vec!["pro".to_string(), "plus".to_string()]),
quota_refresh_interval_minutes: Some(15),
quota_warning_thresholds: Some(AdminQuotaWarningThresholds {
primary: vec![75, 90],
secondary: vec![65, 95],
}),
quota_skip_exhausted: Some(false),
logs_enabled: Some(false),
logs_capacity: Some(3_000),
logs_capture_body: Some(true),
usage_history_retention_days: Some(60),
},
)
.expect("settings patch should be valid");
assert_eq!(current.default_model, "gpt-6");
assert_eq!(current.rotation_strategy, "round_robin");
assert_eq!(current.quota_warning_thresholds.primary, vec![75, 90]);
assert_eq!(current.usage_history_retention_days, Some(60));
assert!(!current.refresh_enabled);
assert!(current.logs_capture_body);
assert_eq!(current.max_concurrent_per_account, 5);
assert_eq!(current.request_interval_ms, 125);
assert_eq!(current.default_reasoning_effort.as_deref(), Some("high"));
}
#[test]