Skip to content

Commit

Permalink
feat(pagination): do it
Browse files Browse the repository at this point in the history
ashleygwilliams authored and mistydemeo committed Apr 8, 2024
1 parent 0c26712 commit 384072e
Showing 2 changed files with 168 additions and 13 deletions.
70 changes: 70 additions & 0 deletions axoupdater-cli/tests/integration.rs
Original file line number Diff line number Diff line change
@@ -297,3 +297,73 @@ fn test_downgrade_to_specific_version() -> std::io::Result<()> {

Ok(())
}

// A similar test to the one above, but it upgrades to a significantly older
// version that's outside the GitHub API's 30 versions.
// This version isn't available for every target, so we only run it for
// certain target triples.
#[test]
fn test_downgrade_to_specific_old_version() -> std::io::Result<()> {
// Only available for x86_64 Darwin and x86_64 Linux
match std::env::consts::OS {
"linux" | "macos" => {
if std::env::consts::ARCH != "x86_64" {
return Ok(());
}
}
"windows" => return Ok(()),
_ => return Ok(()),
}

let tempdir = TempDir::new()?;
let bindir_path = &tempdir.path().join("bin");
let bindir = Utf8Path::from_path(bindir_path).unwrap();
std::fs::create_dir_all(bindir)?;

let base_version = "0.2.116";
let target_version = "0.2.50";

let url = axolotlsay_tarball_path(base_version);

let mut response = reqwest::blocking::get(url)
.unwrap()
.error_for_status()
.unwrap();

let compressed_path =
Utf8PathBuf::from_path_buf(tempdir.path().join("axolotlsay.tar.gz")).unwrap();
let mut contents = vec![];
response.read_to_end(&mut contents)?;
std::fs::write(&compressed_path, contents)?;

// Write the receipt for the updater to use
write_receipt(base_version, &bindir.to_path_buf())?;

LocalAsset::untar_gz_all(&compressed_path, bindir).unwrap();

// Now install our copy of the updater instead of the one axolotlsay came with
let updater_path = bindir.join(format!("axolotlsay-update{EXE_SUFFIX}"));
std::fs::copy(BIN, &updater_path)?;

let mut updater = Cmd::new(&updater_path, "run updater");
updater.arg("--version").arg(target_version);
updater.env("AXOUPDATER_CONFIG_PATH", bindir);
// This installer is so old it doesn't respect the install path, so we
// have to set CARGO_HOME to force it.
updater.env("CARGO_HOME", tempdir.path());
// We'll do that manually
updater.check(false);
let result = updater.output();
assert!(result.is_ok());

// Now let's check the version we just updated to
let new_axolotlsay_path = &bindir.join(format!("axolotlsay{EXE_SUFFIX}"));
assert!(new_axolotlsay_path.exists());
let mut new_axolotlsay = Cmd::new(new_axolotlsay_path, "version test");
new_axolotlsay.arg("--version");
let output = new_axolotlsay.output().unwrap();
let stderr_string = String::from_utf8(output.stdout).unwrap();
assert_eq!(stderr_string, format!("axolotlsay {}\n", target_version));

Ok(())
}
111 changes: 98 additions & 13 deletions axoupdater/src/lib.rs
Original file line number Diff line number Diff line change
@@ -877,33 +877,86 @@ async fn get_specific_github_version(
}

#[cfg(feature = "github_releases")]
async fn get_github_releases(
async fn get_releases(
client: &reqwest::Client,
url: &str,
name: &str,
owner: &str,
app_name: &str,
) -> AxoupdateResult<Vec<Release>> {
let client = reqwest::Client::new();
let resp: Vec<Release> = client
.get(format!("{GITHUB_API}/repos/{owner}/{name}/releases"))
) -> AxoupdateResult<reqwest::Response> {
client
.get(url)
.header(ACCEPT, "application/json")
.header(
USER_AGENT,
format!("axoupdate/{}", env!("CARGO_PKG_VERSION")),
)
.header("X-GitHub-Api-Version", "2022-11-28")
.send()
.await?
.error_for_status()
.map_err(|_| AxoupdateError::ReleaseNotFound {
name: name.to_owned(),
app_name: app_name.to_owned(),
})?
.json::<Vec<GithubRelease>>()
.await?
.into_iter()
.filter_map(|gh| Release::try_from_github(app_name, gh).ok())
.collect();
})
}

// The format of the header looks like so:
// ```
// <https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>; rel="next", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"
// ```
#[cfg(feature = "github_releases")]
fn get_next_url(link_header: &str) -> Option<String> {
let links = link_header.split(',').collect::<Vec<_>>();
for entry in links {
if entry.contains("next") {
let mut link = entry.split(';').collect::<Vec<_>>()[0]
.to_string()
.trim()
.to_string();
link.remove(0);
link.pop();
return Some(link);
}
}
None
}

#[cfg(feature = "github_releases")]
async fn get_github_releases(
name: &str,
owner: &str,
app_name: &str,
) -> AxoupdateResult<Vec<Release>> {
let client = reqwest::Client::new();
let mut url = format!("{GITHUB_API}/repos/{owner}/{name}/releases");
let mut pages_remain = true;
let mut data: Vec<Release> = vec![];

while pages_remain {
let resp = get_releases(&client, &url, name, app_name).await?;

let headers = resp.headers();
let link_header = &headers[reqwest::header::LINK]
.to_str()
.expect("header was not ascii")
.to_string();
pages_remain = link_header.contains("rel=\"next\"");

let mut body: Vec<Release> = resp
.json::<Vec<GithubRelease>>()
.await?
.into_iter()
.filter_map(|gh| Release::try_from_github(app_name, gh).ok())
.collect();
data.append(&mut body);
dbg!(&data);

if pages_remain {
url = get_next_url(link_header).expect("detected a next but it was a lie");
}
}

Ok(resp
Ok(data
.into_iter()
.filter(|r| {
r.assets
@@ -1142,3 +1195,35 @@ fn load_receipt_for(app_name: &str) -> AxoupdateResult<InstallReceipt> {
app_name: app_name.to_owned(),
})
}

#[test]
fn test_link_header_parse() {
let sample = r#"
<https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>; rel="next", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"
"#;

let result = get_next_url(sample);
assert!(result.is_some());
assert_eq!(
"https://api.github.com/repositories/1300192/issues?page=4",
result.unwrap()
);
}

#[test]
fn test_link_header_parse_next_missing() {
let sample = r#"
<https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"
"#;

let result = get_next_url(sample);
assert!(result.is_none());
}

#[test]
fn test_link_header_parse_empty_header() {
let sample = "";

let result = get_next_url(sample);
assert!(result.is_none());
}

0 comments on commit 384072e

Please sign in to comment.