mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
* test(rust): group cache tests under cache/ and fold test_ocr.py into ocr/ The two failure cases in test_ocr.py duplicated the upstream-500 and timeout rows of PUBLIC_FAILURES, so only the file-input encoding case moves to ocr/test_requests.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(rust): split the response cache suite into one file per backend test_response_cache.py grew to 2400 lines. Each backend now has its own file, shared fixtures live in cache/conftest.py and shared helpers in support/cache.py. The helpers alias the private native test handles once, dropping the per-call reportPrivateUsage hits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * split tokenizer test * test(core): consolidate route integration tests under tests/ with rstest and wiremock Moves the public-API OCR route tests out of src/ocr/route.rs and document.rs into tests/ocr/, split per provider plus lifecycle, machine, and document tests, merging the duplicated pairs. Messages, audio transcription, and chat completions share one wiremock-based upstream and recording secret source in tests/support, and gain table-driven cases for auth, routing, upstream errors, streaming, and declines. Tests of litellm-llms items move to that crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(messages): keep the stream relay test independent of the stream head contents The stream head carries no headers on main, so the relay test asserts the open-then-deliver order and the relayed body instead of header hand-off. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Yujong Lee <yujong@berri.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
2.8 KiB
Rust
79 lines
2.8 KiB
Rust
use std::time::Duration;
|
|
|
|
use litellm_llms::base_llm::ocr::{error::Error, handler::read_response_bytes};
|
|
use rstest::rstest;
|
|
use tokio::{
|
|
io::{AsyncReadExt, AsyncWriteExt},
|
|
net::TcpListener,
|
|
};
|
|
|
|
/// Answers one request with raw `response` bytes and then holds the connection open, so a
|
|
/// read that waits for the rest of an oversized body hangs instead of passing.
|
|
async fn read_bounded(response: String, limit: usize) -> Result<bytes::Bytes, Error> {
|
|
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let address = listener.local_addr().unwrap();
|
|
let server = tokio::spawn(async move {
|
|
let (mut socket, _) = listener.accept().await.unwrap();
|
|
let mut request = [0; 4096];
|
|
assert!(socket.read(&mut request).await.unwrap() > 0);
|
|
socket.write_all(response.as_bytes()).await.unwrap();
|
|
std::future::pending::<()>().await;
|
|
});
|
|
let response = reqwest::Client::new()
|
|
.get(format!("http://{address}"))
|
|
.send()
|
|
.await
|
|
.unwrap();
|
|
let result =
|
|
tokio::time::timeout(Duration::from_secs(2), read_response_bytes(response, limit)).await;
|
|
server.abort();
|
|
result.expect("bounded reads must finish without waiting for the rest of an oversized body")
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::declared("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nabcdefgh")]
|
|
#[case::chunked(
|
|
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nabcd\r\n4\r\nefgh\r\n0\r\n\r\n"
|
|
)]
|
|
#[tokio::test]
|
|
async fn a_body_of_exactly_the_limit_is_read(#[case] response: &str) {
|
|
assert_eq!(read_bounded(response.into(), 8).await.unwrap(), "abcdefgh");
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::declared("HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\n")]
|
|
#[case::chunked("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nabcd\r\n5\r\nefghi\r\n")]
|
|
#[tokio::test]
|
|
async fn a_body_over_the_limit_is_rejected(#[case] response: &str) {
|
|
assert!(matches!(
|
|
read_bounded(response.into(), 8).await,
|
|
Err(Error::TooLarge { limit: 8 })
|
|
));
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::declared("Content-Length: 1000000")]
|
|
#[case::chunked("Transfer-Encoding: chunked")]
|
|
#[tokio::test]
|
|
async fn an_oversized_error_keeps_its_status_and_a_bounded_body_without_draining(
|
|
#[case] headers: &str,
|
|
) {
|
|
let prefix = "x".repeat(4096);
|
|
let body = match headers.starts_with("Transfer") {
|
|
true => format!("{:x}\r\n{prefix}\r\n", prefix.len()),
|
|
false => prefix.clone(),
|
|
};
|
|
|
|
let error = read_bounded(
|
|
format!("HTTP/1.1 429 Too Many Requests\r\n{headers}\r\n\r\n{body}"),
|
|
prefix.len(),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
let Error::Transport(litellm_http::transport::Error::Http { status, body }) = error else {
|
|
panic!("unexpected error: {error}");
|
|
};
|
|
assert_eq!(status, 429);
|
|
assert_eq!(body, prefix);
|
|
}
|