use actix_web::http::StatusCode;
use actix_web::{web, App};
use bamboo_infrastructure::api::models::{Content, ContentPart, FunctionCall, Role, ToolCall};
use super::helpers::{build_completion_response, responses_input_to_chat_messages};
#[test]
fn responses_input_string_becomes_single_user_message() {
let msgs = responses_input_to_chat_messages(serde_json::json!("hi")).unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::User);
match &msgs[0].content {
Content::Text(t) => assert_eq!(t, "hi"),
_ => panic!("expected text content"),
}
}
#[test]
fn responses_input_array_parses_role_and_content_string() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{ "role": "system", "content": "s" },
{ "role": "user", "content": "u" },
{ "role": "assistant", "content": "a" }
]))
.unwrap();
assert_eq!(msgs.len(), 3);
assert_eq!(msgs[0].role, Role::System);
assert_eq!(msgs[1].role, Role::User);
assert_eq!(msgs[2].role, Role::Assistant);
}
#[test]
fn responses_input_parts_support_input_text() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{
"role": "user",
"content": [{ "type": "input_text", "text": "hello" }]
}
]))
.unwrap();
assert_eq!(msgs.len(), 1);
match &msgs[0].content {
Content::Parts(parts) => {
assert_eq!(parts.len(), 1);
match &parts[0] {
ContentPart::Text { text } => assert_eq!(text, "hello"),
_ => panic!("expected text part"),
}
}
_ => panic!("expected parts content"),
}
}
#[test]
fn responses_input_parts_support_output_text() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{
"role": "assistant",
"content": [{ "type": "output_text", "text": "hello from assistant" }]
}
]))
.unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::Assistant);
match &msgs[0].content {
Content::Parts(parts) => {
assert_eq!(parts.len(), 1);
match &parts[0] {
ContentPart::Text { text } => assert_eq!(text, "hello from assistant"),
_ => panic!("expected text part"),
}
}
_ => panic!("expected parts content"),
}
}
#[test]
fn responses_input_parts_support_refusal() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{
"role": "assistant",
"content": [{ "type": "refusal", "refusal": "I can't help with that." }]
}
]))
.unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::Assistant);
match &msgs[0].content {
Content::Parts(parts) => {
assert_eq!(parts.len(), 1);
match &parts[0] {
ContentPart::Text { text } => assert_eq!(text, "I can't help with that."),
_ => panic!("expected text part"),
}
}
_ => panic!("expected parts content"),
}
}
#[test]
fn build_completion_response_populates_core_openai_fields() {
let tool_call = ToolCall {
id: "call_1".to_string(),
tool_type: "function".to_string(),
function: FunctionCall {
name: "read_file".to_string(),
arguments: r#"{"path":"README.md"}"#.to_string(),
},
};
let response = build_completion_response(
"hello from assistant".to_string(),
Some(vec![tool_call.clone()]),
"gpt-test",
);
assert!(response.id.starts_with("chatcmpl-"));
assert_eq!(response.object.as_deref(), Some("chat.completion"));
assert_eq!(response.model.as_deref(), Some("gpt-test"));
assert_eq!(response.choices.len(), 1);
assert_eq!(response.choices[0].message.role, Role::Assistant);
assert_eq!(
response.choices[0].message.tool_calls,
Some(vec![tool_call])
);
assert_eq!(response.choices[0].finish_reason.as_deref(), Some("stop"));
match &response.choices[0].message.content {
Content::Text(text) => assert_eq!(text, "hello from assistant"),
_ => panic!("expected text content"),
}
let usage = response.usage.expect("usage should always be present");
assert_eq!(usage.prompt_tokens, 0);
assert_eq!(usage.completion_tokens, 0);
assert_eq!(usage.total_tokens, 0);
}
#[test]
fn responses_input_function_call_item_becomes_assistant_with_tool_calls() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{
"type": "function_call",
"call_id": "call_abc",
"name": "search",
"arguments": "{\"q\":\"test\"}"
}
]))
.unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::Assistant);
match &msgs[0].content {
Content::Text(t) => assert_eq!(t, ""),
_ => panic!("expected empty text content"),
}
let tool_calls = msgs[0].tool_calls.as_ref().expect("expected tool_calls");
assert_eq!(tool_calls.len(), 1);
assert_eq!(tool_calls[0].id, "call_abc");
assert_eq!(tool_calls[0].tool_type, "function");
assert_eq!(tool_calls[0].function.name, "search");
assert_eq!(tool_calls[0].function.arguments, "{\"q\":\"test\"}");
assert!(msgs[0].tool_call_id.is_none());
}
#[test]
fn responses_input_function_call_output_item_becomes_tool_result() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{
"type": "function_call_output",
"call_id": "call_abc",
"output": "search results here"
}
]))
.unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::Tool);
match &msgs[0].content {
Content::Text(t) => assert_eq!(t, "search results here"),
_ => panic!("expected text content with output"),
}
assert_eq!(msgs[0].tool_call_id.as_deref(), Some("call_abc"));
assert!(msgs[0].tool_calls.is_none());
}
#[test]
fn responses_input_mixed_messages_and_function_calls() {
let msgs = responses_input_to_chat_messages(serde_json::json!([
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Find info about Rust." },
{
"type": "function_call",
"call_id": "call_1",
"name": "web_search",
"arguments": "{\"query\":\"Rust programming\"}"
},
{
"type": "function_call_output",
"call_id": "call_1",
"output": "Rust is a systems programming language."
},
{ "role": "assistant", "content": "Here is what I found about Rust." }
]))
.unwrap();
assert_eq!(msgs.len(), 5);
assert_eq!(msgs[0].role, Role::System);
match &msgs[0].content {
Content::Text(t) => assert_eq!(t, "You are a helpful assistant."),
_ => panic!("expected text"),
}
assert_eq!(msgs[1].role, Role::User);
assert_eq!(msgs[2].role, Role::Assistant);
let tc = msgs[2].tool_calls.as_ref().expect("expected tool_calls");
assert_eq!(tc.len(), 1);
assert_eq!(tc[0].id, "call_1");
assert_eq!(tc[0].function.name, "web_search");
assert_eq!(tc[0].function.arguments, "{\"query\":\"Rust programming\"}");
assert_eq!(msgs[3].role, Role::Tool);
assert_eq!(msgs[3].tool_call_id.as_deref(), Some("call_1"));
match &msgs[3].content {
Content::Text(t) => assert_eq!(t, "Rust is a systems programming language."),
_ => panic!("expected text"),
}
assert_eq!(msgs[4].role, Role::Assistant);
match &msgs[4].content {
Content::Text(t) => assert_eq!(t, "Here is what I found about Rust."),
_ => panic!("expected text"),
}
}
#[actix_web::test]
async fn openai_config_registers_models_and_completion_routes() {
let app = actix_web::test::init_service(
App::new().service(web::scope("/openai/v1").configure(super::config)),
)
.await;
for (method, uri) in [
("GET", "/openai/v1/models"),
("POST", "/openai/v1/chat/completions"),
("POST", "/openai/v1/responses"),
] {
let req = match method {
"GET" => actix_web::test::TestRequest::get().uri(uri).to_request(),
"POST" => actix_web::test::TestRequest::post().uri(uri).to_request(),
_ => unreachable!("unexpected HTTP method"),
};
let resp = actix_web::test::call_service(&app, req).await;
assert_ne!(
resp.status(),
StatusCode::NOT_FOUND,
"expected route to be registered: {uri}"
);
}
}