diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 48fe21307..9d7ce0b4b 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -124,6 +124,12 @@ pub enum ResolveError { #[source] name_error: InvalidNameError, }, + #[error("The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}")] + MismatchedPackageName { + request: &'static str, + expected: PackageName, + actual: PackageName, + }, } impl From> for ResolveError { diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index e482b0d27..7ae2636bc 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -25,7 +25,7 @@ use uv_distribution::DistributionDatabase; use uv_distribution_types::{ BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, - IndexMetadata, IndexUrl, InstalledDist, PythonRequirementKind, RemoteSource, Requirement, + IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, }; use uv_git::GitResolver; @@ -2261,12 +2261,32 @@ impl ResolverState { let metadata = provider.get_installed_metadata(&dist).boxed_local().await?; + if let MetadataResponse::Found(metadata) = &metadata { + if &metadata.metadata.name != dist.name() { + return Err(ResolveError::MismatchedPackageName { + request: "installed metadata", + expected: dist.name().clone(), + actual: metadata.metadata.name.clone(), + }); + } + } + Ok(Some(Response::Installed { dist, metadata })) } @@ -2369,6 +2389,13 @@ impl ResolverState { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 2682385a7..ef96ab3b7 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -17,7 +17,6 @@ use wiremock::{ #[cfg(feature = "git")] use crate::common::{self, decode_token}; - use crate::common::{ build_vendor_links_url, download_to_disk, get_bin, uv_snapshot, venv_bin_path, venv_to_interpreter, TestContext, @@ -11108,3 +11107,37 @@ fn pep_751_multiple_sources() -> Result<()> { Ok(()) } + +/// Test that uv doesn't hang if an index returns a distribution for the wrong package. +#[tokio::test] +async fn bogus_redirect() -> Result<()> { + let context = TestContext::new("3.12"); + + let redirect_server = MockServer::start().await; + + // Configure a bogus redirect where for all packages, anyio is returned. + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(302).insert_header("Location", "https://pypi.org/simple/anyio/"), + ) + .mount(&redirect_server) + .await; + + uv_snapshot!( + context + .pip_install() + .arg("--default-index") + .arg(redirect_server.uri()) + .arg("sniffio"), + @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The index returned metadata for the wrong package: expected distribution for sniffio, got distribution for anyio + " + ); + + Ok(()) +}