repositories / smith
smith
There are many coding harnesses - but this one is fast
owned by admin
smith-harness/tests/http_effect.rs
Raw//! End-to-end provider traffic through the typed HTTP effect
//! (`SMH-SPEC-SPEC0001`, Network transport).
//! Integration tests drive a real socket, a real executor, and the
//! durable session store.
use smith_harness::runtime::{RuntimeConfig, RuntimeProvider, build_agent};
use std::io::{Read, Write};
use std::net::TcpListener;
use smith_core::frame::Frame;
use smith_core::session::{EntryContent, PROVIDER_HTTP_KIND};
use smith_core::store::SessionReader;
/// Serve one OpenAI-shaped SSE response and return the port.
fn spawn_sse_server(body: &'static str) -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
std::thread::spawn(move || {
let (mut socket, _) = listener.accept().unwrap();
socket
.set_read_timeout(Some(std::time::Duration::from_secs(10)))
.unwrap();
let mut request = Vec::new();
let mut buffer = [0u8; 4096];
let header_end = loop {
let n = socket.read(&mut buffer).unwrap();
assert!(n > 0 && request.len() + n <= 1 << 20);
request.extend_from_slice(&buffer[..n]);
if let Some(at) = request.windows(4).position(|part| part == b"\r\n\r\n") {
break at + 4;
}
};
let headers = std::str::from_utf8(&request[..header_end]).unwrap();
let len: usize = headers
.lines()
.find_map(|line| {
line.to_ascii_lowercase()
.strip_prefix("content-length: ")
.and_then(|value| value.trim().parse().ok())
})
.unwrap();
while request.len() < header_end + len {
let n = socket.read(&mut buffer).unwrap();
assert!(n > 0 && request.len() + n <= 1 << 20);
request.extend_from_slice(&buffer[..n]);
}
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nConnection: close\r\n\r\n{body}"
);
socket.write_all(response.as_bytes()).unwrap();
});
port
}
fn provider_http_records(path: &std::path::Path) -> Vec<serde_json::Value> {
let config = smith::config::Config::default_valid();
let mut records = Vec::new();
SessionReader::open(path)
.unwrap()
.scan(config.frame_bytes_max, |frame| {
if let Frame::Known { entry } = frame
&& let EntryContent::Meta { kind, detail } = EntryContent::decode(&entry.content)?
&& kind == PROVIDER_HTTP_KIND
{
records.push(serde_json::to_value(detail).unwrap());
}
Ok(())
})
.unwrap();
records
}
#[test]
fn provider_round_trips_through_http_effect_with_durable_redacted_records() {
let dir = std::env::temp_dir().join(format!("smith_http_effect_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"hello \"}}]}\n\n\
data: {\"choices\":[{\"delta\":{\"content\":\"world\"}}]}\n\n\
data: {\"choices\":[{\"finish_reason\":\"stop\",\"delta\":{}}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":2}}\n\n\
data: [DONE]\n\n";
let port = spawn_sse_server(sse);
let config = RuntimeConfig {
provider: RuntimeProvider::OpenAiCompatible {
base_url: format!("http://127.0.0.1:{port}/v1"),
model: "gpt-test".to_string(),
},
api_key: Some("sk-integration-secret".to_string()),
..RuntimeConfig::mock(&dir, Vec::new())
};
let mut agent = build_agent(&config).unwrap();
let outcome = agent.run_turn("say it").unwrap();
assert_eq!(outcome.text, "hello world");
let records = provider_http_records(&config.session_path);
assert_eq!(records.len(), 2, "intent and outcome records");
let intent = &records[0];
assert_eq!(intent["phase"], "intent");
assert_eq!(intent["method"], "POST");
assert!(intent["request_bytes"].as_u64().unwrap() > 0);
assert_eq!(
intent
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect::<Vec<_>>(),
["url", "phase", "method", "headers", "request_bytes"]
);
assert_eq!(
intent["url"],
format!("http://127.0.0.1:{port}/v1/chat/completions")
);
let headers = intent["headers"].as_array().unwrap();
let authorization = headers
.iter()
.find(|header| {
header["name"]
.as_str()
.unwrap_or_default()
.eq_ignore_ascii_case("authorization")
})
.unwrap();
assert_eq!(authorization["value"], "<redacted>");
let outcome_record = &records[1];
assert_eq!(outcome_record["phase"], "outcome");
assert_eq!(outcome_record["status"], 200);
assert_eq!(outcome_record["outcome"], "completed");
assert_eq!(
outcome_record["response_bytes"].as_u64(),
Some(sse.len() as u64)
);
assert_eq!(
outcome_record
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect::<Vec<_>>(),
["phase", "status", "outcome", "response_bytes"]
);
assert!(
!serde_json::to_string(&records)
.unwrap()
.contains("sk-integration-secret")
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn explicit_credential_wins_over_missing_env() {
// No store, no env: only the explicit override can resolve.
let dir = std::env::temp_dir().join(format!("smith_http_explicit_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let store = smith_ai::auth::AuthStore::at(dir.join("empty-store"));
assert!(
store
.load(smith_ai::models::ProviderKind::Anthropic)
.unwrap()
.is_none()
);
let config = RuntimeConfig {
provider: RuntimeProvider::Anthropic {
base_url: "https://example.test".to_string(),
model: "m".to_string(),
},
api_key: Some("sk-explicit".to_string()),
..RuntimeConfig::mock(&dir, Vec::new())
};
let _agent = build_agent(&config).unwrap();
let _ = std::fs::remove_dir_all(&dir);
}