diff --git a/Cargo.lock b/Cargo.lock index c45fb80..9ff2879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1725,9 +1725,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -2167,6 +2167,7 @@ dependencies = [ "regex", "reqwest", "serde", + "serde_json", "strum", "tempfile", "test-case", diff --git a/Cargo.toml b/Cargo.toml index f9e74ee..72eb62c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ platforms = "3.5.0" regex = "1.11.1" reqwest = { version = "0.12.9", default-features = false, features = ["gzip", "json"] } serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.134" strum = { version = "0.26.3", features = ["derive"] } tempfile = "3.14.0" test-case = "3.3.1" diff --git a/ubi/Cargo.toml b/ubi/Cargo.toml index c7b9797..f7f1c3e 100644 --- a/ubi/Cargo.toml +++ b/ubi/Cargo.toml @@ -23,6 +23,7 @@ platforms.workspace = true regex.workspace = true reqwest.workspace = true serde.workspace = true +serde_json.workspace = true strum.workspace = true tempfile.workspace = true thiserror.workspace = true diff --git a/ubi/src/github.rs b/ubi/src/github.rs index bbc85f0..dbe230f 100644 --- a/ubi/src/github.rs +++ b/ubi/src/github.rs @@ -9,7 +9,7 @@ use reqwest::{ header::{HeaderValue, AUTHORIZATION}, Client, RequestBuilder, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::env; use url::Url; @@ -21,7 +21,7 @@ pub(crate) struct GitHub { token: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub(crate) struct Release { pub(crate) assets: Vec, } @@ -95,3 +95,69 @@ impl GitHub { } } } + +#[cfg(test)] +mod tests { + use super::*; + use mockito::Server; + use reqwest::Client; + use test_log::test; + + #[test(tokio::test)] + async fn test_fetch_assets_without_token() -> Result<()> { + test_fetch_assets(None, None).await + } + + #[test(tokio::test)] + async fn test_fetch_assets_with_token() -> Result<()> { + test_fetch_assets(None, Some("ghp_fakeToken")).await + } + + #[test(tokio::test)] + async fn test_fetch_assets_with_tag() -> Result<()> { + test_fetch_assets(Some("v1.0.0"), None).await + } + + async fn test_fetch_assets(tag: Option<&str>, token: Option<&str>) -> Result<()> { + let assets = vec![Asset { + name: "asset1".to_string(), + url: Url::parse("https://api.github.com/repos/houseabsolute/ubi/releases/assets/1")?, + }]; + + let expect_path = if let Some(tag) = tag { + format!("/repos/houseabsolute/ubi/releases/tags/{tag}") + } else { + "/repos/houseabsolute/ubi/releases/latest".to_string() + }; + let authorization_header_matcher = if token.is_some() { + mockito::Matcher::Exact(format!("Bearer {}", token.unwrap())) + } else { + mockito::Matcher::Missing + }; + let mut server = Server::new_async().await; + let m = server + .mock("GET", expect_path.as_str()) + .match_header("Authorization", authorization_header_matcher) + .with_status(200) + .with_body(serde_json::to_string(&Release { + assets: assets.clone(), + })?) + .create_async() + .await; + + let github = GitHub::new( + "houseabsolute/ubi".to_string(), + tag.map(String::from), + Some(Url::parse(&server.url())?), + token, + ); + + let client = Client::new(); + let got_assets = github.fetch_assets(&client).await?; + assert_eq!(got_assets, assets); + + m.assert_async().await; + + Ok(()) + } +} diff --git a/ubi/src/gitlab.rs b/ubi/src/gitlab.rs index e58af34..bf44ba6 100644 --- a/ubi/src/gitlab.rs +++ b/ubi/src/gitlab.rs @@ -6,7 +6,7 @@ use anyhow::Result; use async_trait::async_trait; use log::debug; use reqwest::{header::HeaderValue, header::AUTHORIZATION, Client, RequestBuilder}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::env; use url::Url; @@ -18,12 +18,12 @@ pub(crate) struct GitLab { token: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] struct Release { assets: GitLabAssets, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] struct GitLabAssets { links: Vec, } @@ -97,3 +97,71 @@ impl GitLab { } } } + +#[cfg(test)] +mod tests { + use super::*; + use mockito::Server; + use reqwest::Client; + use test_log::test; + + #[test(tokio::test)] + async fn test_fetch_assets_without_token() -> Result<()> { + test_fetch_assets(None, None).await + } + + #[test(tokio::test)] + async fn test_fetch_assets_with_token() -> Result<()> { + test_fetch_assets(None, Some("glpat-fakeToken")).await + } + + #[test(tokio::test)] + async fn test_fetch_assets_with_tag() -> Result<()> { + test_fetch_assets(Some("v1.0.0"), None).await + } + + async fn test_fetch_assets(tag: Option<&str>, token: Option<&str>) -> Result<()> { + let assets = vec![Asset { + name: "asset1".to_string(), + url: Url::parse("https://gitlab.com/api/v4/projects/owner%2Frepo/releases/assets/1")?, + }]; + + let expect_path = if let Some(tag) = tag { + format!("/projects/houseabsolute%2Fubi/releases/{tag}") + } else { + "/projects/houseabsolute%2Fubi/releases/permalink/latest".to_string() + }; + let authorization_header_matcher = if token.is_some() { + mockito::Matcher::Exact(format!("Bearer {}", token.unwrap())) + } else { + mockito::Matcher::Missing + }; + let mut server = Server::new_async().await; + let m = server + .mock("GET", expect_path.as_str()) + .match_header("Authorization", authorization_header_matcher) + .with_status(200) + .with_body(serde_json::to_string(&Release { + assets: GitLabAssets { + links: assets.clone(), + }, + })?) + .create_async() + .await; + + let github = GitLab::new( + "houseabsolute/ubi".to_string(), + tag.map(String::from), + Some(Url::parse(&server.url())?), + token, + ); + + let client = Client::new(); + let got_assets = github.fetch_assets(&client).await?; + assert_eq!(got_assets, assets); + + m.assert_async().await; + + Ok(()) + } +} diff --git a/ubi/src/ubi.rs b/ubi/src/ubi.rs index 3db9ae5..a1904b8 100644 --- a/ubi/src/ubi.rs +++ b/ubi/src/ubi.rs @@ -5,7 +5,7 @@ use reqwest::{ header::{HeaderValue, ACCEPT}, Client, StatusCode, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{fs::File, io::Write, path::PathBuf}; use tempfile::{tempdir, TempDir}; use url::Url; @@ -21,7 +21,7 @@ pub struct Ubi<'a> { reqwest_client: Client, } -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub(crate) struct Asset { pub(crate) name: String, pub(crate) url: Url,