use std::sync::mpsc::channel;
use super::fake::{dead_port, refuse, serve, sse};
use super::*;
async fn run(port: u16) -> Vec<ChatDelta> {
let (tx, rx) = channel();
stream_chat(
port,
vec![("user".into(), "hi".into())],
ThinkingRequest::Auto,
tx,
)
.await;
rx.try_iter().collect()
}
fn tags(ds: &[ChatDelta]) -> Vec<String> {
ds.iter()
.map(|d| match d {
ChatDelta::Token(t) => format!("tok:{t}"),
ChatDelta::Reasoning(t) => format!("rsn:{t}"),
ChatDelta::Done {
tokens,
reasoning_tokens,
..
} => format!("done:{tokens}/{reasoning_tokens}"),
ChatDelta::Error(e) => format!("err:{e}"),
})
.collect()
}
fn frame(json: &str) -> Vec<u8> {
format!("data: {json}\n\n").into_bytes()
}
#[tokio::test]
async fn a_refused_connection_is_reported_rather_than_leaving_the_pane_blank() {
let ds = run(dead_port()).await;
assert_eq!(ds.len(), 1, "one terminal delta, nothing else");
assert!(
matches!(&ds[0], ChatDelta::Error(e) if e.starts_with("connect:")),
"{:?}",
tags(&ds)
);
}
#[tokio::test]
async fn a_non_200_carries_the_servers_own_explanation_not_just_the_status() {
let f = serve(|s| {
refuse(
s,
"503 Service Unavailable",
"application/json",
r#"{"error":{"message":"no model is loaded; run `met serve`"}}"#,
)
});
let ds = run(f.port).await;
let ChatDelta::Error(e) = &ds[0] else {
panic!("{:?}", tags(&ds))
};
assert!(e.contains("503"), "{e}");
assert!(e.contains("no model is loaded"), "{e}");
}
#[tokio::test]
async fn a_non_200_whose_body_is_not_json_falls_back_to_the_status_line() {
let f = serve(|s| refuse(s, "502 Bad Gateway", "text/html", "<html>nginx</html>"));
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["err:HTTP/1.1 502 Bad Gateway"]);
}
#[tokio::test]
async fn a_non_200_whose_body_arrives_after_the_headers_is_still_read() {
let f = serve(|s| {
use std::io::Write as _;
let body = r#"{"error":{"message":"the model is still loading"}}"#;
let _ = s.write_all(
format!(
"HTTP/1.1 503 Service Unavailable\r\nContent-Length: {}\r\n\r\n",
body.len()
)
.as_bytes(),
);
let _ = s.flush();
std::thread::sleep(std::time::Duration::from_millis(20));
let _ = s.write_all(body.as_bytes());
let _ = s.flush();
});
let ds = run(f.port).await;
let ChatDelta::Error(e) = &ds[0] else {
panic!("{:?}", tags(&ds))
};
assert!(e.contains("still loading"), "{e}");
}
#[tokio::test]
async fn a_frame_split_mid_character_across_reads_arrives_whole() {
let json = r#"{"choices":[{"delta":{"content":"héllo — ✓"}}]}"#;
let whole = frame(json);
let cut = whole.iter().position(|b| *b == 0xE2).expect("multi-byte") + 1;
let (a, b) = whole.split_at(cut);
let (a, b) = (a.to_vec(), b.to_vec());
let f = serve(move |s| sse(s, &[&a, &b, b"data: [DONE]\n\n"]));
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["tok:héllo — ✓", "done:1/0"]);
}
#[tokio::test]
async fn a_chunk_carrying_both_halves_forwards_the_reasoning_first() {
let f = serve(|s| {
sse(
s,
&[
&frame(r#"{"choices":[{"delta":{"reasoning_content":"hm","content":"Paris."}}]}"#),
b"data: [DONE]\n\n",
],
)
});
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["rsn:hm", "tok:Paris.", "done:1/1"]);
}
#[tokio::test]
async fn vllm_wire_reasoning_deltas_are_forwarded_as_reasoning() {
let f = serve(|s| {
sse(
s,
&[
&frame(r#"{"choices":[{"delta":{"reasoning":"hm"}}]}"#),
&frame(r#"{"choices":[{"delta":{"reasoning":null,"content":"4"}}]}"#),
b"data: [DONE]\n\n",
],
)
});
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["rsn:hm", "tok:4", "done:1/1"]);
}
#[tokio::test]
async fn keepalives_comments_and_unparseable_frames_do_not_break_the_stream() {
let f = serve(|s| {
sse(
s,
&[
b": keepalive\n\n",
b"event: ping\n",
b"data: {not json\n\n",
b"datamissing-space\n\n",
&frame(r#"{"choices":[{"delta":{"content":"ok"}}]}"#),
b"data: [DONE]\n\n",
],
)
});
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["tok:ok", "done:1/0"]);
}
#[tokio::test]
async fn deltas_with_nothing_readable_in_them_are_not_counted_as_tokens() {
let f = serve(|s| {
sse(
s,
&[
&frame(r#"{"choices":[{"delta":{"content":""}}]}"#),
&frame(r#"{"choices":[{"delta":{"content":null}}]}"#),
&frame(r#"{"choices":[{"delta":{"content":7}}]}"#),
&frame(r#"{"choices":[{"delta":{"role":"assistant"}}]}"#),
&frame(r#"{"choices":[{"delta":{"tool_calls":[{"index":0}]}}]}"#),
&frame(r#"{"choices":[]}"#),
b"data: [DONE]\n\n",
],
)
});
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["done:0/0"]);
let ChatDelta::Done {
ttft_ms, tok_per_s, ..
} = &ds[0]
else {
panic!("Done")
};
assert!(ttft_ms.is_none(), "nothing readable ever arrived");
assert!(tok_per_s.is_none(), "no division by an empty reply");
}
#[tokio::test]
async fn a_stream_that_ends_without_done_still_reports_what_it_measured() {
let f = serve(|s| {
sse(
s,
&[
&frame(r#"{"choices":[{"delta":{"reasoning_content":"a"}}]}"#),
&frame(r#"{"choices":[{"delta":{"reasoning_content":"b"}}]}"#),
],
)
});
let ds = run(f.port).await;
assert_eq!(tags(&ds), vec!["rsn:a", "rsn:b", "done:0/2"]);
let ChatDelta::Done {
ttft_ms,
answer_ttft_ms,
think_ms,
tok_per_s,
..
} = &ds[2]
else {
panic!("Done")
};
assert!(ttft_ms.is_some(), "the first reasoning delta IS the TTFT");
assert!(answer_ttft_ms.is_none(), "nothing readable ever landed");
assert!(think_ms.is_some(), "the span falls back to the last delta");
assert!(
tok_per_s.is_some(),
"reasoning tokens are still decode work"
);
}
#[tokio::test]
async fn a_very_long_single_frame_is_delivered_as_one_token() {
let long = "x".repeat(64 * 1024);
let body = frame(&serde_json::json!({"choices":[{"delta":{"content":long}}]}).to_string());
let f = serve(move |s| sse(s, &[&body, b"data: [DONE]\n\n"]));
let ds = run(f.port).await;
let ChatDelta::Token(t) = &ds[0] else {
panic!("{:?}", ds.len())
};
assert_eq!(t.len(), 64 * 1024);
assert_eq!(tags(&ds[1..]), vec!["done:1/0"]);
}
#[tokio::test]
async fn ansi_escapes_in_the_model_output_are_passed_through_verbatim() {
let raw = "\u{1b}[31mred\u{1b}[0m\u{7}";
let body = frame(&serde_json::json!({"choices":[{"delta":{"content":raw}}]}).to_string());
let f = serve(move |s| sse(s, &[&body, b"data: [DONE]\n\n"]));
let ds = run(f.port).await;
let ChatDelta::Token(t) = &ds[0] else {
panic!("token")
};
assert_eq!(t, raw);
}
#[tokio::test]
async fn the_request_is_a_streaming_completion_carrying_the_history_in_order() {
let f = serve(|s| sse(s, &[b"data: [DONE]\n\n"]));
let (tx, _rx) = channel::<ChatDelta>();
stream_chat(
f.port,
vec![
("user".into(), "hi".into()),
("assistant".into(), "hello".into()),
("user".into(), "and now?".into()),
],
ThinkingRequest::Off,
tx,
)
.await;
let req = f.request.recv().expect("the server saw a request");
assert!(
req.starts_with("POST /v1/chat/completions HTTP/1.1\r\n"),
"{req}"
);
assert!(req.contains("Accept: text/event-stream"), "{req}");
let body: serde_json::Value =
serde_json::from_str(req.split("\r\n\r\n").nth(1).expect("a body")).expect("valid JSON");
assert_eq!(body["stream"], serde_json::json!(true));
assert_eq!(
body["messages"],
serde_json::json!([
{"role": "user", "content": "hi"},
{"role": "assistant", "content": "hello"},
{"role": "user", "content": "and now?"},
]),
"multi-turn history, in order, roles intact"
);
assert_eq!(
body["chat_template_kwargs"],
serde_json::json!({"enable_thinking": false})
);
}
#[tokio::test]
async fn a_stream_whose_reader_hung_up_stops_instead_of_filling_a_dead_channel() {
let f = serve(|s| {
use std::io::Write as _;
let body = frame(r#"{"choices":[{"delta":{"content":"tick"}}]}"#);
let _ = s.write_all(b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n");
for _ in 0..100_000 {
if s.write_all(&body).is_err() {
return;
}
}
});
let (tx, rx) = channel::<ChatDelta>();
drop(rx);
let done = tokio::time::timeout(
std::time::Duration::from_secs(20),
stream_chat(
f.port,
vec![("user".into(), "hi".into())],
ThinkingRequest::On,
tx,
),
)
.await;
assert!(done.is_ok(), "the task gave up once nobody was listening");
}
#[test]
fn nonempty_accepts_only_a_non_empty_string() {
assert_eq!(nonempty(&serde_json::json!("hi")), Some("hi".into()));
assert_eq!(nonempty(&serde_json::json!("")), None);
assert_eq!(nonempty(&serde_json::json!(null)), None);
assert_eq!(nonempty(&serde_json::json!(3)), None);
assert_eq!(nonempty(&serde_json::json!({"a": "b"})), None);
}
#[test]
fn find_subslice_reports_the_first_match_and_nothing_when_absent() {
assert_eq!(find_subslice(b"ab\r\n\r\ncd", b"\r\n\r\n"), Some(2));
assert_eq!(find_subslice(b"\r\n\r\n", b"\r\n\r\n"), Some(0));
assert_eq!(find_subslice(b"ab\r\ncd", b"\r\n\r\n"), None);
assert_eq!(find_subslice(b"ab", b"\r\n\r\n"), None);
}
#[test]
fn an_empty_conversation_still_produces_a_well_formed_body() {
let body: serde_json::Value =
serde_json::from_str(&request_body(&[], ThinkingRequest::On)).expect("valid JSON");
assert_eq!(body["messages"], serde_json::json!([]));
assert_eq!(body["model"], serde_json::json!("metrale-tui"));
}