mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
test(ai-gateway): pin the crypto provider to ring and prove the dial installs it
The two tests that shipped with the fix both called ensure_crypto_provider themselves, so deleting the call from connect_upstream left the whole suite green, and swapping ring for aws-lc-rs did too. Adds an integration test, which gets its own process, that dials wss:// at a local plain-TCP listener through the public Responses WebSocket entrypoint and asserts an Err plus an installed provider. Without the install in the dial it panics with the original CryptoProvider message. A unit test now compares the installed provider's cipher suites and key-exchange groups against ring's, so the choice of backend is pinned rather than assumed. Also names tls12 in the workspace rustls features: it already arrives through reqwest and tokio-rustls, so the graph is unchanged, but a direct dependency should say it needs TLS 1.2 rather than inherit it.
This commit is contained in:
parent
c79d1d12ae
commit
2c08e7abf8
3 changed files with 81 additions and 5 deletions
|
|
@ -26,7 +26,7 @@ pythonize = "0.29.0"
|
|||
rand = "0.8"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "http2", "stream"] }
|
||||
rstest = "0.26.1"
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.10"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//! Installing from the dial rather than from a `main` also covers the `cdylib`
|
||||
//! the Python bridge loads, the tests, and the benches, none of which have one.
|
||||
//! ring is what reqwest already falls back to, so installing it changes no
|
||||
//! working path, and an embedder that installed its own provider first keeps it.
|
||||
//! working path, and whoever installs into this rustls build first still wins.
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
|
|
@ -38,13 +38,32 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustls::crypto::CryptoProvider;
|
||||
|
||||
use super::ensure_crypto_provider;
|
||||
|
||||
fn fingerprint(
|
||||
provider: &CryptoProvider,
|
||||
) -> (Vec<rustls::CipherSuite>, Vec<rustls::NamedGroup>) {
|
||||
(
|
||||
provider
|
||||
.cipher_suites
|
||||
.iter()
|
||||
.map(|suite| suite.suite())
|
||||
.collect(),
|
||||
provider
|
||||
.kx_groups
|
||||
.iter()
|
||||
.map(|group| group.name())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_config_builder_works_with_both_provider_features_enabled() {
|
||||
ensure_crypto_provider();
|
||||
|
||||
assert!(rustls::crypto::CryptoProvider::get_default().is_some());
|
||||
assert!(CryptoProvider::get_default().is_some());
|
||||
|
||||
let config = rustls::ClientConfig::builder()
|
||||
.with_root_certificates(rustls::RootCertStore::empty())
|
||||
|
|
@ -53,13 +72,29 @@ mod tests {
|
|||
assert!(!config.crypto_provider().cipher_suites.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installs_ring_rather_than_aws_lc_rs() {
|
||||
ensure_crypto_provider();
|
||||
|
||||
let installed = CryptoProvider::get_default().expect("a provider is installed");
|
||||
|
||||
assert_eq!(
|
||||
fingerprint(installed),
|
||||
fingerprint(&rustls::crypto::ring::default_provider())
|
||||
);
|
||||
assert_ne!(
|
||||
fingerprint(installed),
|
||||
fingerprint(&rustls::crypto::aws_lc_rs::default_provider())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_crypto_provider_is_idempotent() {
|
||||
ensure_crypto_provider();
|
||||
let first = rustls::crypto::CryptoProvider::get_default().cloned();
|
||||
let first = CryptoProvider::get_default().cloned();
|
||||
|
||||
ensure_crypto_provider();
|
||||
let second = rustls::crypto::CryptoProvider::get_default().cloned();
|
||||
let second = CryptoProvider::get_default().cloned();
|
||||
|
||||
assert!(first.is_some());
|
||||
assert!(std::sync::Arc::ptr_eq(&first.unwrap(), &second.unwrap()));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
//! Guards the wiring, not just the helper: the dial itself has to install the
|
||||
//! rustls provider, in a test binary where nothing else has installed one.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use litellm_ai_gateway::io::responses_ws::ResponsesWebSocketConnection;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::test]
|
||||
async fn dialing_wss_returns_an_error_instead_of_panicking() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("bind a loopback port");
|
||||
let port = listener
|
||||
.local_addr()
|
||||
.expect("read the bound address")
|
||||
.port();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok((stream, _peer)) = listener.accept().await {
|
||||
drop(stream);
|
||||
}
|
||||
});
|
||||
|
||||
let result = ResponsesWebSocketConnection::connect_url(
|
||||
&format!("wss://127.0.0.1:{port}/"),
|
||||
&HashMap::new(),
|
||||
Some(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"a plain TCP server cannot finish a TLS handshake"
|
||||
);
|
||||
assert!(
|
||||
rustls::crypto::CryptoProvider::get_default().is_some(),
|
||||
"the dial is what installs the process-wide provider"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue