use axum::{
body::Body,
http::{Request, StatusCode},
};
use tower::util::ServiceExt;
use crate::api::{create_router, AppState};
use crate::layers::{Model, ModelConfig};
use crate::tokenizer::BPETokenizer;
fn stop_app() -> axum::Router {
let config = ModelConfig {
vocab_size: 8,
hidden_dim: 8,
num_heads: 1,
num_layers: 1,
intermediate_dim: 16,
eps: 1e-5,
};
let model = Model::new(config).expect("dense model");
let vocab: Vec<String> = (0..8).map(|i| format!("a{i}-b{i}-c{i}")).collect();
let tokenizer = BPETokenizer::new(vocab, vec![], "a0-b0-c0").expect("tokenizer");
create_router(AppState::new(model, tokenizer))
}
async fn post(uri: &str, body: &str) -> axum::response::Response {
stop_app()
.oneshot(
Request::builder()
.method("POST")
.uri(uri)
.header("content-type", "application/json")
.body(Body::from(body.to_string()))
.expect("request"),
)
.await
.expect("response")
}
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(bytes.to_vec()).expect("body is utf-8")
}
async fn body_json(response: axum::response::Response) -> serde_json::Value {
let text = body_text(response).await;
serde_json::from_str(&text).unwrap_or_else(|e| panic!("body is JSON ({e}): {text}"))
}
async fn complete(stop_clause: &str) -> (String, String) {
let body = format!(
r#"{{"model":"default","prompt":"a1-b1-c1","max_tokens":4,"temperature":0{stop_clause}}}"#
);
let response = post("/v1/completions", &body).await;
assert_eq!(
response.status(),
StatusCode::OK,
"completion must be served; body: {}",
body
);
let json = body_json(response).await;
let choice = &json["choices"][0];
(
choice["text"].as_str().expect("text is a string").to_string(),
choice["finish_reason"]
.as_str()
.expect("finish_reason is a string")
.to_string(),
)
}
#[tokio::test]
async fn control_without_stop_produces_the_full_output() {
let (text, finish_reason) = complete("").await;
assert_eq!(
text, "a0-b0-c0a0-b0-c0a0-b0-c0a0-b0-c0",
"control: 4 tokens must decode to the full untruncated text"
);
assert_eq!(
finish_reason, "length",
"control: the budget was exhausted with no stop match"
);
}
#[tokio::test]
async fn stop_sequence_truncates_the_completion_at_the_earliest_occurrence() {
let (full, _) = complete("").await;
assert!(
full.contains("-b"),
"precondition: the control output must CONTAIN the stop string, else this \
test proves nothing; got {full:?}"
);
let cut = full.find("-b").expect("stop occurs in the control output");
assert!(cut > 0, "the stop must occur MID-output, not at position 0");
let (stopped, finish_reason) = complete(r#","stop":["-b"]"#).await;
assert_eq!(
stopped,
full[..cut],
"the completion must be cut at the earliest stop position; pre-fix this \
returned the whole {full:?}"
);
assert!(
!stopped.contains("-b"),
"the returned text must not contain the stop string; got {stopped:?}"
);
assert!(
stopped.len() < full.len(),
"the stopped completion must be SHORTER than the control ({} vs {})",
stopped.len(),
full.len()
);
assert_eq!(
finish_reason, "stop",
"a matched stop beats the token budget; pre-fix this said \"length\""
);
}
#[tokio::test]
async fn stop_list_order_does_not_decide_where_the_cut_lands() {
let (full, _) = complete("").await;
let earliest = full.find("-b").expect("-b occurs");
let later = full.find("-c").expect("-c occurs");
assert!(earliest < later, "the fixture must order the two stops");
let (stopped, finish_reason) = complete(r#","stop":["-c","-b"]"#).await;
assert_eq!(
stopped,
full[..earliest],
"with -c listed first, the cut must still land at the earlier -b"
);
assert_eq!(finish_reason, "stop");
}
#[tokio::test]
async fn unmatched_stop_leaves_the_completion_whole() {
let (full, control_reason) = complete("").await;
let (text, finish_reason) = complete(r#","stop":["ZZZ-not-in-output"]"#).await;
assert_eq!(text, full, "an unmatched stop must change nothing");
assert_eq!(
finish_reason, control_reason,
"an unmatched stop must not fake a stop finish"
);
}
#[tokio::test]
async fn empty_stop_string_does_not_erase_the_completion() {
let (full, _) = complete("").await;
let (text, _) = complete(r#","stop":[""]"#).await;
assert_eq!(text, full, "an empty stop string must be ignored");
}
#[tokio::test]
async fn streamed_completion_is_stopped_at_the_same_place() {
let (expected, _) = complete(r#","stop":["-b"]"#).await;
let response = post(
"/v1/completions",
r#"{"model":"default","prompt":"a1-b1-c1","max_tokens":4,"temperature":0,"stream":true,"stop":["-b"]}"#,
)
.await;
assert_eq!(response.status(), StatusCode::OK);
let body = body_text(response).await;
let frames: Vec<serde_json::Value> = body
.lines()
.filter_map(|line| line.strip_prefix("data: "))
.filter(|payload| payload.trim() != "[DONE]")
.map(|payload| serde_json::from_str(payload).expect("SSE frame is JSON"))
.collect();
let streamed: String = frames
.iter()
.filter_map(|f| f["choices"][0]["text"].as_str())
.collect();
assert_eq!(
streamed, expected,
"the stream must reassemble to the same stopped text as the buffered body"
);
assert!(
!streamed.contains("-b"),
"no delta may carry the stop string; got {streamed:?}"
);
}