diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index 2c85e3e7f0c6..3951458c937e 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -309,6 +309,16 @@ fn site_packages_path(venv: &Path, python: String) -> PathBuf { } } +pub fn venv_bin_path(venv: &Path) -> PathBuf { + if cfg!(unix) { + venv.join("bin") + } else if cfg!(windows) { + venv.join("Scripts") + } else { + unimplemented!("Only Windows and Unix are supported") + } +} + pub fn venv_to_interpreter(venv: &Path) -> PathBuf { if cfg!(unix) { venv.join("bin").join("python") diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index dde3e8637e2f..d97bb302c77b 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use common::{uv_snapshot, TestContext}; use uv_fs::Simplified; -use crate::common::{get_bin, BUILD_VENDOR_LINKS_URL}; +use crate::common::{get_bin, venv_bin_path, BUILD_VENDOR_LINKS_URL}; mod common; @@ -2916,6 +2916,128 @@ fn install_index_with_relative_links() { context.assert_command("import anyio").success(); } +/// Install a package from an index that requires authentication from the keyring. +#[test] +fn install_package_basic_auth_from_keyring() { + let context = TestContext::new("3.12"); + + // Install our keyring plugin + context + .install() + .arg( + context + .workspace_root + .join("scripts") + .join("packages") + .join("keyring_test_plugin"), + ) + .assert() + .success(); + + uv_snapshot!(context.install() + .arg("anyio") + .arg("--index-url") + .arg("https://public@pypi-proxy.fly.dev/basic-auth/simple") + .arg("--keyring-provider") + .arg("subprocess") + .arg("--strict") + .env("KEYRING_TEST_CREDENTIALS", r#"{"pypi-proxy.fly.dev": {"public": "heron"}}"#) + .env("PATH", venv_bin_path(context.venv.as_path())), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Downloaded 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "### + ); + + context.assert_command("import anyio").success(); +} + +/// Install a package from an index that requires authentication +/// but the keyring has the wrong password +#[test] +fn install_package_basic_auth_from_keyring_wrong_password() { + let context = TestContext::new("3.12"); + + // Install our keyring plugin + context + .install() + .arg( + context + .workspace_root + .join("scripts") + .join("packages") + .join("keyring_test_plugin"), + ) + .assert() + .success(); + + uv_snapshot!(context.install() + .arg("anyio") + .arg("--index-url") + .arg("https://public@pypi-proxy.fly.dev/basic-auth/simple") + .arg("--keyring-provider") + .arg("subprocess") + .arg("--strict") + .env("KEYRING_TEST_CREDENTIALS", r#"{"pypi-proxy.fly.dev": {"public": "foobar"}}"#) + .env("PATH", venv_bin_path(context.venv.as_path())), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: anyio==4.3.0 + Caused by: HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth/files/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl.metadata) + "### + ); +} + +/// Install a package from an index that requires authentication +/// but the keyring has the wrong username +#[test] +fn install_package_basic_auth_from_keyring_wrong_username() { + let context = TestContext::new("3.12"); + + // Install our keyring plugin + context + .install() + .arg( + context + .workspace_root + .join("scripts") + .join("packages") + .join("keyring_test_plugin"), + ) + .assert() + .success(); + + uv_snapshot!(context.install() + .arg("anyio") + .arg("--index-url") + .arg("https://public@pypi-proxy.fly.dev/basic-auth/simple") + .arg("--keyring-provider") + .arg("subprocess") + .arg("--strict") + .env("KEYRING_TEST_CREDENTIALS", r#"{"pypi-proxy.fly.dev": {"other": "heron"}}"#) + .env("PATH", venv_bin_path(context.venv.as_path())), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: anyio==4.3.0 + Caused by: HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth/files/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl.metadata) + "### + ); +} + /// Install a package from an index that provides relative links and requires authentication #[test] fn install_index_with_relative_links_authenticated() { diff --git a/scripts/packages/keyring_test_plugin/keyrings/__init__.py b/scripts/packages/keyring_test_plugin/keyrings/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/scripts/packages/keyring_test_plugin/keyrings/test_keyring.py b/scripts/packages/keyring_test_plugin/keyrings/test_keyring.py new file mode 100644 index 000000000000..2895477f487f --- /dev/null +++ b/scripts/packages/keyring_test_plugin/keyrings/test_keyring.py @@ -0,0 +1,23 @@ +import json +import os +import sys + +from keyring import backend + + +class KeyringTest(backend.KeyringBackend): + priority = 9 + + def get_password(self, service, username): + print(f"Request for {username}@{service}", file=sys.stderr) + credentials = json.loads(os.environ.get("KEYRING_TEST_CREDENTIALS") or {}) + return credentials.get(service, {}).get(username) + + def set_password(self, service, username, password): + raise NotImplementedError() + + def delete_password(self, service, username): + raise NotImplementedError() + + def get_credential(self, service, username): + raise NotImplementedError() diff --git a/scripts/packages/keyring_test_plugin/pyproject.toml b/scripts/packages/keyring_test_plugin/pyproject.toml new file mode 100644 index 000000000000..8b904bd00ca0 --- /dev/null +++ b/scripts/packages/keyring_test_plugin/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "keyring-test-plugin" +description = "A keyring plugin for testing." +requires-python = ">=3.7" +version = "0.1.0" +keywords = [] +authors = [ + { name = "Astral Software Inc.", email = "hey@astral.sh" }, +] +dependencies = [ + "keyring" +] + +[tool.flit.module] +name = "keyrings" + +[project.entry-points."keyring.backends"] +AstralTest = "keyrings.test_keyring"