mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-25 21:36:23 +02:00
feat: mock all network requests in unit tests
This commit is contained in:
Generated
+94
-1
@@ -82,6 +82,16 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -803,6 +813,24 @@ dependencies = [
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"deadpool-runtime",
|
||||
"num_cpus",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
@@ -966,6 +994,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"wiremock",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -1211,6 +1240,21 @@ dependencies = [
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -1218,6 +1262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1285,6 +1330,7 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
@@ -1653,6 +1699,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
@@ -1728,6 +1780,12 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
@@ -1741,6 +1799,7 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 1.0.14",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
@@ -2471,6 +2530,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.3"
|
||||
@@ -3095,7 +3164,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.4.0",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.44",
|
||||
"tracing",
|
||||
@@ -5716,6 +5785,30 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiremock"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "101681b74cd87b5899e87bcf5a64e83334dd313fcd3053ea72e6dba18928e301"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"deadpool",
|
||||
"futures",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
|
||||
@@ -20,7 +20,7 @@ tauri-build = { version = "2", features = [] }
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tauri = { version = "2", features = ["devtools"] }
|
||||
tauri = { version = "2", features = ["devtools", "test"] }
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
@@ -41,6 +41,7 @@ core-foundation="0.10"
|
||||
[dev-dependencies]
|
||||
tempfile = "3.13.0"
|
||||
tokio-test = "0.4.4"
|
||||
wiremock = "0.6"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
||||
+473
-195
@@ -231,12 +231,44 @@ struct CachedGithubData {
|
||||
|
||||
pub struct ApiClient {
|
||||
client: Client,
|
||||
firefox_api_base: String,
|
||||
firefox_dev_api_base: String,
|
||||
github_api_base: String,
|
||||
chromium_api_base: String,
|
||||
tor_archive_base: String,
|
||||
mozilla_download_base: String,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
firefox_api_base: "https://product-details.mozilla.org/1.0".to_string(),
|
||||
firefox_dev_api_base: "https://product-details.mozilla.org/1.0".to_string(),
|
||||
github_api_base: "https://api.github.com".to_string(),
|
||||
chromium_api_base: "https://commondatastorage.googleapis.com/chromium-browser-snapshots".to_string(),
|
||||
tor_archive_base: "https://archive.torproject.org/tor-package-archive/torbrowser".to_string(),
|
||||
mozilla_download_base: "https://download.mozilla.org".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_with_base_urls(
|
||||
firefox_api_base: String,
|
||||
firefox_dev_api_base: String,
|
||||
github_api_base: String,
|
||||
chromium_api_base: String,
|
||||
tor_archive_base: String,
|
||||
mozilla_download_base: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
firefox_api_base,
|
||||
firefox_dev_api_base,
|
||||
github_api_base,
|
||||
chromium_api_base,
|
||||
tor_archive_base,
|
||||
mozilla_download_base,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +408,8 @@ impl ApiClient {
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=firefox-{version}&os=osx&lang=en-US"
|
||||
"{}/?product=firefox-{}&os=osx&lang=en-US",
|
||||
self.mozilla_download_base, version
|
||||
)),
|
||||
}
|
||||
})
|
||||
@@ -386,7 +419,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching Firefox releases from Mozilla API...");
|
||||
let url = "https://product-details.mozilla.org/1.0/firefox.json";
|
||||
let url = format!("{}/firefox.json", self.firefox_api_base);
|
||||
|
||||
let response = self
|
||||
.client
|
||||
@@ -414,8 +447,8 @@ impl ApiClient {
|
||||
date: release.date,
|
||||
is_prerelease: !is_stable,
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=firefox-{}&os=osx&lang=en-US",
|
||||
release.version
|
||||
"{}/?product=firefox-{}&os=osx&lang=en-US",
|
||||
self.mozilla_download_base, release.version
|
||||
)),
|
||||
})
|
||||
} else {
|
||||
@@ -460,7 +493,8 @@ impl ApiClient {
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: is_alpha_version(&version),
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=devedition-{version}&os=osx&lang=en-US"
|
||||
"{}/?product=devedition-{}&os=osx&lang=en-US",
|
||||
self.mozilla_download_base, version
|
||||
)),
|
||||
}
|
||||
})
|
||||
@@ -470,7 +504,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching Firefox Developer Edition releases from Mozilla API...");
|
||||
let url = "https://product-details.mozilla.org/1.0/devedition.json";
|
||||
let url = format!("{}/devedition.json", self.firefox_dev_api_base);
|
||||
|
||||
let response = self
|
||||
.client
|
||||
@@ -504,8 +538,8 @@ impl ApiClient {
|
||||
date: release.date,
|
||||
is_prerelease: !is_stable,
|
||||
download_url: Some(format!(
|
||||
"https://download.mozilla.org/?product=devedition-{}&os=osx&lang=en-US",
|
||||
release.version
|
||||
"{}/?product=devedition-{}&os=osx&lang=en-US",
|
||||
self.mozilla_download_base, release.version
|
||||
)),
|
||||
})
|
||||
} else {
|
||||
@@ -534,6 +568,7 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn fetch_mullvad_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -552,7 +587,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching Mullvad releases from GitHub API...");
|
||||
let url = "https://api.github.com/repos/mullvad/mullvad-browser/releases";
|
||||
let url = format!("{}/repos/mullvad/mullvad-browser/releases", self.github_api_base);
|
||||
let releases = self
|
||||
.client
|
||||
.get(url)
|
||||
@@ -583,6 +618,7 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn fetch_zen_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -601,7 +637,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching Zen releases from GitHub API...");
|
||||
let url = "https://api.github.com/repos/zen-browser/desktop/releases";
|
||||
let url = format!("{}/repos/zen-browser/desktop/releases", self.github_api_base);
|
||||
let mut releases = self
|
||||
.client
|
||||
.get(url)
|
||||
@@ -624,6 +660,7 @@ impl ApiClient {
|
||||
Ok(releases)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn fetch_brave_releases(
|
||||
&self,
|
||||
) -> Result<Vec<GithubRelease>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -642,7 +679,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching Brave releases from GitHub API...");
|
||||
let url = "https://api.github.com/repos/brave/brave-browser/releases";
|
||||
let url = format!("{}/repos/brave/brave-browser/releases", self.github_api_base);
|
||||
let releases = self
|
||||
.client
|
||||
.get(url)
|
||||
@@ -696,7 +733,8 @@ impl ApiClient {
|
||||
"Mac"
|
||||
};
|
||||
let url = format!(
|
||||
"https://commondatastorage.googleapis.com/chromium-browser-snapshots/{arch}/LAST_CHANGE"
|
||||
"{}/{arch}/LAST_CHANGE",
|
||||
self.chromium_api_base
|
||||
);
|
||||
let version = self
|
||||
.client
|
||||
@@ -783,7 +821,8 @@ impl ApiClient {
|
||||
date: "".to_string(), // Cache doesn't store dates
|
||||
is_prerelease: false, // Assume all archived versions are stable
|
||||
download_url: Some(format!(
|
||||
"https://archive.torproject.org/tor-package-archive/torbrowser/{version}/tor-browser-macos-{version}.dmg"
|
||||
"{}/{version}/tor-browser-macos-{version}.dmg",
|
||||
self.tor_archive_base
|
||||
)),
|
||||
}
|
||||
}).collect());
|
||||
@@ -791,7 +830,7 @@ impl ApiClient {
|
||||
}
|
||||
|
||||
println!("Fetching TOR releases from archive...");
|
||||
let url = "https://archive.torproject.org/tor-package-archive/torbrowser/";
|
||||
let url = format!("{}/", self.tor_archive_base);
|
||||
let html = self
|
||||
.client
|
||||
.get(url)
|
||||
@@ -855,7 +894,8 @@ impl ApiClient {
|
||||
date: "".to_string(), // TOR archive doesn't provide structured dates
|
||||
is_prerelease: false, // Assume all archived versions are stable
|
||||
download_url: Some(format!(
|
||||
"https://archive.torproject.org/tor-package-archive/torbrowser/{version}/tor-browser-macos-{version}.dmg"
|
||||
"{}/{version}/tor-browser-macos-{version}.dmg",
|
||||
self.tor_archive_base
|
||||
)),
|
||||
}
|
||||
}).collect())
|
||||
@@ -865,7 +905,7 @@ impl ApiClient {
|
||||
&self,
|
||||
version: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let url = format!("https://archive.torproject.org/tor-package-archive/torbrowser/{version}/");
|
||||
let url = format!("{}/{version}/", self.tor_archive_base);
|
||||
let html = self
|
||||
.client
|
||||
.get(&url)
|
||||
@@ -883,6 +923,24 @@ impl ApiClient {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wiremock::{MockServer, Mock, ResponseTemplate};
|
||||
use wiremock::matchers::{method, path, header};
|
||||
|
||||
async fn setup_mock_server() -> MockServer {
|
||||
MockServer::start().await
|
||||
}
|
||||
|
||||
fn create_test_client(server: &MockServer) -> ApiClient {
|
||||
let base_url = server.uri();
|
||||
ApiClient::new_with_base_urls(
|
||||
base_url.clone(), // firefox_api_base
|
||||
base_url.clone(), // firefox_dev_api_base
|
||||
base_url.clone(), // github_api_base
|
||||
base_url.clone(), // chromium_api_base
|
||||
base_url.clone(), // tor_archive_base
|
||||
base_url.clone(), // mozilla_download_base
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_parsing() {
|
||||
@@ -981,236 +1039,456 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_firefox_api() {
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_firefox_releases_with_caching(false).await;
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Firefox releases");
|
||||
|
||||
// Check that releases have required fields
|
||||
let first_release = &releases[0];
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
|
||||
println!("Firefox API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", releases[0].version);
|
||||
let mock_response = r#"{
|
||||
"releases": {
|
||||
"firefox-139.0": {
|
||||
"build_number": 1,
|
||||
"category": "major",
|
||||
"date": "2024-01-15",
|
||||
"description": "Firefox 139.0 Release",
|
||||
"is_security_driven": false,
|
||||
"product": "firefox",
|
||||
"version": "139.0"
|
||||
},
|
||||
"firefox-138.0": {
|
||||
"build_number": 1,
|
||||
"category": "major",
|
||||
"date": "2024-01-01",
|
||||
"description": "Firefox 138.0 Release",
|
||||
"is_security_driven": false,
|
||||
"product": "firefox",
|
||||
"version": "138.0"
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Firefox API test failed: {e}");
|
||||
panic!("Firefox API should work");
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/firefox.json"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_firefox_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].version, "139.0");
|
||||
assert!(releases[0].download_url.is_some());
|
||||
assert!(releases[0].download_url.as_ref().unwrap().contains(&server.uri()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_firefox_developer_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client
|
||||
.fetch_firefox_developer_releases_with_caching(false)
|
||||
let mock_response = r#"{
|
||||
"releases": {
|
||||
"devedition-140.0b1": {
|
||||
"build_number": 1,
|
||||
"category": "major",
|
||||
"date": "2024-01-20",
|
||||
"description": "Firefox Developer Edition 140.0b1",
|
||||
"is_security_driven": false,
|
||||
"product": "devedition",
|
||||
"version": "140.0b1"
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/devedition.json"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(
|
||||
!releases.is_empty(),
|
||||
"Should have Firefox Developer releases"
|
||||
);
|
||||
let result = client.fetch_firefox_developer_releases_with_caching(true).await;
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
|
||||
println!(
|
||||
"Firefox Developer API test passed. Found {} releases",
|
||||
releases.len()
|
||||
);
|
||||
println!("Latest version: {}", releases[0].version);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Firefox Developer API test failed: {e}");
|
||||
panic!("Firefox Developer API should work");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].version, "140.0b1");
|
||||
assert!(releases[0].download_url.is_some());
|
||||
assert!(releases[0].download_url.as_ref().unwrap().contains(&server.uri()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_mullvad_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_mullvad_releases().await;
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Mullvad releases");
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(
|
||||
!first_release.tag_name.is_empty(),
|
||||
"Tag name should not be empty"
|
||||
);
|
||||
|
||||
println!("Mullvad API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", releases[0].tag_name);
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "14.5a6",
|
||||
"name": "Mullvad Browser 14.5a6",
|
||||
"prerelease": true,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "mullvad-browser-macos-14.5a6.dmg",
|
||||
"browser_download_url": "https://example.com/mullvad-14.5a6.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Mullvad API test failed: {e}");
|
||||
panic!("Mullvad API should work");
|
||||
}
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/mullvad/mullvad-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_mullvad_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].tag_name, "14.5a6");
|
||||
assert!(releases[0].is_alpha);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_zen_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(1500)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_zen_releases().await;
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
assert!(!releases.is_empty(), "Should have Zen releases");
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(
|
||||
!first_release.tag_name.is_empty(),
|
||||
"Tag name should not be empty"
|
||||
);
|
||||
|
||||
println!("Zen API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", releases[0].tag_name);
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "1.0.0-twilight",
|
||||
"name": "Zen Browser Twilight",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "zen.macos-universal.dmg",
|
||||
"browser_download_url": "https://example.com/zen-twilight.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Zen API test failed: {e}");
|
||||
panic!("Zen API should work");
|
||||
}
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/zen-browser/desktop/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_zen_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].tag_name, "1.0.0-twilight");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_brave_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_brave_releases().await;
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "v1.81.9",
|
||||
"name": "Brave Release 1.81.9",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "brave-v1.81.9-universal.dmg",
|
||||
"browser_download_url": "https://example.com/brave-1.81.9-universal.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
match result {
|
||||
Ok(releases) => {
|
||||
// Note: Brave might not always have macOS releases, so we don't assert non-empty
|
||||
println!(
|
||||
"Brave API test passed. Found {} releases with macOS assets",
|
||||
releases.len()
|
||||
);
|
||||
if !releases.is_empty() {
|
||||
println!("Latest version: {}", releases[0].tag_name);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Brave API test failed: {e}");
|
||||
panic!("Brave API should work");
|
||||
}
|
||||
}
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/brave/brave-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_brave_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].tag_name, "v1.81.9");
|
||||
assert!(!releases[0].is_alpha);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_chromium_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let arch = if cfg!(target_arch = "aarch64") {
|
||||
"Mac_Arm"
|
||||
} else {
|
||||
"Mac"
|
||||
};
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("/{arch}/LAST_CHANGE")))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string("1465660")
|
||||
.insert_header("content-type", "text/plain"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.fetch_chromium_latest_version().await;
|
||||
|
||||
match result {
|
||||
Ok(version) => {
|
||||
assert!(!version.is_empty(), "Version should not be empty");
|
||||
assert!(
|
||||
version.chars().all(|c| c.is_ascii_digit()),
|
||||
"Version should be numeric"
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
let version = result.unwrap();
|
||||
assert_eq!(version, "1465660");
|
||||
}
|
||||
|
||||
println!("Chromium API test passed. Latest version: {version}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Chromium API test failed: {e}");
|
||||
panic!("Chromium API should work");
|
||||
}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn test_chromium_releases_with_caching() {
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let arch = if cfg!(target_arch = "aarch64") {
|
||||
"Mac_Arm"
|
||||
} else {
|
||||
"Mac"
|
||||
};
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("/{arch}/LAST_CHANGE")))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string("1465660")
|
||||
.insert_header("content-type", "text/plain"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_chromium_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].version, "1465660");
|
||||
assert!(!releases[0].is_prerelease);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tor_api() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(3000)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let client = ApiClient::new();
|
||||
let mock_html = r#"
|
||||
<html>
|
||||
<body>
|
||||
<a href="../">../</a>
|
||||
<a href="14.0.4/">14.0.4/</a>
|
||||
<a href="14.0.3/">14.0.3/</a>
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
// Use a timeout for this test since TOR API can be slow
|
||||
let timeout_duration = tokio::time::Duration::from_secs(30);
|
||||
let result = tokio::time::timeout(
|
||||
timeout_duration,
|
||||
client.fetch_tor_releases_with_caching(false),
|
||||
)
|
||||
.await;
|
||||
let version_html = r#"
|
||||
<html>
|
||||
<body>
|
||||
<a href="tor-browser-macos-14.0.4.dmg">tor-browser-macos-14.0.4.dmg</a>
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
match result {
|
||||
Ok(Ok(releases)) => {
|
||||
assert!(!releases.is_empty(), "Should have TOR releases");
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_html)
|
||||
.insert_header("content-type", "text/html"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let first_release = &releases[0];
|
||||
assert!(
|
||||
!first_release.version.is_empty(),
|
||||
"Version should not be empty"
|
||||
);
|
||||
assert!(
|
||||
first_release.download_url.is_some(),
|
||||
"Should have download URL"
|
||||
);
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/14.0.4/"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(version_html)
|
||||
.insert_header("content-type", "text/html"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
println!("TOR API test passed. Found {} releases", releases.len());
|
||||
println!("Latest version: {}", releases[0].version);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
println!("TOR API test failed: {e}");
|
||||
// Don't panic for TOR API since it can be unreliable
|
||||
println!("TOR API test skipped due to network issues");
|
||||
}
|
||||
Err(_) => {
|
||||
println!("TOR API test timed out after 30 seconds");
|
||||
// Don't panic for timeout, just skip
|
||||
println!("TOR API test skipped due to timeout");
|
||||
}
|
||||
}
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/14.0.3/"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(version_html.replace("14.0.4", "14.0.3"))
|
||||
.insert_header("content-type", "text/html"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_tor_releases_with_caching(true).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let releases = result.unwrap();
|
||||
assert!(!releases.is_empty());
|
||||
assert_eq!(releases[0].version, "14.0.4");
|
||||
assert!(releases[0].download_url.is_some());
|
||||
assert!(releases[0].download_url.as_ref().unwrap().contains(&server.uri()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tor_version_check() {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(3500)).await; // Rate limiting
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let version_html = r#"
|
||||
<html>
|
||||
<body>
|
||||
<a href="tor-browser-macos-14.0.4.dmg">tor-browser-macos-14.0.4.dmg</a>
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/14.0.4/"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(version_html)
|
||||
.insert_header("content-type", "text/html"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = ApiClient::new();
|
||||
let result = client.check_tor_version_has_macos("14.0.4").await;
|
||||
|
||||
match result {
|
||||
Ok(has_macos) => {
|
||||
assert!(has_macos, "Version 14.0.4 should have macOS support");
|
||||
println!("TOR version check test passed. Version 14.0.4 has macOS: {has_macos}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("TOR version check test failed: {e}");
|
||||
panic!("TOR version check should work");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tor_version_check_no_macos() {
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
let version_html = r#"
|
||||
<html>
|
||||
<body>
|
||||
<a href="tor-browser-linux-14.0.4.tar.xz">tor-browser-linux-14.0.4.tar.xz</a>
|
||||
</body>
|
||||
</html>
|
||||
"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/14.0.5/"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(version_html)
|
||||
.insert_header("content-type", "text/html"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.check_tor_version_has_macos("14.0.5").await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_alpha_version() {
|
||||
assert!(is_alpha_version("1.2.3a1"));
|
||||
assert!(is_alpha_version("137.0b5"));
|
||||
assert!(is_alpha_version("140.0rc1"));
|
||||
assert!(!is_alpha_version("139.0"));
|
||||
assert!(!is_alpha_version("1.2.3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_versions_comprehensive() {
|
||||
let mut versions = vec![
|
||||
"1.0.0".to_string(),
|
||||
"1.0.1".to_string(),
|
||||
"1.1.0".to_string(),
|
||||
"2.0.0a1".to_string(),
|
||||
"2.0.0b1".to_string(),
|
||||
"2.0.0rc1".to_string(),
|
||||
"2.0.0".to_string(),
|
||||
"10.0.0".to_string(),
|
||||
"1.0.0-twilight".to_string(),
|
||||
];
|
||||
|
||||
sort_versions(&mut versions);
|
||||
|
||||
// Twilight should be first, then normal semantic versioning
|
||||
assert_eq!(versions[0], "1.0.0-twilight");
|
||||
assert_eq!(versions[1], "10.0.0");
|
||||
assert_eq!(versions[2], "2.0.0");
|
||||
assert_eq!(versions[3], "2.0.0rc1");
|
||||
assert_eq!(versions[4], "2.0.0b1");
|
||||
assert_eq!(versions[5], "2.0.0a1");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling_404() {
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/firefox.json"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_firefox_releases_with_caching(true).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling_invalid_json() {
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/firefox.json"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string("invalid json")
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_firefox_releases_with_caching(true).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_github_api_rate_limit() {
|
||||
let server = setup_mock_server().await;
|
||||
let client = create_test_client(&server);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/zen-browser/desktop/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(429)
|
||||
.insert_header("retry-after", "60"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let result = client.fetch_zen_releases_with_caching(true).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+466
-69
@@ -34,6 +34,14 @@ impl Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_with_api_client(api_client: ApiClient) -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
api_client,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the actual download URL for browsers that need dynamic asset resolution
|
||||
pub async fn resolve_download_url(
|
||||
&self,
|
||||
@@ -44,7 +52,7 @@ impl Downloader {
|
||||
match browser_type {
|
||||
BrowserType::Brave => {
|
||||
// For Brave, we need to find the actual macOS asset
|
||||
let releases = self.api_client.fetch_brave_releases().await?;
|
||||
let releases = self.api_client.fetch_brave_releases_with_caching(true).await?;
|
||||
|
||||
// Find the release with the matching version
|
||||
let release = releases
|
||||
@@ -67,7 +75,7 @@ impl Downloader {
|
||||
}
|
||||
BrowserType::Zen => {
|
||||
// For Zen, verify the asset exists
|
||||
let releases = self.api_client.fetch_zen_releases().await?;
|
||||
let releases = self.api_client.fetch_zen_releases_with_caching(true).await?;
|
||||
|
||||
let release = releases
|
||||
.iter()
|
||||
@@ -87,7 +95,7 @@ impl Downloader {
|
||||
}
|
||||
BrowserType::MullvadBrowser => {
|
||||
// For Mullvad, verify the asset exists
|
||||
let releases = self.api_client.fetch_mullvad_releases().await?;
|
||||
let releases = self.api_client.fetch_mullvad_releases_with_caching(true).await?;
|
||||
|
||||
let release = releases
|
||||
.iter()
|
||||
@@ -112,9 +120,9 @@ impl Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_browser(
|
||||
pub async fn download_browser<R: tauri::Runtime>(
|
||||
&self,
|
||||
app_handle: &tauri::AppHandle,
|
||||
app_handle: &tauri::AppHandle<R>,
|
||||
browser_type: BrowserType,
|
||||
version: &str,
|
||||
download_info: &DownloadInfo,
|
||||
@@ -149,6 +157,11 @@ impl Downloader {
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// Check if the response is successful
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Download failed with status: {}", response.status()).into());
|
||||
}
|
||||
|
||||
let total_size = response.content_length();
|
||||
let mut downloaded = 0u64;
|
||||
let start_time = std::time::Instant::now();
|
||||
@@ -206,12 +219,60 @@ impl Downloader {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api_client::ApiClient;
|
||||
use crate::browser::BrowserType;
|
||||
use crate::browser_version_service::DownloadInfo;
|
||||
|
||||
use wiremock::{MockServer, Mock, ResponseTemplate};
|
||||
use wiremock::matchers::{method, path, header};
|
||||
use tempfile::TempDir;
|
||||
|
||||
async fn setup_mock_server() -> MockServer {
|
||||
MockServer::start().await
|
||||
}
|
||||
|
||||
fn create_test_api_client(server: &MockServer) -> ApiClient {
|
||||
let base_url = server.uri();
|
||||
ApiClient::new_with_base_urls(
|
||||
base_url.clone(), // firefox_api_base
|
||||
base_url.clone(), // firefox_dev_api_base
|
||||
base_url.clone(), // github_api_base
|
||||
base_url.clone(), // chromium_api_base
|
||||
base_url.clone(), // tor_archive_base
|
||||
base_url.clone(), // mozilla_download_base
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_brave_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "v1.81.9",
|
||||
"name": "Brave Release 1.81.9",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "brave-v1.81.9-universal.dmg",
|
||||
"browser_download_url": "https://example.com/brave-1.81.9-universal.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/brave/brave-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
// Test with a known Brave version
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "brave-test.dmg".to_string(),
|
||||
@@ -222,23 +283,40 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::Brave, "v1.81.9", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/brave/brave-browser"));
|
||||
assert!(url.contains(".dmg"));
|
||||
assert!(url.contains("universal"));
|
||||
println!("Brave download URL resolved: {url}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Brave URL resolution failed (expected if version doesn't exist): {e}");
|
||||
// This might fail if the version doesn't exist, which is okay for testing
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, "https://example.com/brave-1.81.9-universal.dmg");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_zen_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "1.11b",
|
||||
"name": "Zen Browser 1.11b",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "zen.macos-universal.dmg",
|
||||
"browser_download_url": "https://example.com/zen-1.11b-universal.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/zen-browser/desktop/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
@@ -250,21 +328,40 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::Zen, "1.11b", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/zen-browser/desktop"));
|
||||
assert!(url.contains("zen.macos-universal.dmg"));
|
||||
println!("Zen download URL resolved: {url}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Zen URL resolution failed (expected if version doesn't exist): {e}");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, "https://example.com/zen-1.11b-universal.dmg");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_mullvad_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "14.5a6",
|
||||
"name": "Mullvad Browser 14.5a6",
|
||||
"prerelease": true,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "mullvad-browser-macos-14.5a6.dmg",
|
||||
"browser_download_url": "https://example.com/mullvad-14.5a6.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/mullvad/mullvad-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
@@ -276,21 +373,16 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::MullvadBrowser, "14.5a6", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert!(url.contains("github.com/mullvad/mullvad-browser"));
|
||||
assert!(url.contains(".dmg"));
|
||||
println!("Mullvad download URL resolved: {url}");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Mullvad URL resolution failed (expected if version doesn't exist): {e}");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, "https://example.com/mullvad-14.5a6.dmg");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_firefox_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://download.mozilla.org/?product=firefox-139.0&os=osx&lang=en-US".to_string(),
|
||||
@@ -302,20 +394,16 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::Firefox, "139.0", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
println!("Firefox download URL (passthrough): {url}");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Firefox URL resolution should not fail: {e}");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, download_info.url);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_chromium_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Mac/1465660/chrome-mac.zip".to_string(),
|
||||
@@ -327,20 +415,16 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::Chromium, "1465660", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
println!("Chromium download URL (passthrough): {url}");
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("Chromium URL resolution should not fail: {e}");
|
||||
}
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, download_info.url);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_tor_download_url() {
|
||||
let downloader = Downloader::new();
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "https://archive.torproject.org/tor-package-archive/torbrowser/14.0.4/tor-browser-macos-14.0.4.dmg".to_string(),
|
||||
@@ -352,14 +436,327 @@ mod tests {
|
||||
.resolve_download_url(BrowserType::TorBrowser, "14.0.4", &download_info)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(url) => {
|
||||
assert_eq!(url, download_info.url);
|
||||
println!("TOR download URL (passthrough): {url}");
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, download_info.url);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_brave_version_not_found() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "v1.81.8",
|
||||
"name": "Brave Release 1.81.8",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "brave-v1.81.8-universal.dmg",
|
||||
"browser_download_url": "https://example.com/brave-1.81.8-universal.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("TOR URL resolution should not fail: {e}");
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/brave/brave-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "brave-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Brave, "v1.81.9", &download_info)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Brave version v1.81.9 not found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_zen_asset_not_found() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "1.11b",
|
||||
"name": "Zen Browser 1.11b",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "zen.linux-universal.tar.bz2",
|
||||
"browser_download_url": "https://example.com/zen-1.11b-linux.tar.bz2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/zen-browser/desktop/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "zen-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Zen, "1.11b", &download_info)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("No macOS universal asset found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_download_browser_with_progress() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
// Create a temporary directory for the test
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dest_path = temp_dir.path();
|
||||
|
||||
// Create test file content (simulating a small download)
|
||||
let test_content = b"This is a test file content for download simulation";
|
||||
|
||||
// Mock the download endpoint
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/test-download"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_bytes(test_content)
|
||||
.insert_header("content-length", test_content.len().to_string())
|
||||
.insert_header("content-type", "application/octet-stream"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: format!("{}/test-download", server.uri()),
|
||||
filename: "test-file.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
// Create a mock app handle for testing
|
||||
let app = tauri::test::mock_app();
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
let result = downloader
|
||||
.download_browser(
|
||||
&app_handle,
|
||||
BrowserType::Firefox,
|
||||
"139.0",
|
||||
&download_info,
|
||||
dest_path,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let downloaded_file = result.unwrap();
|
||||
assert!(downloaded_file.exists());
|
||||
|
||||
// Verify file content
|
||||
let downloaded_content = std::fs::read(&downloaded_file).unwrap();
|
||||
assert_eq!(downloaded_content, test_content);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_download_browser_network_error() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dest_path = temp_dir.path();
|
||||
|
||||
// Mock a 404 response
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/missing-file"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: format!("{}/missing-file", server.uri()),
|
||||
filename: "missing-file.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let app = tauri::test::mock_app();
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
let result = downloader
|
||||
.download_browser(
|
||||
&app_handle,
|
||||
BrowserType::Firefox,
|
||||
"139.0",
|
||||
&download_info,
|
||||
dest_path,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_mullvad_asset_not_found() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "14.5a6",
|
||||
"name": "Mullvad Browser 14.5a6",
|
||||
"prerelease": true,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "mullvad-browser-linux-14.5a6.tar.xz",
|
||||
"browser_download_url": "https://example.com/mullvad-14.5a6.tar.xz"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/mullvad/mullvad-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "mullvad-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::MullvadBrowser, "14.5a6", &download_info)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("No macOS asset found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_brave_version_with_v_prefix() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let mock_response = r#"[
|
||||
{
|
||||
"tag_name": "v1.81.9",
|
||||
"name": "Brave Release 1.81.9",
|
||||
"prerelease": false,
|
||||
"published_at": "2024-01-15T10:00:00Z",
|
||||
"assets": [
|
||||
{
|
||||
"name": "brave-v1.81.9-universal.dmg",
|
||||
"browser_download_url": "https://example.com/brave-1.81.9-universal.dmg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"#;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/repos/brave/brave-browser/releases"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_string(mock_response)
|
||||
.insert_header("content-type", "application/json"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: "placeholder".to_string(),
|
||||
filename: "brave-test.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
// Test with version without v prefix
|
||||
let result = downloader
|
||||
.resolve_download_url(BrowserType::Brave, "1.81.9", &download_info)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url, "https://example.com/brave-1.81.9-universal.dmg");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_download_browser_chunked_response() {
|
||||
let server = setup_mock_server().await;
|
||||
let api_client = create_test_api_client(&server);
|
||||
let downloader = Downloader::new_with_api_client(api_client);
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dest_path = temp_dir.path();
|
||||
|
||||
// Create larger test content to simulate chunked transfer
|
||||
let test_content = vec![42u8; 1024]; // 1KB of data
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/chunked-download"))
|
||||
.and(header("user-agent", "donutbrowser"))
|
||||
.respond_with(ResponseTemplate::new(200)
|
||||
.set_body_bytes(test_content.clone())
|
||||
.insert_header("content-length", test_content.len().to_string())
|
||||
.insert_header("content-type", "application/octet-stream"))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let download_info = DownloadInfo {
|
||||
url: format!("{}/chunked-download", server.uri()),
|
||||
filename: "chunked-file.dmg".to_string(),
|
||||
is_archive: true,
|
||||
};
|
||||
|
||||
let app = tauri::test::mock_app();
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
let result = downloader
|
||||
.download_browser(
|
||||
&app_handle,
|
||||
BrowserType::Chromium,
|
||||
"1465660",
|
||||
&download_info,
|
||||
dest_path,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let downloaded_file = result.unwrap();
|
||||
assert!(downloaded_file.exists());
|
||||
|
||||
let downloaded_content = std::fs::read(&downloaded_file).unwrap();
|
||||
assert_eq!(downloaded_content.len(), test_content.len());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user