#![allow(dead_code)]
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use llm::{LlmResponse, TokenUsage};
use serde_json::Value;
pub fn fixture_path(provider: &str, scenario: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(provider).join(format!("{scenario}.sse"))
}
pub fn read_fixture(provider: &str, scenario: &str) -> Vec<u8> {
let path = fixture_path(provider, scenario);
fs::read(&path).unwrap_or_else(|_| {
panic!(
"missing fixture {provider}/{scenario} at {} — \
run `cargo nextest run --run-ignored only -E 'test(capture_{provider})'` to regenerate",
path.display()
)
})
}
pub fn parse_sse_data_lines(bytes: &[u8]) -> Vec<String> {
let text = std::str::from_utf8(bytes).expect("fixture is utf-8");
text.lines()
.filter_map(|line| line.strip_prefix("data: ").or_else(|| line.strip_prefix("data:")))
.map(str::trim)
.filter(|s| !s.is_empty() && *s != "[DONE]")
.map(str::to_string)
.collect()
}
pub fn find_usage(events: &[LlmResponse]) -> Option<TokenUsage> {
events.iter().find_map(|e| match e {
LlmResponse::Usage { tokens } => Some(*tokens),
_ => None,
})
}
pub fn assert_minimal_usage(usage: &TokenUsage, scenario: &str) {
assert!(usage.input_tokens > 0, "{scenario}: input_tokens should be > 0");
assert!(usage.output_tokens > 0, "{scenario}: output_tokens should be > 0");
}
pub fn require_env(name: &'static str) -> String {
env::var(name).unwrap_or_else(|_| panic!("{name} must be set to capture fixtures"))
}
pub async fn post_json_capture(client: &reqwest::Client, url: &str, headers: &[(&str, &str)], body: &Value) -> Vec<u8> {
let mut req = client.post(url).header("content-type", "application/json").json(body);
for (k, v) in headers {
req = req.header(*k, *v);
}
let resp = req.send().await.expect("http request to provider failed");
let status = resp.status();
let bytes = resp.bytes().await.expect("reading response body failed").to_vec();
assert!(status.is_success(), "provider returned {}: {}", status.as_u16(), String::from_utf8_lossy(&bytes));
bytes
}
pub fn write_fixture(provider: &str, scenario: &str, bytes: &[u8]) {
let path = fixture_path(provider, scenario);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create fixtures dir");
}
fs::write(&path, bytes).unwrap_or_else(|e| {
panic!("write {}: {e}", path.display());
});
eprintln!("wrote {} ({} bytes)", relative_to_manifest(&path).display(), bytes.len());
}
pub fn lorem_filler(approx_tokens: usize) -> String {
let line = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ";
let target_chars = approx_tokens * 4;
let mut s = String::with_capacity(target_chars);
while s.len() < target_chars {
s.push_str(line);
}
s
}
fn relative_to_manifest(path: &Path) -> PathBuf {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.strip_prefix(&manifest).map_or_else(|_| path.to_path_buf(), PathBuf::from)
}