From 3406ca1e1167c522c5cdc7c8a86c45c7ba423696 Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Tue, 8 Sep 2026 14:00:00 -0700 Subject: [PATCH] test(core): move route tests to integration suite --- .../core/src/audio_transcription/mod.rs | 3 -- .../core/src/chat_completions/handler.rs | 6 ++-- .../crates/core/src/chat_completions/mod.rs | 4 +-- .../core/src/chat_completions/request.rs | 4 +-- .../crates/core/src/chat_completions/types.rs | 20 ++++++------- .../crates/core/src/messages/common_utils.rs | 6 ++-- litellm-rust/crates/core/src/messages/mod.rs | 5 +--- .../chat_completions/transformation.rs | 4 --- .../chat_completions/transformation.rs | 4 --- .../core/src/providers/reducto/ocr/mod.rs | 3 -- .../anthropic_chat_completions.rs} | 12 ++++++-- .../tests.rs => tests/audio_transcription.rs} | 24 ++++++++++++---- .../bedrock_chat_completions.rs} | 28 +++++++++++++++---- .../tests.rs => tests/chat_completions.rs} | 26 +++++++++++------ .../messages/tests.rs => tests/messages.rs} | 26 ++++++++++++++--- .../ocr/tests.rs => tests/reducto_ocr.rs} | 4 +-- 16 files changed, 112 insertions(+), 67 deletions(-) rename litellm-rust/crates/core/{src/providers/anthropic/chat_completions/tests.rs => tests/anthropic_chat_completions.rs} (97%) rename litellm-rust/crates/core/{src/audio_transcription/tests.rs => tests/audio_transcription.rs} (89%) rename litellm-rust/crates/core/{src/providers/bedrock/chat_completions/tests.rs => tests/bedrock_chat_completions.rs} (95%) rename litellm-rust/crates/core/{src/chat_completions/tests.rs => tests/chat_completions.rs} (96%) rename litellm-rust/crates/core/{src/messages/tests.rs => tests/messages.rs} (95%) rename litellm-rust/crates/core/{src/providers/reducto/ocr/tests.rs => tests/reducto_ocr.rs} (98%) diff --git a/litellm-rust/crates/core/src/audio_transcription/mod.rs b/litellm-rust/crates/core/src/audio_transcription/mod.rs index 40af2e3c86d..dab55f06d7d 100644 --- a/litellm-rust/crates/core/src/audio_transcription/mod.rs +++ b/litellm-rust/crates/core/src/audio_transcription/mod.rs @@ -32,6 +32,3 @@ pub async fn audio_transcription(request: AudioTranscriptionRequest<'_>) -> Resu .await .into_result() } - -#[cfg(test)] -mod tests; diff --git a/litellm-rust/crates/core/src/chat_completions/handler.rs b/litellm-rust/crates/core/src/chat_completions/handler.rs index 7c5609a6a11..301cd5b81f9 100644 --- a/litellm-rust/crates/core/src/chat_completions/handler.rs +++ b/litellm-rust/crates/core/src/chat_completions/handler.rs @@ -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, 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, Error> { diff --git a/litellm-rust/crates/core/src/chat_completions/mod.rs b/litellm-rust/crates/core/src/chat_completions/mod.rs index 1611f014961..5f5c6bf611f 100644 --- a/litellm-rust/crates/core/src/chat_completions/mod.rs +++ b/litellm-rust/crates/core/src/chat_completions/mod.rs @@ -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; diff --git a/litellm-rust/crates/core/src/chat_completions/request.rs b/litellm-rust/crates/core/src/chat_completions/request.rs index 0449beec83e..09b49721a02 100644 --- a/litellm-rust/crates/core/src/chat_completions/request.rs +++ b/litellm-rust/crates/core/src/chat_completions/request.rs @@ -37,7 +37,7 @@ pub(super) fn parse_messages(messages: Value) -> Result, Error> .map_err(|err| Error::InvalidRequest(format!("invalid chat completions messages: {err}"))) } -pub(super) fn resolve_request( +pub fn resolve_request( request: ChatCompletionsRequest<'_>, ) -> Result, 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 { let (headers, auth) = validate_environment(&request, &request.model, request.config)?; diff --git a/litellm-rust/crates/core/src/chat_completions/types.rs b/litellm-rust/crates/core/src/chat_completions/types.rs index 3cb5ad9cf6b..3945408b350 100644 --- a/litellm-rust/crates/core/src/chat_completions/types.rs +++ b/litellm-rust/crates/core/src/chat_completions/types.rs @@ -22,7 +22,7 @@ pub struct ChatCompletionsRequest<'a> { pub timeout: Option, } -pub(super) struct ResolvedChatCompletionsRequest<'a> { +pub struct ResolvedChatCompletionsRequest<'a> { pub(super) model: String, pub(super) config: &'static dyn ChatCompletionsProviderConfig, pub(super) messages: Vec, @@ -33,16 +33,16 @@ pub(super) struct ResolvedChatCompletionsRequest<'a> { pub(super) timeout: Option, } -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, - pub(super) timeout: Option, + pub optional_params: Map, + pub timeout: Option, } /// The provider-shaped request body a config produces. Named rather than a bare diff --git a/litellm-rust/crates/core/src/messages/common_utils.rs b/litellm-rust/crates/core/src/messages/common_utils.rs index 8f0f6652fa4..6b688a3251e 100644 --- a/litellm-rust/crates/core/src/messages/common_utils.rs +++ b/litellm-rust/crates/core/src/messages/common_utils.rs @@ -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>, ) -> Result, Error> { shared_string_headers(HEADER_CONTEXT, extra_headers) diff --git a/litellm-rust/crates/core/src/messages/mod.rs b/litellm-rust/crates/core/src/messages/mod.rs index 4e52bf62f16..d56f5d36ddd 100644 --- a/litellm-rust/crates/core/src/messages/mod.rs +++ b/litellm-rust/crates/core/src/messages/mod.rs @@ -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 Vec { serde_json::from_value(value).expect("valid messages") diff --git a/litellm-rust/crates/core/src/audio_transcription/tests.rs b/litellm-rust/crates/core/tests/audio_transcription.rs similarity index 89% rename from litellm-rust/crates/core/src/audio_transcription/tests.rs rename to litellm-rust/crates/core/tests/audio_transcription.rs index c7a354a89f1..9f9b719e1a6 100644 --- a/litellm-rust/crates/core/src/audio_transcription/tests.rs +++ b/litellm-rust/crates/core/tests/audio_transcription.rs @@ -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>, } +#[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"); diff --git a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs b/litellm-rust/crates/core/tests/bedrock_chat_completions.rs similarity index 95% rename from litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs rename to litellm-rust/crates/core/tests/bedrock_chat_completions.rs index c86f061b9ca..8787c710bad 100644 --- a/litellm-rust/crates/core/src/providers/bedrock/chat_completions/tests.rs +++ b/litellm-rust/crates/core/tests/bedrock_chat_completions.rs @@ -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 { serde_json::from_value(value).expect("valid messages") @@ -37,6 +45,10 @@ fn reason(msgs: Value, opts: Value) -> Option { #[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(¶ms(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. diff --git a/litellm-rust/crates/core/src/chat_completions/tests.rs b/litellm-rust/crates/core/tests/chat_completions.rs similarity index 96% rename from litellm-rust/crates/core/src/chat_completions/tests.rs rename to litellm-rust/crates/core/tests/chat_completions.rs index afbd698d5dc..3471d2a8a27 100644 --- a/litellm-rust/crates/core/src/chat_completions/tests.rs +++ b/litellm-rust/crates/core/tests/chat_completions.rs @@ -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, ¶ms) + litellm_core::chat_completions::chat_completions_decline_reason( + model, provider, messages, ¶ms, + ) } #[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"), diff --git a/litellm-rust/crates/core/src/messages/tests.rs b/litellm-rust/crates/core/tests/messages.rs similarity index 95% rename from litellm-rust/crates/core/src/messages/tests.rs rename to litellm-rust/crates/core/tests/messages.rs index 38f0e20f7d3..584023636aa 100644 --- a/litellm-rust/crates/core/src/messages/tests.rs +++ b/litellm-rust/crates/core/tests/messages.rs @@ -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); diff --git a/litellm-rust/crates/core/src/providers/reducto/ocr/tests.rs b/litellm-rust/crates/core/tests/reducto_ocr.rs similarity index 98% rename from litellm-rust/crates/core/src/providers/reducto/ocr/tests.rs rename to litellm-rust/crates/core/tests/reducto_ocr.rs index 2b66d058b5d..5f17925ee07 100644 --- a/litellm-rust/crates/core/src/providers/reducto/ocr/tests.rs +++ b/litellm-rust/crates/core/tests/reducto_ocr.rs @@ -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 {