Files
codex/codex-rs/http-client/src/transport_tests.rs
T
Michael Bolin adb143a291 Add a policy-aware HTTP client builder (#34630)
## What changed

- Add `HttpClientBuilder` for configuring default headers, redirects, the
  Cloudflare cookie store, and request diagnostics without exposing the
  underlying transport.
- Provide factory-backed construction for fixed destinations that respects
  outbound proxy policy, alongside explicit direct and legacy
  transport-default construction paths.
- Preserve the request-logging setting in `ReqwestTransport`, so disabling
  diagnostics also suppresses transport-level URL and request-body traces.
- Add `HEAD`, `DELETE`, and query-parameter helpers to the shared client
  wrappers.

## Testing

- Verify builder configuration survives policy-aware construction and custom
  CA fallback.
- Verify disabled request logging omits request URLs and bodies from transport
  traces.

GitOrigin-RevId: 529c33abefeb88f38ff9a0ead374d29fcc872e6a
2026-07-21 22:40:12 +00:00

93 lines
2.9 KiB
Rust

use super::*;
use serde_json::json;
use std::io::Write;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
#[tokio::test]
async fn enabled_request_logging_emits_transport_url_and_body() {
let logs = capture_transport_logs(HttpClient::new(test_reqwest_client())).await;
assert!(logs.contains("log capture sentinel"));
assert!(logs.contains("url-secret"));
assert!(logs.contains("body-secret"));
}
#[tokio::test]
async fn disabled_request_logging_suppresses_transport_url_and_body() {
let logs = capture_transport_logs(HttpClient::new_without_request_logging(
test_reqwest_client(),
))
.await;
assert!(logs.contains("log capture sentinel"));
assert!(!logs.contains("url-secret"));
assert!(!logs.contains("body-secret"));
}
fn test_reqwest_client() -> reqwest::Client {
reqwest::Client::builder()
.no_proxy()
.build()
.expect("HTTP client should build")
}
async fn capture_transport_logs(client: HttpClient) -> String {
let unavailable_server =
std::net::TcpListener::bind(("127.0.0.1", 0)).expect("server port should bind");
let server_addr = unavailable_server
.local_addr()
.expect("server listener should have an address");
drop(unavailable_server);
let transport = ReqwestTransport::from_http_client(client);
let log_buffer = Arc::new(Mutex::new(Vec::new()));
let writer_buffer = Arc::clone(&log_buffer);
let subscriber = tracing_subscriber::registry().with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(move || TestLogWriter(Arc::clone(&writer_buffer)))
.with_filter(
tracing_subscriber::filter::Targets::new()
.with_target("codex_http_client::transport", tracing::Level::TRACE),
),
);
let _guard = tracing::subscriber::set_default(subscriber);
tracing::trace!(target: "codex_http_client::transport", "log capture sentinel");
let mut request = Request::new(
Method::POST,
format!("http://{server_addr}/request?token=url-secret"),
)
.with_json(&json!({"token": "body-secret"}));
request.timeout = Some(Duration::from_secs(1));
let _ = transport.execute(request).await;
String::from_utf8(
log_buffer
.lock()
.expect("log buffer should not be poisoned")
.clone(),
)
.expect("captured logs should be UTF-8")
}
#[derive(Clone)]
struct TestLogWriter(Arc<Mutex<Vec<u8>>>);
impl Write for TestLogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0
.lock()
.map_err(|_| std::io::Error::other("log buffer should not be poisoned"))?
.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}