use std::{path::PathBuf, process::Command};
use anyhow::Result;
use assert_fs::prelude::*;
use axoupdater::{
ReleaseSourceType,
test::helpers::{RuntestArgs, perform_runtest},
};
use regex::escape;
use serde_json::json;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use uv_static::EnvVars;
use uv_test::{TestContext, get_bin, uv_snapshot};
#[test]
fn check_self_update() {
if std::env::var(EnvVars::CI)
.map(|s| s.is_empty())
.unwrap_or(true)
{
return;
}
let args = RuntestArgs {
app_name: "uv".to_owned(),
package: "uv".to_owned(),
owner: "astral-sh".to_owned(),
bin: get_bin!(),
binaries: vec!["uv".to_owned()],
args: vec!["self".to_owned(), "update".to_owned()],
release_type: ReleaseSourceType::GitHub,
};
let installed_bin = perform_runtest(&args);
let status = Command::new(installed_bin)
.arg("--version")
.status()
.expect("failed to run 'uv --version'");
assert!(status.success(), "'uv --version' returned non-zero");
}
#[test]
fn self_update_offline_error() {
let context = uv_test::test_context!("3.12");
uv_snapshot!(context.self_update().arg("--offline"),
@r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
error: Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)
");
}
#[test]
fn self_update_offline_quiet() {
let context = uv_test::test_context!("3.12");
uv_snapshot!(context.self_update().arg("--offline").arg("--quiet"),
@r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
error: Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)
");
}
#[test]
fn self_update_offline_extra_quiet() {
let context = uv_test::test_context!("3.12");
uv_snapshot!(context.self_update().arg("--offline").arg("--quiet").arg("--quiet"),
@r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
");
}
async fn setup_mock_update(
context: &TestContext,
target_version: &str,
) -> Result<(PathBuf, MockServer)> {
let receipt_dir = context.temp_dir.child("receipt");
receipt_dir.create_dir_all()?;
let install_prefix = std::path::absolute(
get_bin!()
.parent()
.expect("uv binary should have a parent directory"),
)?;
receipt_dir
.child("uv-receipt.json")
.write_str(&serde_json::to_string_pretty(&json!({
"install_prefix": install_prefix,
"binaries": ["uv"],
"cdylibs": [],
"source": {
"release_type": "github",
"owner": "astral-sh",
"name": "uv",
"app_name": "uv",
},
"version": env!("CARGO_PKG_VERSION"),
"provider": {
"source": "cargo-dist",
"version": "0.31.0",
},
"modify_path": true,
}))?)?;
let server = MockServer::start().await;
let installer_name = if cfg!(windows) {
"uv-installer.ps1"
} else {
"uv-installer.sh"
};
Mock::given(method("GET"))
.and(path(format!(
"/api/v3/repos/astral-sh/uv/releases/tags/{target_version}"
)))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"tag_name": target_version,
"name": target_version,
"url": format!("{}/repos/astral-sh/uv/releases/tags/{target_version}", server.uri()),
"assets": [{
"url": format!("{}/assets/{installer_name}", server.uri()),
"browser_download_url": format!("{}/downloads/{installer_name}", server.uri()),
"name": installer_name,
}],
"prerelease": false,
})))
.mount(&server)
.await;
Ok((receipt_dir.to_path_buf(), server))
}
#[test]
fn test_self_update_help() {
let context = uv_test::test_context_with_versions!(&[]);
let output = context
.help()
.arg("self")
.arg("update")
.output()
.expect("`uv help self update` should succeed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("A GitHub token for authentication."));
assert!(stdout.contains("A token is not required but can be used to reduce the"));
assert!(stdout.contains("chance of encountering rate limits"));
}
#[tokio::test]
async fn test_self_update_uses_custom_path_with_ghe_override() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filter((
escape(&format!("v{}", env!("CARGO_PKG_VERSION"))),
"v[CURRENT_VERSION]",
));
let receipt_dir = context.temp_dir.child("receipt");
receipt_dir.create_dir_all()?;
let install_prefix = std::path::absolute(
get_bin!()
.parent()
.expect("uv binary should have a parent directory"),
)?;
receipt_dir
.child("uv-receipt.json")
.write_str(&serde_json::to_string_pretty(&json!({
"install_prefix": install_prefix,
"binaries": ["uv"],
"cdylibs": [],
"source": {
"release_type": "github",
"owner": "astral-sh",
"name": "uv",
"app_name": "uv",
},
"version": env!("CARGO_PKG_VERSION"),
"provider": {
"source": "cargo-dist",
"version": "0.31.0",
},
"modify_path": true,
}))?)?;
let server = MockServer::start().await;
let target_version = "9.9.9";
let installer_name = if cfg!(windows) {
"uv-installer.ps1"
} else {
"uv-installer.sh"
};
Mock::given(method("GET"))
.and(path(format!(
"/api/v3/repos/astral-sh/uv/releases/tags/{target_version}"
)))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"tag_name": target_version,
"name": target_version,
"url": format!("{}/repos/astral-sh/uv/releases/tags/{target_version}", server.uri()),
"assets": [{
"url": format!("{}/assets/{installer_name}", server.uri()),
"browser_download_url": format!("{}/downloads/{installer_name}", server.uri()),
"name": installer_name,
}],
"prerelease": false,
})))
.mount(&server)
.await;
uv_snapshot!(context.filters(), context.self_update()
.arg(target_version)
.arg("--dry-run")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
info: Checking for updates...
Would update uv from v[CURRENT_VERSION] to v9.9.9
");
Ok(())
}
#[tokio::test]
async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filtered_current_version();
let target_version = "9.9.9";
let (receipt_dir, server) = setup_mock_update(&context, target_version).await?;
uv_snapshot!(context.filters(), context.self_update()
.arg(target_version)
.arg("--dry-run")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
info: Checking for updates...
Would update uv from v[CURRENT_VERSION] to v9.9.9
");
Ok(())
}
#[tokio::test]
async fn self_update_dry_run_quiet() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filtered_current_version();
let target_version = "9.9.9";
let (receipt_dir, server) = setup_mock_update(&context, target_version).await?;
uv_snapshot!(context.filters(), context.self_update()
.arg(target_version)
.arg("--dry-run")
.arg("--quiet")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Would update uv from v[CURRENT_VERSION] to v9.9.9
");
Ok(())
}
#[tokio::test]
async fn self_update_dry_run_extra_quiet() -> Result<()> {
let context = uv_test::test_context!("3.12");
let receipt_dir = context.temp_dir.child("receipt");
receipt_dir.create_dir_all()?;
let target_version = "9.9.9";
let (receipt_dir, server) = setup_mock_update(&context, target_version).await?;
uv_snapshot!(context.self_update()
.arg(target_version)
.arg("--dry-run")
.arg("--quiet")
.arg("--quiet")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
Ok(())
}
#[tokio::test]
async fn self_update_noop_dry_run() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filtered_current_version();
let target_version = env!("CARGO_PKG_VERSION");
let (receipt_dir, server) = setup_mock_update(&context, target_version).await?;
uv_snapshot!(context.filters(), context.self_update()
.arg(target_version)
.arg("--dry-run")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
info: Checking for updates...
You're on the latest version of uv (v[CURRENT_VERSION])
");
Ok(())
}
#[tokio::test]
async fn self_update_noop_dry_run_quiet() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filtered_current_version();
let target_version = env!("CARGO_PKG_VERSION");
let (receipt_dir, server) = setup_mock_update(&context, target_version).await?;
uv_snapshot!(context.filters(), context.self_update()
.arg(target_version)
.arg("--dry-run")
.arg("--quiet")
.env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str())
.env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
Ok(())
}