diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index ad2453e05..ead9c4063 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -5827,3 +5827,150 @@ fn sync_build_tag() -> Result<()> { Ok(()) } + +#[test] +fn url_hash_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + iniconfig = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz" } + "#, + )?; + + // Write a lockfile with an invalid hash. + context.temp_dir.child("uv.lock").write_str(indoc! {r#" + version = 1 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz" } + sdist = { hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b4" } + + [[package]] + name = "project" + version = "0.1.0" + source = { editable = "." } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig", url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz" }] + "#})?; + + // Running `uv sync` should fail. + uv_snapshot!(context.filters(), context.sync(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + × Failed to download and build `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + ╰─▶ Hash mismatch for `iniconfig @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` + + Expected: + sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b4 + + Computed: + sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 + help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` + "###); + + Ok(()) +} + +#[test] +fn path_hash_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + // Download the source. + let archive = context.temp_dir.child("iniconfig-2.0.0.tar.gz"); + download_to_disk( + "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", + &archive, + ); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [tool.uv.sources] + iniconfig = { path = "iniconfig-2.0.0.tar.gz" } + "#, + )?; + + // Write a lockfile with an invalid hash. + context.temp_dir.child("uv.lock").write_str(indoc! {r#" + version = 1 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { path = "iniconfig-2.0.0.tar.gz" } + sdist = { hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b4" } + + [[package]] + name = "project" + version = "0.1.0" + source = { editable = "." } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig", path = "iniconfig-2.0.0.tar.gz" }] + "#})?; + + // Running `uv sync` should fail. + uv_snapshot!(context.filters(), context.sync(), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + × Failed to build `iniconfig @ file://[TEMP_DIR]/iniconfig-2.0.0.tar.gz` + ╰─▶ Hash mismatch for `iniconfig @ file://[TEMP_DIR]/iniconfig-2.0.0.tar.gz` + + Expected: + sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b4 + + Computed: + sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 + help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` + "###); + + Ok(()) +}