From 77ce0465ea95b6e24ebe56ee3a7c81326d69d9a9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 7 Nov 2026 09:37:17 +0100 Subject: [PATCH] stream: the proxy-environment test is an integration target, so the lib carries no environment read Moves the_stream_agent_ignores_the_proxy_environment to crates/mind-body/tests/stream_proxy.rs, as control.rs carries its env-touching tests. It asserts through the public surface: with a proxy in the child's environment the stream's agent reaches its endpoint directly and follows no redirect, and a plain ureq agent in the same environment takes the proxy. No behaviour change. Co-Authored-By: Claude Sonnet 5.5 --- crates/mind-body/src/stream/tests.rs | 21 ------- crates/mind-body/tests/stream_proxy.rs | 87 ++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 crates/mind-body/tests/stream_proxy.rs diff --git a/crates/mind-body/src/stream/tests.rs b/crates/mind-body/src/stream/tests.rs index ab7e1c6..36797cf 100644 --- a/crates/mind-body/src/stream/tests.rs +++ b/crates/mind-body/src/stream/tests.rs @@ -840,24 +840,3 @@ fn identical_memory_lines_are_each_cited_once() { let cited = seed.reads.iter().filter(|read| read.kind == PipelineKind::Memory).count(); assert!(shown >= 1 && cited == shown, "{shown} lines shown, {cited} cited"); } - -/// The environment's proxy settings are read when an agent is built, so the check runs in a child process that has them. -#[test] -fn the_stream_agent_ignores_the_proxy_environment() { - if std::env::var("STREAM_PROXY_CHILD").is_ok() { - let plain: ureq::Agent = ureq::Agent::config_builder().build().into(); - assert!(plain.config().proxy().is_some(), "the child's environment names a proxy"); - let stream = OllamaStream::open("http://127.0.0.1:9", "p", "b").unwrap(); - assert!(stream.agent.config().proxy().is_none(), "the stream's agent took the environment's proxy"); - assert_eq!(stream.agent.config().max_redirects(), 0); - return; - } - let child = std::process::Command::new(std::env::current_exe().unwrap()) - .args(["--exact", "stream::tests::the_stream_agent_ignores_the_proxy_environment", "--nocapture"]) - .env("STREAM_PROXY_CHILD", "1") - .env("ALL_PROXY", "http://192.0.2.1:3128") - .env("HTTP_PROXY", "http://192.0.2.1:3128") - .output() - .unwrap(); - assert!(child.status.success(), "{}{}", String::from_utf8_lossy(&child.stdout), String::from_utf8_lossy(&child.stderr)); -} diff --git a/crates/mind-body/tests/stream_proxy.rs b/crates/mind-body/tests/stream_proxy.rs new file mode 100644 index 0000000..088c9c7 --- /dev/null +++ b/crates/mind-body/tests/stream_proxy.rs @@ -0,0 +1,87 @@ +//! The stream's HTTP agent ignores the process environment's proxy settings and follows no redirects. +//! +//! An agent reads `ALL_PROXY` and friends when it is built, so the check runs in a child process +//! that has them. The parent hosts three loopback listeners: the model host (it answers every +//! request with a redirect), the proxy the child's environment names, and the redirect's target. + +// The suite re-executes its own test binary as the child and hands it the endpoint through the +// environment. That is test plumbing in its own crate: it reaches no library code, which +// `#![forbid]`s these readers. +#![allow(clippy::disallowed_methods, reason = "the test re-executes itself and hands the child its role through the environment")] + +use std::io::{Read, Write}; +use std::net::TcpListener; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use mind_body::stream::{OllamaStream, StreamModel}; + +/// Counts the connections a loopback listener accepts, answering each with `reply` when given. +fn listen(reply: Option) -> (String, Arc) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let address = listener.local_addr().unwrap(); + let hits = Arc::new(AtomicUsize::new(0)); + let counted = Arc::clone(&hits); + std::thread::spawn(move || { + for connection in listener.incoming().flatten() { + let mut connection = connection; + counted.fetch_add(1, Ordering::SeqCst); + if let Some(reply) = &reply { + let mut seen = [0u8; 4096]; + let _ = connection.read(&mut seen); + let _ = connection.write_all(reply.as_bytes()); + } + } + }); + (format!("http://{address}"), hits) +} + +fn settled(hits: &AtomicUsize, wanted: bool) -> bool { + for _ in 0..40 { + if (hits.load(Ordering::SeqCst) > 0) == wanted { + return true; + } + std::thread::sleep(Duration::from_millis(50)); + } + false +} + +fn child(role: &str, endpoint: &str, proxy: &str) { + let status = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["--exact", "the_stream_agent_ignores_the_proxy_environment", "--nocapture"]) + .env("STREAM_PROXY_CHILD", role) + .env("STREAM_PROXY_ENDPOINT", endpoint) + .env("ALL_PROXY", proxy) + .env("HTTP_PROXY", proxy) + .status() + .unwrap(); + assert!(status.success(), "the {role} child failed"); +} + +#[test] +fn the_stream_agent_ignores_the_proxy_environment() { + if let Ok(role) = std::env::var("STREAM_PROXY_CHILD") { + let endpoint = std::env::var("STREAM_PROXY_ENDPOINT").unwrap(); + if role == "plain" { + let plain: ureq::Agent = ureq::Agent::config_builder().timeout_global(Some(Duration::from_secs(2))).build().into(); + let _ = plain.get(&endpoint).call(); + } else { + let _ = OllamaStream::open(&endpoint, "p", "b").unwrap().project("hello"); + } + return; + } + let (target, target_hits) = listen(None); + let (proxy, proxy_hits) = listen(None); + let redirect = format!("HTTP/1.1 302 Found\r\nLocation: {target}/elsewhere\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"); + let (endpoint, endpoint_hits) = listen(Some(redirect)); + + child("stream", &endpoint, &proxy); + assert!(settled(&endpoint_hits, true), "the stream never reached its own endpoint"); + std::thread::sleep(Duration::from_millis(300)); + assert_eq!(proxy_hits.load(Ordering::SeqCst), 0, "the stream's agent took the environment's proxy"); + assert_eq!(target_hits.load(Ordering::SeqCst), 0, "the stream's agent followed a redirect"); + + child("plain", &endpoint, &proxy); + assert!(settled(&proxy_hits, true), "the child's environment names a proxy"); +}