fix: restore security hardening compatibility and validation

Restore authorized rule reveal, explicit full HTTP capture and retention, video task business fields, and valid payment URLs. Add opt-in credential preservation for trusted recovery, fix frontend type contracts and async races, and eliminate PostgreSQL test fixture resource leaks. Document audit coverage and successful fmt and CI-scoped Clippy checks.
This commit is contained in:
elky
2026-09-07 21:14:27 +08:00
parent a5c3699ae9
commit a90d564931
191 changed files with 6785 additions and 1643 deletions
+17 -4
View File
@@ -326,7 +326,7 @@ async fn gateway_reads_video_task_detail_via_internal_async_task_endpoint() {
}
#[tokio::test]
async fn gateway_does_not_redirect_sanitized_openai_video_url_from_internal_endpoint() {
async fn gateway_redirects_persisted_openai_video_url_from_authenticated_internal_endpoint() {
let repository = Arc::new(InMemoryVideoTaskRepository::default());
let mut task = sample_video_task(
"task-redirect",
@@ -341,7 +341,10 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_from_internal_endp
.upsert(task)
.await
.expect("upsert should succeed");
assert_eq!(stored.video_url, None);
assert_eq!(
stored.video_url.as_deref(),
Some("https://8.8.8.8/video-task-redirect.mp4")
);
let state = AppState::new()
.expect("gateway state should build")
@@ -349,7 +352,10 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_from_internal_endp
let (gateway_url, gateway_handle, access_token) =
start_authenticated_operational_server(state).await;
let client = authenticated_operational_client(&access_token);
let client = super::authenticated_operational_client_with_builder(
reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()),
&access_token,
);
let response = client
.get(format!(
"{gateway_url}/_gateway/async-tasks/video-tasks/task-redirect/video"
@@ -358,7 +364,14 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_from_internal_endp
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::NOT_FOUND);
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
assert_eq!(
response
.headers()
.get("location")
.and_then(|value| value.to_str().ok()),
stored.video_url.as_deref()
);
gateway_handle.abort();
}
@@ -1,3 +1,4 @@
mod keys;
mod quota;
mod routes;
mod rules_reveal;
@@ -0,0 +1,151 @@
use std::sync::Arc;
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
use axum::body::Body;
use http::{HeaderMap, HeaderValue, Method, Request, StatusCode};
use http_body_util::BodyExt;
use serde_json::{json, Value};
use super::super::super::{build_router_with_state, sample_endpoint, sample_provider, AppState};
use crate::admin_api::{maybe_build_local_admin_response, AdminRouteRequest};
use crate::audit::AdminAuditEvent;
use crate::constants::{
GATEWAY_HEADER, TRUSTED_ADMIN_SESSION_ID_HEADER, TRUSTED_ADMIN_USER_ID_HEADER,
TRUSTED_ADMIN_USER_ROLE_HEADER,
};
use crate::control::resolve_public_request_context;
use crate::data::GatewayDataState;
use crate::tests::send_request;
fn seeded_state() -> AppState {
let mut endpoint = sample_endpoint(
"endpoint-rules",
"provider-rules",
"openai:chat",
"https://example.test",
);
endpoint.header_rules =
Some(json!([{"action": "set", "key": "x-auth", "value": "request-secret"}]));
endpoint.body_rules =
Some(json!([{"action": "set", "path": "auth.token", "value": "body-secret"}]));
endpoint.config = Some(json!({
"private_token": "unrelated-secret",
"response_header_rules": [{"action": "set", "key": "x-auth", "value": "response-secret"}]
}));
let repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![sample_provider("provider-rules", "custom", 10)],
vec![endpoint],
vec![],
));
AppState::new().unwrap().with_data_state_for_tests(
GatewayDataState::with_provider_catalog_reader_for_tests(repository),
)
}
fn admin_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
for (name, value) in [
(GATEWAY_HEADER, "rust-phase3b"),
(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user"),
(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin"),
(TRUSTED_ADMIN_SESSION_ID_HEADER, "admin-session"),
] {
headers.insert(name, HeaderValue::from_static(value));
}
headers
}
#[tokio::test]
async fn endpoint_rules_reveal_is_scoped_audited_and_not_cached() {
let state = seeded_state();
let context = resolve_public_request_context(
&state,
&Method::GET,
&"/api/admin/endpoints/endpoint-rules/rules/reveal"
.parse()
.unwrap(),
&admin_headers(),
"reveal-test",
)
.await
.unwrap();
let response = maybe_build_local_admin_response(AdminRouteRequest::new(
&state,
&context,
&"127.0.0.1:12345".parse().unwrap(),
&admin_headers(),
None,
))
.await
.unwrap()
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.headers()[http::header::CACHE_CONTROL], "no-store");
assert_eq!(response.headers()[http::header::PRAGMA], "no-cache");
let audit = response.extensions().get::<AdminAuditEvent>().unwrap();
assert_eq!(audit.event_name, "admin_endpoint_rules_revealed");
assert_eq!(audit.action, "reveal_endpoint_rules");
assert_eq!(audit.target_id, "endpoint-rules");
let body = response.into_body().collect().await.unwrap().to_bytes();
let payload: Value = serde_json::from_slice(&body).unwrap();
assert_eq!(payload["header_rules"][0]["value"], "request-secret");
assert_eq!(payload["body_rules"][0]["value"], "body-secret");
assert_eq!(
payload["response_header_rules"][0]["value"],
"response-secret"
);
assert_eq!(payload.as_object().unwrap().len(), 3);
assert!(!payload.to_string().contains("unrelated-secret"));
}
#[tokio::test]
async fn endpoint_rules_reveal_denies_anonymous_and_non_admin_requests() {
let router = build_router_with_state(seeded_state());
for role in [None, Some("user")] {
let mut request =
Request::builder().uri("/api/admin/endpoints/endpoint-rules/rules/reveal");
if let Some(role) = role {
request = request
.header(GATEWAY_HEADER, "rust-phase3b")
.header(TRUSTED_ADMIN_USER_ID_HEADER, "normal-user")
.header(TRUSTED_ADMIN_USER_ROLE_HEADER, role)
.header(TRUSTED_ADMIN_SESSION_ID_HEADER, "user-session");
}
let response = send_request(router.clone(), request.body(Body::empty()).unwrap()).await;
assert!(matches!(
response.status(),
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN
));
let body = response.into_body().collect().await.unwrap().to_bytes();
assert!(!String::from_utf8_lossy(&body).contains("request-secret"));
}
}
#[tokio::test]
async fn endpoint_rules_reveal_returns_not_found_and_data_unavailable_without_fallback() {
for (state, expected) in [
(seeded_state(), StatusCode::NOT_FOUND),
(AppState::new().unwrap(), StatusCode::SERVICE_UNAVAILABLE),
] {
let context = resolve_public_request_context(
&state,
&Method::GET,
&"/api/admin/endpoints/missing/rules/reveal".parse().unwrap(),
&admin_headers(),
"reveal-missing-test",
)
.await
.unwrap();
let response = maybe_build_local_admin_response(AdminRouteRequest::new(
&state,
&context,
&"127.0.0.1:12345".parse().unwrap(),
&admin_headers(),
None,
))
.await
.unwrap()
.unwrap();
assert_eq!(response.status(), expected);
}
}
@@ -221,13 +221,17 @@ async fn gateway_handles_admin_video_tasks_list_locally_with_trusted_admin_princ
assert_eq!(payload["pages"], json!(1));
assert_eq!(payload["items"].as_array().map(Vec::len), Some(1));
assert_eq!(payload["items"][0]["id"], "task-completed");
// Video-task persistence intentionally drops user-facing PII. The admin
// projection must therefore use the privacy-safe fallback when no separate
// user snapshot is joined.
assert_eq!(payload["items"][0]["username"], "Unknown");
assert_eq!(payload["items"][0]["username"], "alice");
assert_eq!(payload["items"][0]["provider_name"], "OpenAI");
assert_eq!(payload["items"][0]["status"], "completed");
assert!(payload["items"][0]["prompt"].is_null());
assert_eq!(
payload["items"][0]["prompt"],
format!("{}...", "x".repeat(100))
);
assert_eq!(
payload["items"][0]["video_url"],
"https://8.8.8.8/task-completed.mp4"
);
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();
@@ -393,7 +397,9 @@ async fn gateway_handles_admin_video_task_detail_locally_with_trusted_admin_prin
assert_eq!(response.status(), StatusCode::OK);
let payload: serde_json::Value = response.json().await.expect("json body should parse");
assert_eq!(payload["id"], "task-detail");
assert_eq!(payload["username"], "Unknown");
assert_eq!(payload["prompt"], "detail prompt");
assert_eq!(payload["video_url"], "https://8.8.8.8/task-detail.mp4");
assert_eq!(payload["username"], "charlie");
assert_eq!(payload["provider_name"], "OpenAI");
assert_eq!(payload["endpoint"]["id"], "endpoint-1");
assert_eq!(payload["endpoint"]["api_format"], "openai:video");
@@ -734,7 +740,7 @@ async fn local_admin_video_task_cancel_attaches_explicit_audit() {
}
#[tokio::test]
async fn gateway_does_not_redirect_sanitized_openai_video_url_or_forward_upstream() {
async fn gateway_redirects_persisted_openai_video_url_without_forwarding_admin_request() {
let upstream_hits = Arc::new(Mutex::new(0usize));
let upstream_hits_clone = Arc::clone(&upstream_hits);
let upstream = Router::new().route(
@@ -762,7 +768,10 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_or_forward_upstrea
))
.await
.expect("task should upsert");
assert_eq!(stored.video_url, None);
assert_eq!(
stored.video_url.as_deref(),
Some("https://8.8.8.8/task-redirect.mp4")
);
let (_upstream_url, upstream_handle) = start_server(upstream).await;
let gateway = build_router_with_state(
@@ -788,7 +797,14 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_or_forward_upstrea
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::NOT_FOUND);
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
assert_eq!(
response
.headers()
.get(http::header::LOCATION)
.and_then(|value| value.to_str().ok()),
stored.video_url.as_deref()
);
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();
@@ -796,22 +812,22 @@ async fn gateway_does_not_redirect_sanitized_openai_video_url_or_forward_upstrea
}
#[tokio::test]
async fn local_admin_video_task_video_is_unavailable_after_openai_url_sanitization() {
async fn local_admin_video_task_download_preserves_signed_url_and_attaches_audit() {
let repository = Arc::new(InMemoryVideoTaskRepository::default());
let stored = repository
.upsert(sample_admin_video_task(
"task-video-audit",
VideoTaskStatus::Completed,
1_710_000_550,
"user-5",
"frank",
"provider-openai",
"gpt-video",
"video audit prompt",
))
.await
.expect("task should upsert");
assert_eq!(stored.video_url, None);
let mut task = sample_admin_video_task(
"task-video-audit",
VideoTaskStatus::Completed,
1_710_000_550,
"user-5",
"frank",
"provider-openai",
"gpt-video",
"video audit prompt",
);
task.video_url =
Some("https://8.8.8.8/video.mp4?signature=a%2Fb%2Bc%3D&part=2&part=1".to_string());
let stored = repository.upsert(task).await.expect("task should upsert");
assert_eq!(stored.prompt.as_deref(), Some("video audit prompt"));
let state = AppState::new()
.expect("gateway state should build")
@@ -825,8 +841,15 @@ async fn local_admin_video_task_video_is_unavailable_after_openai_url_sanitizati
)
.await;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
assert!(response.extensions().get::<AdminAuditEvent>().is_none());
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
assert_eq!(
response
.headers()
.get(http::header::LOCATION)
.and_then(|value| value.to_str().ok()),
stored.video_url.as_deref()
);
assert!(response.extensions().get::<AdminAuditEvent>().is_some());
}
#[tokio::test]
+240 -42
View File
@@ -12,6 +12,7 @@ use super::{
UsageReadRepository, UsageRuntimeConfig, DEVELOPMENT_ENCRYPTION_KEY, TRACE_ID_HEADER,
};
use crate::constants::LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER;
use aether_data_contracts::repository::usage::UsageBodyCaptureState;
fn deep_nested_metadata(levels: usize) -> serde_json::Value {
let mut current = json!({"leaf": "value"});
@@ -84,6 +85,58 @@ where
stored.expect("usage should be present once the expected status is observed")
}
async fn load_admin_usage_capture_detail(
state: &crate::AppState,
usage_id: &str,
include_bodies: bool,
) -> serde_json::Value {
use crate::admin_api::{maybe_build_local_admin_response, AdminRouteRequest};
use crate::constants::{
GATEWAY_HEADER, TRUSTED_ADMIN_SESSION_ID_HEADER, TRUSTED_ADMIN_USER_ID_HEADER,
TRUSTED_ADMIN_USER_ROLE_HEADER,
};
use crate::control::resolve_public_request_context;
use http_body_util::BodyExt;
let mut headers = http::HeaderMap::new();
for (name, value) in [
(GATEWAY_HEADER, "rust-phase3b"),
(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user"),
(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin"),
(TRUSTED_ADMIN_SESSION_ID_HEADER, "admin-session"),
] {
headers.insert(name, HeaderValue::from_static(value));
}
let uri = format!("/api/admin/usage/{usage_id}?include_bodies={include_bodies}")
.parse()
.unwrap();
let context = resolve_public_request_context(
state,
&http::Method::GET,
&uri,
&headers,
"usage-full-detail",
)
.await
.unwrap();
let response = maybe_build_local_admin_response(AdminRouteRequest::new(
state,
&context,
&"127.0.0.1:12345".parse().unwrap(),
&headers,
None,
))
.await
.unwrap()
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(response
.extensions()
.get::<crate::audit::AdminAuditEvent>()
.is_some());
serde_json::from_slice(&response.into_body().collect().await.unwrap().to_bytes()).unwrap()
}
#[test]
fn gateway_handles_local_openai_chat_sync_report_with_local_reporting_when_usage_runtime_enabled() {
run_async_test_on_large_stack(
@@ -348,7 +401,7 @@ async fn gateway_truncates_deep_request_echo_for_local_openai_chat_sync_usage_im
Arc::clone(&request_candidate_repository),
Arc::clone(&usage_repository),
DEVELOPMENT_ENCRYPTION_KEY,
),
).with_system_config_values_for_tests([("request_record_level".to_string(), json!("full"))]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
@@ -402,10 +455,28 @@ async fn gateway_truncates_deep_request_echo_for_local_openai_chat_sync_usage_im
let stored_usage = stored_usage.expect("usage should be recorded");
assert_eq!(stored_usage.status, "completed");
assert_eq!(stored_usage.total_tokens, 5);
assert!(stored_usage.request_body.is_none());
let request_body = stored_usage.request_body.as_ref().unwrap();
assert_eq!(
request_body["messages"][0]["content"]
.as_str()
.unwrap()
.len(),
128 * 1024
);
assert!(
request_body["metadata"]["child"]["child"]["child"]["child"]["child"]
.get("depth")
.is_some()
);
assert!(stored_usage.request_body_ref.is_none());
assert!(stored_usage.request_body_state.is_none());
assert!(stored_usage.request_headers.is_none());
assert_eq!(
stored_usage.request_body_state,
Some(UsageBodyCaptureState::Inline)
);
assert_eq!(
stored_usage.request_headers.as_ref().unwrap()["authorization"],
"[redacted]"
);
gateway_handle.abort();
execution_runtime_handle.abort();
@@ -489,10 +560,10 @@ async fn gateway_ignores_legacy_max_request_body_size_for_local_openai_chat_sync
Arc::clone(&usage_repository),
DEVELOPMENT_ENCRYPTION_KEY,
)
.with_system_config_values_for_tests([(
"max_request_body_size".to_string(),
json!(128),
)]),
.with_system_config_values_for_tests([
("max_request_body_size".to_string(), json!(128)),
("request_record_level".to_string(), json!("full")),
]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
@@ -535,12 +606,30 @@ async fn gateway_ignores_legacy_max_request_body_size_for_local_openai_chat_sync
)
.await;
assert_eq!(stored_usage.total_tokens, 5);
assert!(stored_usage.request_body.is_none());
assert!(
stored_usage.request_body.as_ref().unwrap()["messages"][0]["content"]
.as_str()
.unwrap()
.len()
> 128
);
assert!(stored_usage.request_body_ref.is_none());
assert!(stored_usage.request_body_state.is_none());
assert!(stored_usage.provider_request_body.is_none());
assert_eq!(
stored_usage.request_body_state,
Some(UsageBodyCaptureState::Inline)
);
assert!(
stored_usage.provider_request_body.as_ref().unwrap()["messages"][0]["content"]
.as_str()
.unwrap()
.len()
> 128
);
assert!(stored_usage.provider_request_body_ref.is_none());
assert!(stored_usage.provider_request_body_state.is_none());
assert_eq!(
stored_usage.provider_request_body_state,
Some(UsageBodyCaptureState::Inline)
);
gateway_handle.abort();
execution_runtime_handle.abort();
@@ -551,11 +640,19 @@ async fn gateway_ignores_legacy_max_request_body_size_for_local_openai_chat_sync
fn gateway_strips_request_and_response_bodies_when_request_record_level_is_base() {
run_async_test_on_large_stack(
"gateway_strips_request_and_response_bodies_when_request_record_level_is_base",
gateway_strips_request_and_response_bodies_when_request_record_level_is_base_impl(),
gateway_honors_request_record_level_impl("base"),
);
}
async fn gateway_strips_request_and_response_bodies_when_request_record_level_is_base_impl() {
#[test]
fn gateway_full_request_record_level_preserves_sync_bodies_in_admin_detail() {
run_async_test_on_large_stack(
"gateway_full_request_record_level_preserves_sync_bodies_in_admin_detail",
gateway_honors_request_record_level_impl("full"),
);
}
async fn gateway_honors_request_record_level_impl(record_level: &str) {
let usage_repository = Arc::new(InMemoryUsageReadRepository::default());
let request_candidate_repository = Arc::new(InMemoryRequestCandidateRepository::default());
@@ -630,14 +727,14 @@ async fn gateway_strips_request_and_response_bodies_when_request_record_level_is
)
.with_system_config_values_for_tests([(
"request_record_level".to_string(),
json!("base"),
json!(record_level),
)]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
..UsageRuntimeConfig::default()
});
let gateway = build_router_with_state(gateway_state);
let gateway = build_router_with_state(gateway_state.clone());
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
@@ -678,14 +775,39 @@ async fn gateway_strips_request_and_response_bodies_when_request_record_level_is
assert_eq!(stored_usage.status, "completed");
assert_eq!(stored_usage.total_tokens, 5);
assert_eq!(stored_usage.response_time_ms, Some(25));
assert!(stored_usage.request_body.is_none());
assert!(stored_usage.request_body_ref.is_none());
assert!(stored_usage.provider_request_body.is_none());
assert!(stored_usage.provider_request_body_ref.is_none());
assert!(stored_usage.response_body.is_none());
assert!(stored_usage.response_body_ref.is_none());
assert!(stored_usage.client_response_body.is_none());
assert!(stored_usage.client_response_body_ref.is_none());
let detail = load_admin_usage_capture_detail(&gateway_state, &stored_usage.id, true).await;
let shallow = load_admin_usage_capture_detail(&gateway_state, &stored_usage.id, false).await;
for field in [
"request_body",
"provider_request_body",
"response_body",
"client_response_body",
] {
assert!(shallow[field].is_null());
let expected_captured = record_level == "full" && field != "client_response_body";
assert_eq!(
shallow[format!("has_{field}")],
expected_captured,
"availability for {field}"
);
if expected_captured {
assert!(!detail[field].is_null(), "full should expose {field}");
} else {
assert!(
detail[field].is_null(),
"uncaptured {field} must remain absent"
);
}
}
if record_level == "full" {
assert_eq!(
detail["request_body"]["messages"][0]["content"],
"request body should not be persisted"
);
assert_eq!(detail["provider_request_body"]["model"], "gpt-5-upstream");
assert_eq!(detail["response_body"], body_json);
assert!(detail["client_response_body"].is_null());
}
let stored_candidates = request_candidate_repository
.list_by_request_id("trace-openai-chat-local-report-sync-base-123")
@@ -825,10 +947,16 @@ async fn gateway_records_failed_usage_when_all_local_openai_chat_candidates_exha
);
assert!(stored_usage.response_body.is_none());
assert!(stored_usage.response_body_ref.is_none());
assert!(stored_usage.response_body_state.is_none());
assert_eq!(
stored_usage.response_body_state,
Some(UsageBodyCaptureState::Disabled)
);
assert!(stored_usage.client_response_body.is_none());
assert!(stored_usage.client_response_body_ref.is_none());
assert!(stored_usage.client_response_body_state.is_none());
assert_eq!(
stored_usage.client_response_body_state,
Some(UsageBodyCaptureState::Disabled)
);
let stored_candidates = request_candidate_repository
.list_by_request_id("trace-openai-chat-local-report-sync-failure-123")
@@ -930,7 +1058,10 @@ async fn gateway_records_failed_usage_when_sync_runtime_transport_is_unavailable
assert_eq!(stored_usage.status_code, Some(503));
assert!(stored_usage.response_body.is_none());
assert!(stored_usage.response_body_ref.is_none());
assert!(stored_usage.response_body_state.is_none());
assert_eq!(
stored_usage.response_body_state,
Some(UsageBodyCaptureState::Disabled)
);
let stored_candidates = request_candidate_repository
.list_by_request_id("trace-openai-chat-local-transport-unavailable-123")
@@ -1272,7 +1403,10 @@ async fn gateway_records_failed_usage_for_claude_runtime_miss_without_execution_
);
assert!(stored_usage.client_response_body.is_none());
assert!(stored_usage.client_response_body_ref.is_none());
assert!(stored_usage.client_response_body_state.is_none());
assert_eq!(
stored_usage.client_response_body_state,
Some(UsageBodyCaptureState::Disabled)
);
assert!(stored_usage.error_message.is_none());
let stored_candidates = request_candidate_repository
@@ -1296,11 +1430,20 @@ fn gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usa
{
run_async_test_on_large_stack(
"gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usage_runtime_enabled",
gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usage_runtime_enabled_impl(),
gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usage_runtime_enabled_impl("basic"),
);
}
#[test]
fn gateway_full_request_record_level_preserves_stream_bodies_in_admin_detail() {
run_async_test_on_large_stack(
"gateway_full_request_record_level_preserves_stream_bodies_in_admin_detail",
gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usage_runtime_enabled_impl("full"),
);
}
async fn gateway_handles_local_openai_chat_stream_report_with_local_reporting_when_usage_runtime_enabled_impl(
record_level: &str,
) {
let usage_repository = Arc::new(InMemoryUsageReadRepository::default());
let request_candidate_repository = Arc::new(InMemoryRequestCandidateRepository::default());
@@ -1406,13 +1549,13 @@ async fn gateway_handles_local_openai_chat_stream_report_with_local_reporting_wh
Arc::clone(&request_candidate_repository),
Arc::clone(&usage_repository),
DEVELOPMENT_ENCRYPTION_KEY,
),
).with_system_config_values_for_tests([("request_record_level".to_string(), json!(record_level))]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
..UsageRuntimeConfig::default()
});
let gateway = build_router_with_state(gateway_state);
let gateway = build_router_with_state(gateway_state.clone());
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = reqwest::Client::new()
@@ -1448,6 +1591,30 @@ async fn gateway_handles_local_openai_chat_stream_report_with_local_reporting_wh
assert!(stored_usage.response_time_ms >= stored_usage.first_byte_time_ms);
assert!(stored_usage.is_stream);
let detail = load_admin_usage_capture_detail(&gateway_state, &stored_usage.id, true).await;
for field in [
"request_body",
"provider_request_body",
"response_body",
"client_response_body",
] {
if record_level == "full" {
assert!(
!detail[field].is_null(),
"full stream should expose {field}"
);
} else {
assert!(
detail[field].is_null(),
"basic stream must not persist {field}"
);
}
}
if record_level == "full" {
assert!(detail["response_body"].to_string().contains("hello"));
assert!(detail["client_response_body"].to_string().contains("hello"));
}
let stored_candidates = request_candidate_repository
.list_by_request_id("trace-openai-chat-local-report-stream-123")
.await
@@ -1585,10 +1752,10 @@ async fn gateway_ignores_legacy_max_response_body_size_for_stream_usage_impl() {
Arc::clone(&usage_repository),
DEVELOPMENT_ENCRYPTION_KEY,
)
.with_system_config_values_for_tests([(
"max_response_body_size".to_string(),
json!(128),
)]),
.with_system_config_values_for_tests([
("max_response_body_size".to_string(), json!(128)),
("request_record_level".to_string(), json!("full")),
]),
)
.with_usage_runtime_for_tests(UsageRuntimeConfig {
enabled: true,
@@ -1624,12 +1791,34 @@ async fn gateway_ignores_legacy_max_response_body_size_for_stream_usage_impl() {
)
.await;
assert_eq!(stored_usage.total_tokens, 6);
assert!(stored_usage.response_body.is_none());
assert!(
stored_usage
.response_body
.as_ref()
.unwrap()
.to_string()
.len()
> 128
);
assert!(stored_usage.response_body_ref.is_none());
assert!(stored_usage.response_body_state.is_none());
assert!(stored_usage.client_response_body.is_none());
assert_eq!(
stored_usage.response_body_state,
Some(UsageBodyCaptureState::Inline)
);
assert!(
stored_usage
.client_response_body
.as_ref()
.unwrap()
.to_string()
.len()
> 128
);
assert!(stored_usage.client_response_body_ref.is_none());
assert!(stored_usage.client_response_body_state.is_none());
assert_eq!(
stored_usage.client_response_body_state,
Some(UsageBodyCaptureState::Inline)
);
gateway_handle.abort();
execution_runtime_handle.abort();
@@ -1903,10 +2092,16 @@ async fn gateway_records_failed_usage_when_all_local_claude_cli_candidates_are_s
Some("all_candidates_skipped")
);
assert!(stored_usage.error_message.is_none());
assert!(stored_usage.request_headers.is_none());
assert_eq!(
stored_usage.request_headers.as_ref().unwrap()["authorization"],
"[redacted]"
);
assert!(stored_usage.request_body.is_none());
assert!(stored_usage.request_body_ref.is_none());
assert!(stored_usage.request_body_state.is_none());
assert_eq!(
stored_usage.request_body_state,
Some(UsageBodyCaptureState::Disabled)
);
assert!(stored_usage.provider_request_body.is_none());
assert_eq!(
stored_usage
@@ -2169,7 +2364,10 @@ fn gateway_keeps_failed_usage_request_capture_lightweight_for_large_local_claude
)
.await;
assert_eq!(stored_usage.status, "failed");
assert!(stored_usage.request_body_state.is_none());
assert_eq!(
stored_usage.request_body_state,
Some(UsageBodyCaptureState::Disabled)
);
assert!(stored_usage.request_body.is_none());
assert!(stored_usage.request_body_ref.is_none());
assert!(stored_usage.provider_request_body.is_none());
@@ -213,6 +213,13 @@ async fn gateway_background_video_task_poller_refreshes_due_openai_task_from_rep
};
assert_eq!(stored.status, VideoTaskStatus::Processing);
assert_eq!(stored.prompt.as_deref(), Some("hello"));
assert_eq!(stored.username.as_deref(), Some("video-user"));
assert_eq!(stored.api_key_name.as_deref(), Some("video-key"));
assert_eq!(stored.duration_seconds, Some(4));
assert_eq!(stored.resolution.as_deref(), Some("720p"));
assert_eq!(stored.aspect_ratio.as_deref(), Some("16:9"));
assert_eq!(stored.size.as_deref(), Some("1280x720"));
assert_eq!(stored.progress_percent, 37);
assert_eq!(stored.poll_count, 1);
assert!(
@@ -32,6 +32,7 @@ async fn gateway_executes_openai_video_content_from_reconstructed_data_task_with
struct SeenExecutionRuntimeStreamRequest {
method: String,
url: String,
headers: serde_json::Value,
}
fn hash_api_key(value: &str) -> String {
@@ -159,6 +160,7 @@ async fn gateway_executes_openai_video_content_from_reconstructed_data_task_with
.and_then(|value| value.as_str())
.unwrap_or_default()
.to_string(),
headers: payload.get("headers").cloned().unwrap_or_else(|| json!({})),
});
let frames = [
@@ -252,7 +254,10 @@ async fn gateway_executes_openai_video_content_from_reconstructed_data_task_with
updated_at_unix_secs: 456,
error_code: None,
error_message: None,
video_url: Some("https://cdn.example.com/video-content.mp4".to_string()),
video_url: Some(
"https://cdn.example.com/video-content.mp4?signature=a%2Fb%2Bc%3D&part=2&part=1"
.to_string(),
),
request_metadata: None,
})
.await
@@ -358,8 +363,9 @@ async fn gateway_executes_openai_video_content_from_reconstructed_data_task_with
assert_eq!(seen_stream_request.method, "GET");
assert_eq!(
seen_stream_request.url,
"https://api.openai.example/v1/videos/ext-video-content-followup-123/content"
"https://cdn.example.com/video-content.mp4?signature=a%2Fb%2Bc%3D&part=2&part=1"
);
assert!(seen_stream_request.headers.get("authorization").is_none());
assert_eq!(*decision_stream_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*execute_stream_hits.lock().expect("mutex should lock"), 0);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);