1use crate::api::platform::Platform;
2use crate::api::version::Version;
3use crate::api::{API_BASE_URL, Download, DownloadsByPlatform, fetch_endpoint};
4use serde::{Deserialize, Serialize};
5
6const KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH: &str =
37 "/chrome-for-testing/known-good-versions-with-downloads.json";
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Downloads {
42 pub chrome: Vec<Download>,
44
45 pub chromedriver: Option<Vec<Download>>,
48
49 #[serde(rename = "chrome-headless-shell")]
52 pub chrome_headless_shell: Option<Vec<Download>>,
53}
54
55impl Downloads {
56 #[must_use]
58 pub fn chrome_for_platform(&self, platform: Platform) -> Option<&Download> {
59 self.chrome.for_platform(platform)
60 }
61
62 #[must_use]
64 pub fn chromedriver_for_platform(&self, platform: Platform) -> Option<&Download> {
65 self.chromedriver.as_deref()?.for_platform(platform)
66 }
67
68 #[must_use]
70 pub fn chrome_headless_shell_for_platform(&self, platform: Platform) -> Option<&Download> {
71 self.chrome_headless_shell
72 .as_deref()?
73 .for_platform(platform)
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct VersionWithoutChannel {
82 pub version: Version,
84
85 pub revision: String,
87
88 pub downloads: Downloads,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct KnownGoodVersions {
99 #[serde(with = "time::serde::rfc3339")]
101 pub timestamp: time::OffsetDateTime,
102
103 pub versions: Vec<VersionWithoutChannel>,
105}
106
107impl KnownGoodVersions {
108 pub async fn fetch(client: &reqwest::Client) -> crate::Result<Self> {
119 Self::fetch_with_base_url(client, &API_BASE_URL).await
120 }
121
122 pub async fn fetch_with_base_url(
129 client: &reqwest::Client,
130 base_url: &reqwest::Url,
131 ) -> crate::Result<Self> {
132 fetch_endpoint::<Self>(
133 client,
134 base_url,
135 KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH,
136 "KnownGoodVersions",
137 )
138 .await
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::api::Download;
146 use crate::api::known_good_versions::KnownGoodVersions;
147 use crate::api::platform::Platform;
148 use crate::api::version::Version;
149 use crate::error::Error;
150 use assertr::prelude::*;
151 use time::macros::datetime;
152 use url::Url;
153
154 #[tokio::test]
156 async fn can_request_from_real_world_endpoint() {
157 let result = KnownGoodVersions::fetch(&reqwest::Client::new()).await;
158 assert_that!(result).is_ok();
159 }
160
161 #[tokio::test]
163 async fn can_query_known_good_versions_api_endpoint_and_deserialize_response() {
164 let mut server = mockito::Server::new_async().await;
165 let _mock = server
166 .mock("GET", KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
167 .with_status(200)
168 .with_header("content-type", "application/json")
169 .with_body(include_str!(
170 "./../../test-data/known_good_versions_with_downloads_test_response.json"
171 ))
172 .create();
173
174 let mock_url: Url = server.url().parse().unwrap();
175
176 let data = KnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &mock_url)
177 .await
178 .unwrap();
179
180 assert_that!(data).is_equal_to(KnownGoodVersions {
181 timestamp: datetime!(2026-04-13 08:53:52.847 UTC),
182 versions: vec![
183 VersionWithoutChannel {
184 version: Version { major: 113, minor: 0, patch: 5672, build: 0 },
185 revision: String::from("1121455"),
186 downloads: Downloads {
187 chrome: vec![
188 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/linux64/chrome-linux64.zip") },
189 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/mac-arm64/chrome-mac-arm64.zip") },
190 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/mac-x64/chrome-mac-x64.zip") },
191 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/win32/chrome-win32.zip") },
192 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/113.0.5672.0/win64/chrome-win64.zip") },
193 ],
194 chromedriver: None,
195 chrome_headless_shell: None,
196 },
197 },
198 VersionWithoutChannel {
199 version: Version { major: 149, minor: 0, patch: 7789, build: 0 },
200 revision: String::from("1613465"),
201 downloads: Downloads {
202 chrome: vec![
203 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chrome-linux64.zip") },
204 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chrome-mac-arm64.zip") },
205 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chrome-mac-x64.zip") },
206 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chrome-win32.zip") },
207 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chrome-win64.zip") },
208 ],
209 chromedriver: Some(vec![
210 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chromedriver-linux64.zip") },
211 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chromedriver-mac-arm64.zip") },
212 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chromedriver-mac-x64.zip") },
213 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chromedriver-win32.zip") },
214 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chromedriver-win64.zip") },
215 ]),
216 chrome_headless_shell: Some(vec![
217 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chrome-headless-shell-linux64.zip") },
218 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
219 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/mac-x64/chrome-headless-shell-mac-x64.zip") },
220 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chrome-headless-shell-win32.zip") },
221 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chrome-headless-shell-win64.zip") },
222 ]),
223 },
224 },
225 ],
226 });
227 }
228
229 #[tokio::test]
230 async fn unsuccessful_http_status_is_reported_as_request_error() {
231 let mut server = mockito::Server::new_async().await;
232 let _mock = server
233 .mock("GET", KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
234 .with_status(500)
235 .with_header("content-type", "application/json")
236 .with_body(include_str!(
237 "./../../test-data/known_good_versions_with_downloads_test_response.json"
238 ))
239 .create();
240
241 let url: Url = server.url().parse().unwrap();
242
243 let err = KnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
244 .await
245 .unwrap_err();
246
247 let Error::Request(request_error) = err.current_context() else {
248 panic!("expected request error, got: {:?}", err.current_context());
249 };
250
251 assert_that!(request_error.status())
252 .is_equal_to(Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR));
253 }
254}