use axum::{
body::Body,
http::{Request, StatusCode},
};
use tower::util::ServiceExt;
use crate::api::{create_router, AppState};
async fn send(state: AppState, uri: &str, json: &str) -> axum::response::Response {
create_router(state)
.oneshot(
Request::builder()
.method("POST")
.uri(uri)
.header("content-type", "application/json")
.body(Body::from(json.to_string()))
.expect("build request"),
)
.await
.expect("dispatch")
}
fn content_type(response: &axum::response::Response) -> String {
response
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or_default()
.to_string()
}
async fn body_text(response: axum::response::Response) -> String {
let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.expect("read body");
String::from_utf8_lossy(&bytes).into_owned()
}
fn streamed_text(body: &str) -> String {
body.lines()
.filter_map(|line| line.strip_prefix("data: "))
.filter(|payload| payload.trim() != "[DONE]")
.filter_map(|payload| serde_json::from_str::<serde_json::Value>(payload).ok())
.filter_map(|frame| {
frame["choices"][0]["delta"]["content"]
.as_str()
.map(str::to_string)
})
.collect()
}
const GREEDY_FIELDS: &str = r#"{"model":"default","messages":[{"role":"user","content":"token5 token6"}],"max_tokens":4,"temperature":0.0"#;
fn greedy_body() -> String {
format!("{GREEDY_FIELDS}}}")
}
fn greedy_streaming_body() -> String {
format!("{GREEDY_FIELDS},\"stream\":true}}")
}
#[cfg(feature = "gpu")]
#[tokio::test]
async fn chat_completions_stream_serves_a_quantized_server() {
use super::native_routes_2376::quantized_state;
let response = send(
quantized_state(),
"/v1/chat/completions/stream",
&greedy_body(),
)
.await;
let status = response.status();
let ctype = content_type(&response);
let body = body_text(response).await;
assert_eq!(
status,
StatusCode::OK,
"a quantized server serves /v1/chat/completions; the /stream sibling \
answered {status} with {body}"
);
assert!(
ctype.starts_with("text/event-stream"),
"a streaming route must be SSE-framed, got {ctype:?}"
);
assert!(
body.trim_end().ends_with("data: [DONE]"),
"an OpenAI stream terminates with the [DONE] sentinel; got:\n{body}"
);
}
#[cfg(feature = "gpu")]
#[tokio::test]
async fn stream_route_and_stream_flag_deliver_the_same_text() {
use super::native_routes_2376::quantized_state;
let via_flag = body_text(
send(
quantized_state(),
"/v1/chat/completions",
&greedy_streaming_body(),
)
.await,
)
.await;
let via_route = body_text(
send(
quantized_state(),
"/v1/chat/completions/stream",
&greedy_body(),
)
.await,
)
.await;
let expected = streamed_text(&via_flag);
assert!(
via_flag.trim_end().ends_with("data: [DONE]"),
"control: the flag form must stream:\n{via_flag}"
);
assert_eq!(
streamed_text(&via_route),
expected,
"the /stream route delivered different text than the stream flag on the \
same server and request:\nroute body:\n{via_route}\nflag body:\n{via_flag}"
);
}
#[tokio::test]
async fn temperature_zero_is_served_on_both_chat_routes() {
for uri in ["/v1/chat/completions", "/v1/chat/completions/stream"] {
let state = AppState::demo().expect("dense demo AppState");
let response = send(state, uri, &greedy_streaming_body()).await;
let status = response.status();
let body = body_text(response).await;
assert_eq!(
status,
StatusCode::OK,
"{uri} refused temperature 0 with {status}: {body}"
);
assert!(
body.trim_end().ends_with("data: [DONE]"),
"{uri} must stream to completion; got:\n{body}"
);
}
}
#[tokio::test]
async fn stream_route_with_no_model_is_503_not_404() {
let state = AppState::demo_mock().expect("model-less AppState");
let response = send(state, "/v1/chat/completions/stream", &greedy_body()).await;
let status = response.status();
let body = body_text(response).await;
assert_eq!(
status,
StatusCode::SERVICE_UNAVAILABLE,
"a mounted route with nothing to serve is 503 (what /v1/predict and \
/v1/gpu/warmup answer); 404 claims the route does not exist. Got \
{status} with {body}"
);
}
#[tokio::test]
async fn stream_route_with_an_unknown_model_name_is_still_404() {
let mut state = AppState::demo_mock().expect("model-less AppState");
state.registry = Some(std::sync::Arc::new(crate::registry::ModelRegistry::new(1)));
let response = send(
state,
"/v1/chat/completions/stream",
r#"{"model":"no-such-model","messages":[{"role":"user","content":"token5"}],"max_tokens":2,"temperature":0.0}"#,
)
.await;
let status = response.status();
let body = body_text(response).await;
assert_eq!(
status,
StatusCode::NOT_FOUND,
"the client named a model this server does not have; that is 404. Got \
{status} with {body}"
);
}