From e12f98a3e40ddb61edd2fd32be4bfcf01c160102 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 7 Feb 2025 19:51:19 -0600 Subject: [PATCH 1/2] Add test case for trailing `/` --- crates/uv/tests/it/lock.rs | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 97e4828734ae5..2321a7f15cf55 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -8358,6 +8358,53 @@ fn lock_multiple_indexes_same_realm_different_credentials() -> Result<()> { Ok(()) } +// Same as [`lock_multiple_indexes_same_realm_different_credentials`], but with trailing slashes +// on the index URL +#[test] +fn lock_multiple_indexes_same_realm_different_credentials_trailing_slash() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "foo" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig", "anyio"] + + [tool.uv.sources] + iniconfig = { index = "internal-proxy-heron" } + anyio = { index = "internal-proxy-eagle" } + + [[tool.uv.index]] + name = "internal-proxy-heron" + url = "https://pypi-proxy.fly.dev/basic-auth-heron/simple/" + + [[tool.uv.index]] + name = "internal-proxy-eagle" + url = "https://pypi-proxy.fly.dev/basic-auth-eagle/simple/" + "#, + )?; + + // Provide credentials via environment variables. + uv_snapshot!(context.filters(), context.lock() + .env(EnvVars::index_username("INTERNAL_PROXY_HERON"), "public") + .env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron") + .env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public") + .env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata` + Caused by: HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata) + "###); + + Ok(()) +} + /// Resolve against an index that uses relative links. #[test] fn lock_relative_index() -> Result<()> { From e7ffc2af5789c9138c655935a6f11dccc5324da2 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 7 Feb 2025 19:57:19 -0600 Subject: [PATCH 2/2] Fix credential caching for index roots when URL ends in `simple/` --- crates/uv-distribution-types/src/index.rs | 11 ++++++++--- crates/uv/tests/it/lock.rs | 7 +++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index e8465ddaecb58..d949c37766207 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -160,14 +160,19 @@ impl Index { /// For indexes with a `/simple` endpoint, this is simply the URL with the final segment /// removed. This is useful, e.g., for credential propagation to other endpoints on the index. pub fn root_url(&self) -> Option { - let segments = self.raw_url().path_segments()?; - let last = segments.last()?; + let mut segments = self.raw_url().path_segments()?; + let last = match segments.next_back()? { + // If the last segment is empty due to a trailing `/`, skip it (as in `pop_if_empty`) + "" => segments.next_back()?, + segment => segment, + }; + if !last.eq_ignore_ascii_case("simple") { return None; } let mut url = self.raw_url().clone(); - url.path_segments_mut().ok()?.pop(); + url.path_segments_mut().ok()?.pop_if_empty().pop(); Some(url) } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 2321a7f15cf55..d7f940fd9b51b 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -8393,13 +8393,12 @@ fn lock_multiple_indexes_same_realm_different_credentials_trailing_slash() -> Re .env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron") .env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public") .env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###" - success: false - exit_code: 2 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata` - Caused by: HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata) + Resolved 5 packages in [TIME] "###); Ok(())