Files
sausage/host/tests/it/proxy_models.rs
T

108 lines
5.8 KiB
Rust

//! The host proxy catalog selects distinct pi-ai transports with shared proxy authentication.
use sausage::{
exec::{Command, Exec, Options, Stream},
world::World,
};
use std::{sync::Arc, time::Duration};
struct NoExec;
#[async_trait::async_trait]
impl Exec for NoExec {
async fn exec(
&self,
_: Command,
_: Options,
_: &mut (dyn for<'a> FnMut(Stream, &'a [u8]) + Send),
) -> anyhow::Result<i32> {
anyhow::bail!("unexpected world command")
}
}
fn event(name: &str, value: serde_json::Value) -> String {
format!("event: {name}\ndata: {value}\n\n")
}
#[tokio::test(flavor = "current_thread")]
async fn provider_transport_and_prices() -> anyhow::Result<()> {
use serde_json::json;
// Production initializes this in the process runtime; this fixture uses NoExec.
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let server = tiny_http::Server::http("127.0.0.1:0").map_err(|e| anyhow::anyhow!(e))?;
let base = format!("http://{}", server.server_addr());
let proxy = json!({"base_url":base,"api_key":"test-proxy-key"});
let thread = std::thread::spawn(move || -> anyhow::Result<()> {
for path in [
"/v1/models",
"/v1/responses",
"/apis/anthropic/v1/messages?beta=true",
"/offline/v1/models",
] {
let mut request = server
.recv_timeout(Duration::from_secs(20))?
.ok_or_else(|| anyhow::anyhow!("request timeout"))?;
assert_eq!(request.url(), path);
let authenticated = request.headers().iter().any(|h| {
(h.field.equiv("Authorization") && h.value.as_str() == "Bearer test-proxy-key")
|| (h.field.equiv("x-api-key") && h.value.as_str() == "test-proxy-key")
});
assert!(authenticated, "missing proxy key");
if path == "/offline/v1/models" {
request.respond(tiny_http::Response::empty(503))?;
continue;
}
let (body, mime) = if path == "/v1/models" {
let rows: Vec<_> = [("openai", "gpt-6.1-sol", "openai-responses", "/v1/responses"),
("anthropic", "claude-opus-5-5", "anthropic-messages", "/apis/anthropic/v1/messages")]
.into_iter().map(|(creator,id,api,path)|json!({"creator":creator,"id":id,"endpoints":[{"api":api,"path":path}],
"providers":[{"pricing":{"currency":"USD","unit_tokens":1000,"input":0.002,"output":0.008,"cached_input":0.0001,"cache_write":0.003}}]})).collect();
(json!({"data":rows}).to_string(), "application/json")
} else {
let mut body = String::new();
request.as_reader().read_to_string(&mut body)?;
let body: serde_json::Value = serde_json::from_str(&body)?;
assert_eq!(body["stream"], true);
assert!(
request
.headers()
.iter()
.any(|h| h.field.equiv("Shopify-Usage-Tag")
&& h.value.as_str() == "[\"pi\",\"sausage\"]")
);
let stream = if path == "/v1/responses" {
assert_eq!(body["model"], "gpt-6.1-sol");
[
event("response.output_item.added", json!({"type":"response.output_item.added","output_index":0,"item":{"type":"message","id":"m","role":"assistant","content":[]}})),
event("response.content_part.added", json!({"type":"response.content_part.added","output_index":0,"content_index":0,"part":{"type":"output_text","text":""}})),
event("response.output_text.delta", json!({"type":"response.output_text.delta","output_index":0,"content_index":0,"delta":"hello"})),
event("response.completed", json!({"type":"response.completed","response":{"id":"r","status":"completed","output":[],"usage":{"input_tokens":10,"output_tokens":1,"total_tokens":11}}})),
].concat()
} else {
assert_eq!(body["model"], "claude-opus-5-5");
assert_eq!(body["thinking"]["type"], "adaptive");
assert_eq!(body["output_config"]["effort"], "low");
[
event("message_start", json!({"type":"message_start","message":{"id":"m","type":"message","role":"assistant","model":"claude-opus-5-5","content":[],"usage":{"input_tokens":10,"output_tokens":0}}})),
event("content_block_start", json!({"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}})),
event("content_block_delta", json!({"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}})),
event("content_block_stop", json!({"type":"content_block_stop","index":0})),
event("message_delta", json!({"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":1}})),
event("message_stop", json!({"type":"message_stop"})),
].concat()
};
(stream, "text/event-stream")
};
request.respond(
tiny_http::Response::from_string(body)
.with_header(tiny_http::Header::from_bytes("Content-Type", mime).unwrap()),
)?;
}
Ok(())
});
let dir = tempfile::tempdir()?;
let source = format!(
"globalThis.testProxy = {proxy};\n{}",
include_str!("../../js/proxy-models-test-bundle.js")
);
let result =
sausage::harness::run_module(World::open(dir.path())?, Arc::new(NoExec), &source).await;
thread.join().unwrap()?;
result
}