#[cfg(test)]
mod tests {
use crate::core::language_models::BaseLanguageModel;
use crate::schema::Message;
use serde_json::json;
use super::super::model::ResponsesModel;
use super::super::types::*;
#[test]
fn test_builtin_tool_to_api_value() {
let web_search = BuiltinTool::WebSearch;
assert_eq!(web_search.to_api_value(), json!({"type": "web_search"}));
let file_search = BuiltinTool::FileSearch {
vector_store_ids: vec!["vs_123".to_string()],
};
assert_eq!(
file_search.to_api_value(),
json!({"type": "file_search", "vector_store_ids": ["vs_123"]})
);
let code_interp = BuiltinTool::CodeInterpreter;
assert_eq!(
code_interp.to_api_value(),
json!({"type": "code_interpreter"})
);
let computer = BuiltinTool::ComputerUse {
display_width: Some(1024),
display_height: Some(768),
};
let cv = computer.to_api_value();
assert_eq!(cv["type"], "computer_use");
assert_eq!(cv["display_width"], 1024);
assert_eq!(cv["display_height"], 768);
let computer_no_dims = BuiltinTool::ComputerUse {
display_width: None,
display_height: None,
};
let cv2 = computer_no_dims.to_api_value();
assert_eq!(cv2["type"], "computer_use");
assert!(cv2.get("display_width").is_none());
assert!(cv2.get("display_height").is_none());
}
#[test]
fn test_config_builder() {
let config = ResponsesConfig::new("sk-test")
.with_model("gpt-4o")
.with_base_url("https://api.openai.com/v1")
.with_temperature(0.7)
.with_max_tokens(1024)
.with_builtin_tool(BuiltinTool::WebSearch)
.with_builtin_tool(BuiltinTool::CodeInterpreter);
assert_eq!(config.api_key, "sk-test");
assert_eq!(config.model, "gpt-4o");
assert_eq!(config.base_url, "https://api.openai.com/v1");
assert_eq!(config.temperature, Some(0.7));
assert_eq!(config.max_tokens, Some(1024));
assert_eq!(config.builtin_tools.len(), 2);
}
#[test]
fn test_message_to_input_system() {
let msg = Message::system("You are helpful.");
let val = ResponsesModel::message_to_input(&msg);
assert_eq!(val["role"], "system");
assert_eq!(val["content"], "You are helpful.");
}
#[test]
fn test_message_to_input_human() {
let msg = Message::human("Hello!");
let val = ResponsesModel::message_to_input(&msg);
assert_eq!(val["role"], "user");
assert_eq!(val["content"], "Hello!");
}
#[test]
fn test_message_to_input_ai() {
let msg = Message::ai("Hi there!");
let val = ResponsesModel::message_to_input(&msg);
assert_eq!(val["role"], "assistant");
assert_eq!(val["content"], "Hi there!");
}
#[test]
fn test_message_to_input_tool() {
let msg = Message::tool("call_123", "result data");
let val = ResponsesModel::message_to_input(&msg);
assert_eq!(val["type"], "function_call_output");
assert_eq!(val["call_id"], "call_123");
assert_eq!(val["output"], "result data");
}
#[test]
fn test_build_request_body_basic() {
let config = ResponsesConfig::new("sk-test").with_model("gpt-4o");
let model = ResponsesModel::new(config);
let messages = vec![
Message::system("Be helpful."),
Message::human("What is 2+2?"),
];
let body = model.build_request_body(messages, false);
assert_eq!(body["model"], "gpt-4o");
assert_eq!(body["stream"], false);
let input = body["input"].as_array().unwrap();
assert_eq!(input.len(), 2);
assert_eq!(input[0]["role"], "system");
assert_eq!(input[1]["role"], "user");
}
#[test]
fn test_build_request_body_with_tools() {
let config = ResponsesConfig::new("sk-test")
.with_model("gpt-4o")
.with_builtin_tool(BuiltinTool::WebSearch)
.with_builtin_tool(BuiltinTool::FileSearch {
vector_store_ids: vec!["vs_abc".to_string()],
});
let model = ResponsesModel::new(config);
let messages = vec![Message::human("Search the web")];
let body = model.build_request_body(messages, false);
let tools = body["tools"].as_array().unwrap();
assert_eq!(tools.len(), 2);
assert_eq!(tools[0]["type"], "web_search");
assert_eq!(tools[1]["type"], "file_search");
}
#[test]
fn test_build_request_body_with_options() {
let config = ResponsesConfig::new("sk-test")
.with_model("gpt-4o")
.with_temperature(0.5)
.with_max_tokens(2048);
let model = ResponsesModel::new(config);
let messages = vec![Message::human("Hi")];
let body = model.build_request_body(messages, true);
assert_eq!(body["temperature"], 0.5);
assert_eq!(body["max_output_tokens"], 2048);
assert_eq!(body["stream"], true);
}
#[test]
fn test_parse_response_text_only() {
let api_response = ResponsesApiResponse {
id: "resp_001".to_string(),
object: Some("response".to_string()),
model: Some("gpt-4o".to_string()),
output: vec![ResponsesOutputItem::Message(ResponsesMessage {
id: Some("msg_001".to_string()),
role: Some("assistant".to_string()),
content: vec![ResponsesContentPart::OutputText(ResponsesOutputText {
text: "Hello, world!".to_string(),
annotations: vec![],
})],
status: Some("completed".to_string()),
})],
usage: Some(ResponsesUsage {
input_tokens: 10,
output_tokens: 5,
total_tokens: 15,
}),
};
let result = ResponsesModel::parse_response(api_response).unwrap();
assert_eq!(result.content, "Hello, world!");
assert_eq!(result.model, "gpt-4o");
assert!(result.tool_calls.is_none());
let usage = result.token_usage.unwrap();
assert_eq!(usage.prompt_tokens, 10);
assert_eq!(usage.completion_tokens, 5);
assert_eq!(usage.total_tokens, 15);
}
#[test]
fn test_parse_response_with_web_search() {
let api_response = ResponsesApiResponse {
id: "resp_002".to_string(),
object: Some("response".to_string()),
model: Some("gpt-4o".to_string()),
output: vec![
ResponsesOutputItem::WebSearchCall(ResponsesWebSearchCall {
id: "ws_001".to_string(),
status: "completed".to_string(),
query: Some("Rust programming".to_string()),
}),
ResponsesOutputItem::Message(ResponsesMessage {
id: Some("msg_002".to_string()),
role: Some("assistant".to_string()),
content: vec![ResponsesContentPart::OutputText(ResponsesOutputText {
text: "Rust is a systems language.".to_string(),
annotations: vec![],
})],
status: Some("completed".to_string()),
}),
],
usage: None,
};
let result = ResponsesModel::parse_response(api_response).unwrap();
assert_eq!(result.content, "Rust is a systems language.");
let tc = result.tool_calls.unwrap();
assert_eq!(tc.len(), 1);
assert_eq!(tc[0].id, "ws_001");
assert_eq!(tc[0].function.name, "web_search");
}
#[test]
fn test_parse_response_with_code_interpreter() {
let api_response = ResponsesApiResponse {
id: "resp_003".to_string(),
object: Some("response".to_string()),
model: Some("gpt-4o".to_string()),
output: vec![
ResponsesOutputItem::CodeInterpreterCall(ResponsesCodeInterpreterCall {
id: "ci_001".to_string(),
code: Some("print(2+2)".to_string()),
results: Some(vec![json!({"type": "text", "text": "4"})]),
status: Some("completed".to_string()),
}),
ResponsesOutputItem::Message(ResponsesMessage {
id: Some("msg_003".to_string()),
role: Some("assistant".to_string()),
content: vec![ResponsesContentPart::OutputText(ResponsesOutputText {
text: "The answer is 4.".to_string(),
annotations: vec![],
})],
status: Some("completed".to_string()),
}),
],
usage: None,
};
let result = ResponsesModel::parse_response(api_response).unwrap();
assert_eq!(result.content, "The answer is 4.");
let tc = result.tool_calls.unwrap();
assert_eq!(tc[0].function.name, "code_interpreter");
}
#[test]
fn test_parse_response_refusal() {
let api_response = ResponsesApiResponse {
id: "resp_004".to_string(),
object: Some("response".to_string()),
model: Some("gpt-4o".to_string()),
output: vec![ResponsesOutputItem::Message(ResponsesMessage {
id: Some("msg_004".to_string()),
role: Some("assistant".to_string()),
content: vec![ResponsesContentPart::Refusal(ResponsesRefusal {
refusal: "I cannot do that.".to_string(),
})],
status: Some("completed".to_string()),
})],
usage: None,
};
let result = ResponsesModel::parse_response(api_response).unwrap();
assert!(result.content.contains("[Refusal: I cannot do that.]"));
}
#[test]
fn test_parse_response_multiple_content_parts() {
let api_response = ResponsesApiResponse {
id: "resp_005".to_string(),
object: Some("response".to_string()),
model: Some("gpt-4o".to_string()),
output: vec![ResponsesOutputItem::Message(ResponsesMessage {
id: Some("msg_005".to_string()),
role: Some("assistant".to_string()),
content: vec![
ResponsesContentPart::OutputText(ResponsesOutputText {
text: "Part 1".to_string(),
annotations: vec![],
}),
ResponsesContentPart::OutputText(ResponsesOutputText {
text: "Part 2".to_string(),
annotations: vec![],
}),
],
status: Some("completed".to_string()),
})],
usage: None,
};
let result = ResponsesModel::parse_response(api_response).unwrap();
assert_eq!(result.content, "Part 1\nPart 2");
}
#[test]
fn test_error_display() {
let http_err = ResponsesError::Http("connection refused".to_string());
assert_eq!(format!("{}", http_err), "HTTP error: connection refused");
let api_err = ResponsesError::Api("rate limited".to_string());
assert_eq!(format!("{}", api_err), "API error: rate limited");
let parse_err = ResponsesError::Parse("invalid json".to_string());
assert_eq!(format!("{}", parse_err), "Parse error: invalid json");
}
#[test]
fn test_stream_event_deserialize_text_delta() {
let data = r#"{"type":"response.output_text.delta","delta":"Hello"}"#;
let event: ResponsesStreamEvent = serde_json::from_str(data).unwrap();
match event {
ResponsesStreamEvent::OutputTextDelta(delta) => {
assert_eq!(delta.delta, "Hello");
}
_ => panic!("Expected OutputTextDelta"),
}
}
#[test]
fn test_stream_event_deserialize_completed() {
let data = r#"{
"type": "response.completed",
"response": {
"id": "resp_001",
"model": "gpt-4o",
"output": [],
"usage": {"input_tokens": 5, "output_tokens": 3, "total_tokens": 8}
}
}"#;
let event: ResponsesStreamEvent = serde_json::from_str(data).unwrap();
match event {
ResponsesStreamEvent::Completed(completed) => {
assert_eq!(completed.response.id, "resp_001");
}
_ => panic!("Expected Completed"),
}
}
#[test]
fn test_with_builtin_tool_returns_new_instance() {
let config = ResponsesConfig::new("sk-test");
let model = ResponsesModel::new(config);
assert_eq!(model.config.builtin_tools.len(), 0);
let model2 = model.with_builtin_tool(BuiltinTool::WebSearch);
assert_eq!(model2.config.builtin_tools.len(), 1);
let model3 = model2.with_builtin_tool(BuiltinTool::CodeInterpreter);
assert_eq!(model3.config.builtin_tools.len(), 2);
}
#[test]
fn test_base_language_model_trait() {
let config = ResponsesConfig::new("sk-test")
.with_model("gpt-4o")
.with_temperature(0.7)
.with_max_tokens(512);
let model = ResponsesModel::new(config);
assert_eq!(model.model_name(), "gpt-4o");
assert_eq!(model.temperature(), Some(0.7));
assert_eq!(model.max_tokens(), Some(512));
assert!(model.get_num_tokens("hello world") > 0);
}
#[test]
fn test_with_temperature_and_max_tokens() {
let config = ResponsesConfig::new("sk-test");
let model = ResponsesModel::new(config);
assert_eq!(model.temperature(), None);
assert_eq!(model.max_tokens(), None);
let model2 = model.with_temperature(0.3);
assert_eq!(model2.temperature(), Some(0.3));
let model3 = model2.with_max_tokens(1024);
assert_eq!(model3.max_tokens(), Some(1024));
}
}