Add basic network error tests (#13585)

Add basic tests for error messages on retryable network errors.

This test mod is intended to grow to ensure that we handle retryable
errors correctly and that we show the appropriate error message if we
failed after retrying.

The starter tests show some common cases we've seen download errors in:
simple and find links indexes, file downloads and Python installs.

For `io::Error` fault injection to test the reqwest `Err` path besides
the HTTP status code `Ok` path, see
https://github.com/LukeMathWalker/wiremock-rs/issues/149.
This commit is contained in:
konsti 2025-06-10 12:00:04 +02:00 committed by GitHub
parent 9129d2a9a3
commit c54f131500
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 157 additions and 1 deletions

View file

@ -39,7 +39,7 @@ mod lock_conflict;
mod lock_scenarios;
mod version;
mod network;
#[cfg(all(feature = "python", feature = "pypi"))]
mod pip_check;
@ -120,6 +120,8 @@ mod tree;
#[cfg(feature = "python")]
mod venv;
mod version;
#[cfg(all(feature = "python", feature = "pypi"))]
mod workflow;

View file

@ -0,0 +1,154 @@
use std::env;
use assert_fs::fixture::{FileWriteStr, PathChild};
use http::StatusCode;
use serde_json::json;
use wiremock::matchers::method;
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::common::{TestContext, uv_snapshot};
/// Check the simple index error message when the server returns HTTP status 500, a retryable error.
#[tokio::test]
async fn simple_http_500() {
let context = TestContext::new("3.12");
let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR))
.mount(&server)
.await;
let mock_server_uri = server.uri();
let filters = vec![(mock_server_uri.as_str(), "[SERVER]")];
uv_snapshot!(filters, context
.pip_install()
.arg("tqdm")
.arg("--index-url")
.arg(server.uri()), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch: `[SERVER]/tqdm/`
Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/tqdm/)
");
}
/// Check the find links error message when the server returns HTTP status 500, a retryable error.
#[tokio::test]
async fn find_links_http_500() {
let context = TestContext::new("3.12");
let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR))
.mount(&server)
.await;
let mock_server_uri = server.uri();
let filters = vec![(mock_server_uri.as_str(), "[SERVER]")];
uv_snapshot!(filters, context
.pip_install()
.arg("tqdm")
.arg("--no-index")
.arg("--find-links")
.arg(server.uri()), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to read `--find-links` URL: [SERVER]/
Caused by: Failed to fetch: `[SERVER]/`
Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/)
");
}
/// Check the direct package URL error message when the server returns HTTP status 500, a retryable
/// error.
#[tokio::test]
async fn direct_url_http_500() {
let context = TestContext::new("3.12");
let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR))
.mount(&server)
.await;
let mock_server_uri = server.uri();
let tqdm_url = format!(
"{mock_server_uri}/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl"
);
let filters = vec![(mock_server_uri.as_str(), "[SERVER]")];
uv_snapshot!(filters, context
.pip_install()
.arg(format!("tqdm @ {tqdm_url}")), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download `tqdm @ [SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl`
Failed to fetch: `[SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl`
HTTP status server error (500 Internal Server Error) for url ([SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl)
");
}
/// Check the Python install error message when the server returns HTTP status 500, a retryable
/// error.
#[tokio::test]
async fn python_install_http_500() {
let context = TestContext::new("3.12")
.with_filtered_python_keys()
.with_filtered_exe_suffix()
.with_managed_python_dirs();
let server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR))
.mount(&server)
.await;
let mock_server_uri = server.uri();
let python_downloads_json = context.temp_dir.child("python_downloads.json");
let interpreter = json!({
"cpython-3.10.0-darwin-aarch64-none": {
"arch": {
"family": "aarch64",
"variant": null
},
"libc": "none",
"major": 3,
"minor": 10,
"name": "cpython",
"os": "darwin",
"patch": 0,
"prerelease": "",
"sha256": null,
"url": format!("{mock_server_uri}/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst"),
"variant": null
}
});
python_downloads_json
.write_str(&serde_json::to_string(&interpreter).unwrap())
.unwrap();
let filters = vec![(mock_server_uri.as_str(), "[SERVER]")];
uv_snapshot!(filters, context
.python_install()
.arg("cpython-3.10.0-darwin-aarch64-none")
.arg("--python-downloads-json-url")
.arg(python_downloads_json.path()), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
error: Failed to install cpython-3.10.0-macos-aarch64-none
Caused by: Failed to download [SERVER]/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst
Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst)
");
}