Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pagination): do it #70

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions axoupdater-cli/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand Down Expand Up @@ -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());
}
Loading