#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_chat_no_model_returns_dry_run() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let state = super::state::BancoStateInner::with_defaults();
assert!(!state.model.is_loaded());
let app = super::router::create_banco_router(state);
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Hello!"}]
});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let json: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
assert_eq!(json["choices"][0]["finish_reason"], "dry_run");
assert!(json["choices"][0]["message"]["content"]
.as_str()
.expect("content")
.contains("No model loaded"));
assert_eq!(json["choices"][0]["message"]["role"], "assistant");
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_chat_response_has_openai_fields() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Test"}]
});
let response = app
.oneshot(
Request::post("/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let json: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
assert!(json["id"].as_str().is_some(), "must have id");
assert_eq!(json["object"], "chat.completion");
assert!(json["created"].as_u64().is_some(), "must have created timestamp");
assert!(json["model"].as_str().is_some(), "must have model");
assert!(json["choices"].as_array().is_some(), "must have choices");
assert!(json["usage"]["prompt_tokens"].as_u64().is_some());
assert!(json["usage"]["completion_tokens"].as_u64().is_some());
assert!(json["usage"]["total_tokens"].as_u64().is_some());
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_stream_returns_sse_chunks() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Test"}],
"stream": true
});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
let ct = response.headers().get("content-type").expect("content-type");
assert!(ct.to_str().expect("str").contains("text/event-stream"));
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let body_str = String::from_utf8_lossy(&bytes);
assert!(body_str.contains("data:"), "SSE must have data: lines");
assert!(body_str.contains("[DONE]"), "SSE must end with [DONE]");
for line in body_str.lines() {
if let Some(data) = line.strip_prefix("data:") {
let data = data.trim();
if data == "[DONE]" {
break;
}
let chunk: serde_json::Value = serde_json::from_str(data).expect("parse chunk");
assert_eq!(chunk["object"], "chat.completion.chunk");
assert!(chunk["choices"][0]["delta"].is_object());
break;
}
}
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_chat_empty_messages_returns_400() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({"messages": []});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::BAD_REQUEST);
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_tokenize_heuristic_fallback() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({"text": "Hello, world! This is a test."});
let response = app
.oneshot(
Request::post("/api/v1/tokenize")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let json: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
let count = json["count"].as_u64().expect("count");
assert!(count > 0, "should estimate non-zero tokens");
let tokens = json["tokens"].as_array().expect("tokens");
assert_eq!(tokens.len(), count as usize, "token IDs should match count");
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_detokenize_heuristic_fallback() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({"tokens": [1, 2, 3, 4, 5]});
let response = app
.oneshot(
Request::post("/api/v1/detokenize")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let json: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
let text = json["text"].as_str().expect("text");
assert!(text.contains("5 tokens"), "should describe token count");
}
#[cfg(feature = "realizar")]
#[test]
#[allow(non_snake_case)]
fn test_P2B_encode_prompt_finds_known_tokens() {
let vocab: Vec<String> = vec![
"<unk>".to_string(),
"</s>".to_string(),
"H".to_string(),
"e".to_string(),
"l".to_string(),
"o".to_string(),
];
let tokens = super::inference::encode_prompt(&vocab, "Hello");
assert_eq!(tokens.len(), 5);
assert_eq!(tokens[0], 2); assert_eq!(tokens[1], 3); assert_eq!(tokens[2], 4); assert_eq!(tokens[3], 4); assert_eq!(tokens[4], 5); }
#[cfg(feature = "realizar")]
#[test]
#[allow(non_snake_case)]
fn test_P2B_sample_temperature_zero_is_greedy() {
let params = super::inference::SamplingParams { temperature: 0.0, top_k: 40, max_tokens: 10 };
assert!(params.temperature.abs() < f32::EPSILON);
}
#[test]
#[allow(non_snake_case)]
fn test_P2B_model_slot_empty() {
let slot = super::model_slot::ModelSlot::empty();
assert!(!slot.is_loaded());
assert!(slot.info().is_none());
assert_eq!(slot.uptime_secs(), 0);
}
#[test]
#[allow(non_snake_case)]
fn test_P2B_model_slot_unload_empty_returns_error() {
let slot = super::model_slot::ModelSlot::empty();
let result = slot.unload();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), super::model_slot::ModelSlotError::NoModelLoaded);
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_usage_tokens_add_up() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Hello, world!"}]
});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
let bytes = axum::body::to_bytes(response.into_body(), 1_048_576).await.expect("body");
let json: serde_json::Value = serde_json::from_slice(&bytes).expect("parse");
let prompt = json["usage"]["prompt_tokens"].as_u64().expect("prompt");
let completion = json["usage"]["completion_tokens"].as_u64().expect("completion");
let total = json["usage"]["total_tokens"].as_u64().expect("total");
assert_eq!(total, prompt + completion);
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_max_tokens_in_request() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = super::router::create_banco_router(super::state::BancoStateInner::with_defaults());
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Test"}],
"max_tokens": 10
});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
}
#[tokio::test]
#[allow(non_snake_case)]
async fn test_P2B_chat_with_conversation_id() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let state = super::state::BancoStateInner::with_defaults();
let conv_id = state.conversations.create("banco-echo");
let app = super::router::create_banco_router(state.clone());
let body = serde_json::json!({
"messages": [{"role": "user", "content": "Hello!"}],
"conversation_id": conv_id
});
let response = app
.oneshot(
Request::post("/api/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&body).expect("json")))
.expect("req"),
)
.await
.expect("resp");
assert_eq!(response.status(), axum::http::StatusCode::OK);
let full = state.conversations.get(&conv_id).expect("get conv");
assert!(full.messages.len() >= 2, "should have user message + assistant response");
assert_eq!(full.messages[0].role, crate::serve::templates::Role::User);
assert_eq!(full.messages.last().expect("last").role, crate::serve::templates::Role::Assistant);
}