test(core): move route tests to integration suite

This commit is contained in:
Yujong Lee 2026-09-08 14:00:00 -07:00
parent 277a71cd30
commit 3406ca1e11
16 changed files with 112 additions and 67 deletions

View file

@ -32,6 +32,3 @@ pub async fn audio_transcription(request: AudioTranscriptionRequest<'_>) -> Resu
.await
.into_result()
}
#[cfg(test)]
mod tests;

View file

@ -93,7 +93,7 @@ pub(super) async fn execute_settled_request(
/// second kind has already been billed, and a host that keeps a reference
/// implementation must not retry those, so collapse them to one variant that
/// can only mean the provider was already called.
pub(super) fn as_response_error(err: Error) -> Error {
pub fn as_response_error(err: Error) -> Error {
match err {
already @ (Error::InvalidResponse(_) | Error::Http { .. }) => already,
other => Error::InvalidResponse(other.to_string()),
@ -101,7 +101,7 @@ pub(super) fn as_response_error(err: Error) -> Error {
}
#[cfg(feature = "bedrock-auth")]
pub(super) async fn signed_headers(
pub async fn signed_headers(
request: &ProviderChatCompletionsRequest,
body: &[u8],
) -> Result<Vec<(String, String)>, Error> {
@ -158,7 +158,7 @@ pub(super) async fn signed_headers(
}
#[cfg(not(feature = "bedrock-auth"))]
pub(super) async fn signed_headers(
pub async fn signed_headers(
request: &ProviderChatCompletionsRequest,
_body: &[u8],
) -> Result<Vec<(String, String)>, Error> {

View file

@ -20,6 +20,7 @@ pub mod types;
use serde_json::{Map, Value};
use handler::execute_chat_completions_provider_call;
pub use handler::{as_response_error, signed_headers};
use request::{parse_messages, resolve_provider_config, resolve_request};
use types::{ChatCompletionsRequest, ChatCompletionsResponse};
@ -156,6 +157,3 @@ pub fn chat_completions_decline_reason(
.unsupported_reason(&messages, optional_params)
.map(|reason| reason.0)
}
#[cfg(test)]
mod tests;

View file

@ -37,7 +37,7 @@ pub(super) fn parse_messages(messages: Value) -> Result<Vec<ChatMessage>, Error>
.map_err(|err| Error::InvalidRequest(format!("invalid chat completions messages: {err}")))
}
pub(super) fn resolve_request(
pub fn resolve_request(
request: ChatCompletionsRequest<'_>,
) -> Result<ResolvedChatCompletionsRequest<'_>, Error> {
let (model, config) = resolve_provider_config(request.model, request.custom_llm_provider)?;
@ -116,7 +116,7 @@ fn validate_environment(
Ok((headers, auth))
}
pub(super) fn build_provider_request(
pub fn build_provider_request(
request: ResolvedChatCompletionsRequest<'_>,
) -> Result<ProviderChatCompletionsRequest, Error> {
let (headers, auth) = validate_environment(&request, &request.model, request.config)?;

View file

@ -22,7 +22,7 @@ pub struct ChatCompletionsRequest<'a> {
pub timeout: Option<Duration>,
}
pub(super) struct ResolvedChatCompletionsRequest<'a> {
pub struct ResolvedChatCompletionsRequest<'a> {
pub(super) model: String,
pub(super) config: &'static dyn ChatCompletionsProviderConfig,
pub(super) messages: Vec<ChatMessage>,
@ -33,16 +33,16 @@ pub(super) struct ResolvedChatCompletionsRequest<'a> {
pub(super) timeout: Option<Duration>,
}
pub(super) struct ProviderChatCompletionsRequest {
pub(super) model: String,
pub(super) config: &'static dyn ChatCompletionsProviderConfig,
pub(super) url: String,
pub(super) body: Value,
pub(super) upstream_headers: Vec<(String, String)>,
pub(super) auth: ChatCompletionsAuth,
pub struct ProviderChatCompletionsRequest {
pub model: String,
pub config: &'static dyn ChatCompletionsProviderConfig,
pub url: String,
pub body: Value,
pub upstream_headers: Vec<(String, String)>,
pub auth: ChatCompletionsAuth,
#[cfg_attr(not(feature = "bedrock-auth"), allow(dead_code))]
pub(super) optional_params: Map<String, Value>,
pub(super) timeout: Option<Duration>,
pub optional_params: Map<String, Value>,
pub timeout: Option<Duration>,
}
/// The provider-shaped request body a config produces. Named rather than a bare

View file

@ -6,12 +6,12 @@ use serde_json::{Map, Value};
use super::transformation::AnthropicMessagesProviderConfig;
pub(super) use crate::http_utils::{has_bearer_auth, has_header, truncate_error_body};
pub use crate::http_utils::{has_bearer_auth, has_header, truncate_error_body};
const HEADER_CONTEXT: &str = "messages";
#[tracing::instrument(target = "litellm::function_trace", level = "trace", skip_all)]
pub(super) fn messages_provider_config(
pub fn messages_provider_config(
provider: &str,
) -> Option<&'static dyn AnthropicMessagesProviderConfig> {
match provider {
@ -21,7 +21,7 @@ pub(super) fn messages_provider_config(
}
}
pub(super) fn string_headers(
pub fn string_headers(
extra_headers: Option<Map<String, Value>>,
) -> Result<Vec<(String, String)>, Error> {
shared_string_headers(HEADER_CONTEXT, extra_headers)

View file

@ -9,7 +9,7 @@
use crate::Error;
use crate::constants::ANTHROPIC_MESSAGES_PROVIDER;
mod client;
mod common_utils;
pub mod common_utils;
mod handler;
pub mod lifecycle;
pub mod request;
@ -64,6 +64,3 @@ pub async fn messages_stream(request: MessagesRequest) -> Result<StreamingCall,
)
.await
}
#[cfg(test)]
mod tests;

View file

@ -214,7 +214,3 @@ impl ChatCompletionsProviderConfig for AnthropicChatCompletionsConfig {
})
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;

View file

@ -302,7 +302,3 @@ impl ChatCompletionsProviderConfig for BedrockChatCompletionsConfig {
})
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;

View file

@ -1,4 +1 @@
pub mod transformation;
#[cfg(test)]
mod tests;

View file

@ -1,6 +1,12 @@
use super::*;
use crate::Error;
use serde_json::json;
use litellm_core::Error;
use litellm_core::chat_completions::transformation::{
ChatCompletionsAuth, ChatCompletionsProviderConfig, Unsupported,
};
use litellm_core::chat_completions::types::{
ChatCompletionsResponse, ChatMessage, ProviderChatResponseData,
};
use litellm_core::providers::anthropic::chat_completions::transformation::*;
use serde_json::{Map, Value, json};
fn messages(value: Value) -> Vec<ChatMessage> {
serde_json::from_value(value).expect("valid messages")

View file

@ -1,20 +1,31 @@
#![recursion_limit = "256"]
#[cfg(feature = "bedrock-auth")]
use std::io::{Read, Write};
#[cfg(feature = "bedrock-auth")]
use std::net::TcpListener;
#[cfg(feature = "bedrock-auth")]
use std::sync::{Arc, Mutex};
#[cfg(feature = "bedrock-auth")]
use std::thread;
use serde_json::{Map, json};
use super::types::AudioTranscriptionRequest;
use super::{AudioRoute, AudioRouteRequest, DefaultAudioServices, audio_transcription};
use crate::Error;
use crate::integrations::custom_guardrail::{
use litellm_core::Error;
#[cfg(feature = "bedrock-auth")]
use litellm_core::audio_transcription::audio_transcription;
#[cfg(feature = "bedrock-auth")]
use litellm_core::audio_transcription::types::AudioTranscriptionRequest;
use litellm_core::audio_transcription::{AudioRoute, AudioRouteRequest, DefaultAudioServices};
#[cfg(feature = "bedrock-auth")]
use litellm_core::integrations::custom_guardrail::{
CustomGuardrail, GuardrailContext, GuardrailDecision, GuardrailEventHook, GuardrailFuture,
GuardrailRequest,
};
use crate::lifecycle::{ExecutedCall, RouteProjection};
use litellm_core::lifecycle::{ExecutedCall, RouteProjection};
#[tokio::test]
#[cfg(feature = "bedrock-auth")]
async fn bedrock_request_is_signed_and_contains_audio() {
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
let address = listener.local_addr().expect("address");
@ -56,10 +67,12 @@ async fn bedrock_request_is_signed_and_contains_audio() {
server.join().expect("server");
}
#[cfg(feature = "bedrock-auth")]
struct ReplacingGuardrail {
calls: Mutex<Vec<&'static str>>,
}
#[cfg(feature = "bedrock-auth")]
impl CustomGuardrail for ReplacingGuardrail {
fn guardrail_name(&self) -> &str {
"audio-test"
@ -96,6 +109,7 @@ impl CustomGuardrail for ReplacingGuardrail {
}
#[tokio::test]
#[cfg(feature = "bedrock-auth")]
async fn route_owns_guardrail_provider_and_terminal_sequence() {
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
let address = listener.local_addr().expect("address");

View file

@ -1,6 +1,14 @@
use super::*;
use crate::Error;
use serde_json::json;
#![cfg(feature = "bedrock-auth")]
use litellm_core::Error;
use litellm_core::chat_completions::transformation::{
ChatCompletionsAuth, ChatCompletionsProviderConfig, Unsupported,
};
use litellm_core::chat_completions::types::{
ChatCompletionsResponse, ChatMessage, ProviderChatResponseData,
};
use litellm_core::providers::bedrock::chat_completions::transformation::*;
use serde_json::{Map, Value, json};
fn messages(value: Value) -> Vec<ChatMessage> {
serde_json::from_value(value).expect("valid messages")
@ -37,6 +45,10 @@ fn reason(msgs: Value, opts: Value) -> Option<Unsupported> {
#[test]
fn builds_the_converse_body_python_builds() {
assert_eq!(
BEDROCK_CHAT_COMPLETIONS_CONFIG.request_body_behavior(),
litellm_core::lifecycle::RequestBodyBehavior::SERIALIZED_AT_BUILD
);
let body = transform(
json!([
{"role": "system", "content": "be terse"},
@ -553,7 +565,7 @@ fn leaves_a_complete_converse_url_untouched() {
#[test]
fn host_supplied_credentials_outrank_ambient_profile_and_role_state() {
use crate::providers::bedrock::aws_base::host_supplied_credentials;
use litellm_core::providers::bedrock::aws_base::host_supplied_credentials;
let supplied = params(json!({
"aws_access_key_id": "AKIAHOST",
@ -561,9 +573,15 @@ fn host_supplied_credentials_outrank_ambient_profile_and_role_state() {
"aws_session_token": "hosttoken"
}));
let credentials = host_supplied_credentials(&supplied).expect("host credentials");
let different_secret = host_supplied_credentials(&params(json!({
"aws_access_key_id": "AKIAHOST",
"aws_secret_access_key": "different",
"aws_session_token": "hosttoken"
})))
.expect("credentials with a different secret");
assert_eq!(credentials.access_key_id(), "AKIAHOST");
assert_eq!(credentials.secret_access_key(), "hostsecret");
assert_eq!(credentials.session_token(), Some("hosttoken"));
assert_ne!(credentials, different_secret);
// Without a full static pair there is nothing to honor, so the core falls
// back to deriving credentials itself.

View file

@ -1,10 +1,12 @@
use serde_json::{Map, Value, json};
use crate::error::Error;
use litellm_core::error::Error;
use super::request::{build_provider_request, resolve_request};
use super::transformation::ChatCompletionsAuth;
use super::types::{ChatCompletionsRequest, ProviderChatCompletionsRequest};
use litellm_core::chat_completions::request::{build_provider_request, resolve_request};
use litellm_core::chat_completions::transformation::ChatCompletionsAuth;
use litellm_core::chat_completions::types::{
ChatCompletionsRequest, ProviderChatCompletionsRequest,
};
fn build_chat_completions_request(
request: ChatCompletionsRequest<'_>,
@ -54,6 +56,10 @@ fn resolves_the_provider_from_the_model_prefix() {
assert_eq!(built.model, "claude-sonnet-4-5");
assert_eq!(built.url, "https://api.anthropic.com/v1/messages");
assert_eq!(built.body["model"], json!("claude-sonnet-4-5"));
assert_eq!(
built.config.request_body_behavior(),
litellm_core::lifecycle::RequestBodyBehavior::STRUCTURED_AT_SEND
);
}
#[test]
@ -325,7 +331,7 @@ async fn a_forwarded_client_header_does_not_enter_the_bedrock_signature() {
json!("abc-123"),
)]));
let built = build_chat_completions_request(call).expect("builds");
let signed = super::handler::signed_headers(&built, br#"{"a":1}"#)
let signed = litellm_core::chat_completions::signed_headers(&built, br#"{"a":1}"#)
.await
.expect("signs");
@ -376,7 +382,7 @@ async fn a_forwarded_header_the_signer_computes_declines_to_python() {
call.api_key = None;
call.extra_headers = Some(Map::from_iter([(forwarded.to_string(), json!("forged"))]));
let built = build_chat_completions_request(call).expect("builds");
let error = super::handler::signed_headers(&built, br#"{"a":1}"#)
let error = litellm_core::chat_completions::signed_headers(&built, br#"{"a":1}"#)
.await
.expect_err("{forwarded} should decline instead of being signed");
assert!(
@ -492,7 +498,9 @@ fn decline_reason(
Value::Object(map) => map,
other => panic!("params must be an object, got {other}"),
};
super::chat_completions_decline_reason(model, provider, messages, &params)
litellm_core::chat_completions::chat_completions_decline_reason(
model, provider, messages, &params,
)
}
#[test]
@ -595,7 +603,7 @@ mod round_trip {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use crate::chat_completions::chat_completions;
use litellm_core::chat_completions::chat_completions;
async fn read_http_request(socket: &mut TcpStream) -> String {
let mut request = Vec::new();
@ -800,7 +808,7 @@ mod round_trip {
#[test]
fn response_errors_collapse_to_one_variant_that_can_only_mean_already_sent() {
use crate::chat_completions::handler::as_response_error;
use litellm_core::chat_completions::as_response_error;
for original in [
Error::MissingField("usage"),

View file

@ -4,13 +4,14 @@ use serde_json::{Map, Value, json};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use crate::error::Error;
use litellm_core::error::Error;
use super::common_utils::{
use litellm_core::messages::common_utils::{
has_bearer_auth, has_header, messages_provider_config, string_headers, truncate_error_body,
};
use super::messages;
use super::types::MessagesRequest;
use litellm_core::messages::messages;
use litellm_core::messages::request::build_endpoint;
use litellm_core::messages::types::{MessagesOptions, MessagesRequest};
async fn read_http_request(socket: &mut TcpStream) -> String {
let mut request = Vec::new();
@ -60,6 +61,23 @@ fn provider_config_resolves_anthropic_and_azure_ai() {
assert!(messages_provider_config("openai").is_none());
}
#[test]
fn messages_capture_the_structured_body_during_request_building() {
let endpoint = build_endpoint(MessagesOptions {
model: "anthropic/claude-sonnet-4-5".into(),
api_key: Some("test-key".into()),
api_base: None,
custom_llm_provider: None,
extra_headers: None,
timeout: None,
})
.expect("endpoint");
assert_eq!(
endpoint.request_body_behavior(),
litellm_core::lifecycle::RequestBodyBehavior::STRUCTURED_AT_BUILD
);
}
#[test]
fn truncate_error_body_caps_long_payloads() {
let body = "x".repeat(400);

View file

@ -1,8 +1,8 @@
use rstest::{fixture, rstest};
use serde_json::{Value, json};
use super::transformation::*;
use crate::ocr::transformation::OcrProviderConfig;
use litellm_core::ocr::transformation::OcrProviderConfig;
use litellm_core::providers::reducto::ocr::transformation::*;
#[fixture]
fn parse_response() -> Value {