Files
codex-proxy-rs/backend/crates/gateway-api/tests/support.rs
T
ZyyandCodex d922c3af60 fix(diagnostics): preserve error chains and avoid logging backpressure
Preserve native failure causes through store, provider, plugin, admin, and
worker boundaries, including secondary cleanup failures. Route operational
failures through the shared diagnostics sink with protected error details.

Use bounded nonblocking file logging with batched writes and reserved
warning/error capacity. Reserve observation capacity for failures and
request lifecycle records, and rate-limit overload warnings.

Validate error propagation and blocked-output HTTP, admin, SSE, and WebSocket
behavior; keep the local migration plan out of version control.

Co-authored-by: Codex <noreply@openai.com>
2026-10-07 00:25:18 +08:00

177 lines
5.4 KiB
Rust

//! API 测试共用的应用组装、认证请求与服务替身
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{Arc, atomic::Ordering},
};
use async_trait::async_trait;
use axum::{
body::{Body, to_bytes},
extract::ConnectInfo,
http::{Method, Request, header},
};
use gateway_admin::{
model::system::{
SystemOperationAccepted, SystemUpdateDetail, SystemUpdateStatus, SystemVersion,
},
ports::system::{SystemOperationError, SystemOperations, SystemUpdateEventStream},
};
use gateway_core::{
engine::execution::{ClientAuthenticationError, ClientKeyVerifier},
policy::ClientApiKeyId,
};
use serde_json::Value;
pub(crate) const RAW_KEY: &str = "cpr_live_candidate_must_not_leak";
pub(crate) async fn auth_app() -> (axum::Router, Arc<crate::admin::MemoryAuthStore>) {
let fixture = key_fixture().await;
(
crate::openai::api_router_with_admin(fixture.services),
fixture.auth,
)
}
pub(crate) async fn key_fixture() -> crate::admin::AdminTestFixture {
let fixture = crate::admin::AdminTestFixture::with_key_verifier(
Arc::new(AcceptingVerifier {
key_id: ClientApiKeyId::new("key-42").expect("key ID"),
}),
Arc::new(VersionSystem),
)
.await;
fixture.auth.enabled.store(true, Ordering::SeqCst);
fixture
}
pub(crate) fn json_request(method: Method, uri: &str, body: Value) -> Request<Body> {
let mut request = Request::builder()
.method(method)
.uri(uri)
.header(header::CONTENT_TYPE, "application/json")
.header(header::ORIGIN, "https://console.example.test")
.body(Body::from(body.to_string()))
.expect("client JSON request");
request.extensions_mut().insert(ConnectInfo(SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(203, 0, 113, 10)),
41_000,
)));
request
}
pub(crate) fn empty_request(method: Method, uri: &str) -> Request<Body> {
Request::builder()
.method(method)
.uri(uri)
.body(Body::empty())
.expect("client empty request")
}
pub(crate) fn cookie_request(method: Method, uri: &str, cookie: &str) -> Request<Body> {
Request::builder()
.method(method)
.uri(uri)
.header(header::COOKIE, cookie)
.body(Body::empty())
.expect("client cookie request")
}
pub(crate) async fn response_json(response: axum::response::Response) -> Value {
let body = to_bytes(response.into_body(), 64 * 1024)
.await
.expect("read client response body");
serde_json::from_slice(&body).expect("parse client response JSON")
}
struct VersionSystem;
#[async_trait]
impl SystemOperations for VersionSystem {
async fn version(&self) -> Result<SystemVersion, SystemOperationError> {
Ok(SystemVersion {
version: "3.7.0".to_owned(),
git_sha: "internal-revision".to_owned(),
build_time: "internal-build-time".to_owned(),
deployment_mode: "binary".to_owned(),
update_channel: "release".to_owned(),
latest_version: "3.8.0".to_owned(),
has_update: true,
update_cached: true,
update_warning: Some("internal-update-warning".to_owned()),
})
}
async fn update_detail(
&self,
_: bool,
_: Option<gateway_admin::model::system::SystemUpdateChannel>,
) -> Result<SystemUpdateDetail, SystemOperationError> {
unreachable!("client route must not request update details")
}
fn update_events(&self) -> SystemUpdateEventStream {
unreachable!("client route must not subscribe to updates")
}
async fn perform_update(
&self,
_: Option<String>,
_: Option<gateway_admin::model::system::SystemUpdateChannel>,
_: std::sync::Arc<dyn gateway_admin::ports::system::SystemUpdatePreflight>,
) -> Result<SystemOperationAccepted, SystemOperationError> {
unreachable!("client route must not perform updates")
}
async fn update_status(&self) -> Result<SystemUpdateStatus, SystemOperationError> {
unreachable!("client route must not request update status")
}
async fn rollback(
&self,
_: std::sync::Arc<dyn gateway_admin::ports::system::SystemUpdatePreflight>,
) -> Result<SystemOperationAccepted, SystemOperationError> {
unreachable!("client route must not roll back")
}
async fn restart(
&self,
_preflight: Arc<dyn gateway_admin::ports::system::SystemRestartPreflight>,
) -> Result<SystemOperationAccepted, SystemOperationError> {
unreachable!("client route must not restart")
}
}
struct AcceptingVerifier {
key_id: ClientApiKeyId,
}
impl ClientKeyVerifier for AcceptingVerifier {
fn verify_client_key(
&self,
candidate: &str,
) -> Result<ClientApiKeyId, ClientAuthenticationError> {
if candidate == RAW_KEY {
Ok(self.key_id.clone())
} else {
Err(ClientAuthenticationError::InvalidKey)
}
}
}
#[derive(Default)]
pub(crate) struct RecordingDiagnostics(
pub std::sync::Mutex<Vec<gateway_core::diagnostics::OperationalFailure>>,
);
#[async_trait]
impl gateway_core::diagnostics::OperationalDiagnostics for RecordingDiagnostics {
async fn record_failure(
&self,
failure: gateway_core::diagnostics::OperationalFailure,
) -> Result<(), gateway_core::error::StoreError> {
self.0.lock().unwrap().push(failure);
Ok(())
}
}