1use crate::api::channel::Channel;
2use crate::api::version::Version;
3use crate::api::{API_BASE_URL, Download};
4use crate::error::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8const LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH: &str =
78 "/chrome-for-testing/last-known-good-versions-with-downloads.json";
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83pub struct Downloads {
84 pub chrome: Vec<Download>,
86
87 pub chromedriver: Vec<Download>,
89
90 #[serde(rename = "chrome-headless-shell")]
95 pub chrome_headless_shell: Vec<Download>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct VersionInChannel {
101 pub channel: Channel,
103
104 pub version: Version,
106
107 pub revision: String,
109
110 pub downloads: Downloads,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct LastKnownGoodVersions {
119 #[serde(with = "time::serde::rfc3339")]
121 pub timestamp: time::OffsetDateTime,
122
123 pub channels: HashMap<Channel, VersionInChannel>,
125}
126
127impl LastKnownGoodVersions {
128 pub async fn fetch(client: &reqwest::Client) -> Result<Self> {
136 Self::fetch_with_base_url(client, &API_BASE_URL).await
137 }
138
139 pub async fn fetch_with_base_url(
145 client: &reqwest::Client,
146 base_url: &reqwest::Url,
147 ) -> Result<LastKnownGoodVersions> {
148 let last_known_good_versions = client
149 .get(base_url.join(LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)?)
150 .send()
151 .await?
152 .json::<Self>()
153 .await?;
154 Ok(last_known_good_versions)
155 }
156
157 #[must_use]
159 pub fn channel(&self, channel: Channel) -> Option<&VersionInChannel> {
160 self.channels.get(&channel)
161 }
162
163 #[must_use]
165 pub fn stable(&self) -> Option<&VersionInChannel> {
166 self.channel(Channel::Stable)
167 }
168
169 #[must_use]
171 pub fn beta(&self) -> Option<&VersionInChannel> {
172 self.channel(Channel::Beta)
173 }
174
175 #[must_use]
177 pub fn dev(&self) -> Option<&VersionInChannel> {
178 self.channel(Channel::Dev)
179 }
180
181 #[must_use]
183 pub fn canary(&self) -> Option<&VersionInChannel> {
184 self.channel(Channel::Canary)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use crate::api::Download;
191 use crate::api::channel::Channel;
192 use crate::api::last_known_good_versions::{
193 Downloads, LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH, LastKnownGoodVersions,
194 VersionInChannel,
195 };
196 use crate::api::platform::Platform;
197 use crate::api::version::Version;
198 use assertr::prelude::*;
199 use std::collections::HashMap;
200 use time::macros::datetime;
201 use url::Url;
202
203 #[tokio::test]
204 async fn can_request_from_real_world_endpoint() {
205 let result = LastKnownGoodVersions::fetch(&reqwest::Client::new()).await;
206 assert_that!(result).is_ok();
207 }
208
209 #[tokio::test]
211 async fn can_query_last_known_good_versions_api_endpoint_and_deserialize_response() {
212 let mut server = mockito::Server::new_async().await;
213
214 let _mock = server
215 .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
216 .with_status(200)
217 .with_header("content-type", "application/json")
218 .with_body(include_str!(
219 "./../../test-data/last_known_good_versions_test_response.json"
220 ))
221 .create();
222
223 let url: Url = server.url().parse().unwrap();
224
225 let data = LastKnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
226 .await
227 .unwrap();
228
229 assert_that!(data).is_equal_to(LastKnownGoodVersions {
230 timestamp: datetime!(2025-01-17 10:09:31.683 UTC),
231 channels: HashMap::from([
232 (
233 Channel::Stable,
234 VersionInChannel {
235 channel: Channel::Stable,
236 version: Version { major: 132, minor: 0, patch: 6834, build: 83 },
237 revision: String::from("1381561"),
238 downloads: Downloads {
239 chrome: vec![
240 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-linux64.zip") },
241 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chrome-mac-arm64.zip") },
242 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chrome-mac-x64.zip") },
243 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-win32.zip") },
244 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-win64.zip") },
245 ],
246 chromedriver: vec![
247 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chromedriver-linux64.zip") },
248 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chromedriver-mac-arm64.zip") },
249 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chromedriver-mac-x64.zip") },
250 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chromedriver-win32.zip") },
251 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chromedriver-win64.zip") },
252 ],
253 chrome_headless_shell: vec![
254 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/linux64/chrome-headless-shell-linux64.zip") },
255 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
256 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/mac-x64/chrome-headless-shell-mac-x64.zip") },
257 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win32/chrome-headless-shell-win32.zip") },
258 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.83/win64/chrome-headless-shell-win64.zip") },
259 ],
260 },
261 }
262 ),
263 (Channel::Beta, VersionInChannel {
264 channel: Channel::Beta,
265 version: Version { major: 133, minor: 0, patch: 6943, build: 16 },
266 revision: String::from("1402768"),
267 downloads: Downloads {
268 chrome: vec![
269 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-linux64.zip") },
270 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chrome-mac-arm64.zip") },
271 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chrome-mac-x64.zip") },
272 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-win32.zip") },
273 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-win64.zip") },
274 ],
275 chromedriver: vec![
276 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chromedriver-linux64.zip") },
277 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chromedriver-mac-arm64.zip") },
278 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chromedriver-mac-x64.zip") },
279 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chromedriver-win32.zip") },
280 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chromedriver-win64.zip") },
281 ],
282 chrome_headless_shell: vec![
283 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/linux64/chrome-headless-shell-linux64.zip") },
284 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
285 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/mac-x64/chrome-headless-shell-mac-x64.zip") },
286 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win32/chrome-headless-shell-win32.zip") },
287 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/133.0.6943.16/win64/chrome-headless-shell-win64.zip") },
288 ],
289 },
290 }),
291 (Channel::Dev, VersionInChannel {
292 channel: Channel::Dev,
293 version: Version { major: 134, minor: 0, patch: 6958, build: 2 },
294 revision: String::from("1406477"),
295 downloads: Downloads {
296 chrome: vec![
297 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-linux64.zip") },
298 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chrome-mac-arm64.zip") },
299 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chrome-mac-x64.zip") },
300 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-win32.zip") },
301 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-win64.zip") },
302 ],
303 chromedriver: vec![
304 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chromedriver-linux64.zip") },
305 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chromedriver-mac-arm64.zip") },
306 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chromedriver-mac-x64.zip") },
307 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chromedriver-win32.zip") },
308 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chromedriver-win64.zip") },
309 ],
310 chrome_headless_shell: vec![
311 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/linux64/chrome-headless-shell-linux64.zip") },
312 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
313 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/mac-x64/chrome-headless-shell-mac-x64.zip") },
314 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win32/chrome-headless-shell-win32.zip") },
315 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6958.2/win64/chrome-headless-shell-win64.zip") },
316 ],
317 },
318 }),
319 (Channel::Canary, VersionInChannel {
320 channel: Channel::Canary,
321 version: Version { major: 134, minor: 0, patch: 6962, build: 0 },
322 revision: String::from("1407692"),
323 downloads: Downloads {
324 chrome: vec![
325 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-linux64.zip") },
326 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chrome-mac-arm64.zip") },
327 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chrome-mac-x64.zip") },
328 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-win32.zip") },
329 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-win64.zip") },
330 ],
331 chromedriver: vec![
332 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chromedriver-linux64.zip") },
333 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chromedriver-mac-arm64.zip") },
334 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chromedriver-mac-x64.zip") },
335 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chromedriver-win32.zip") },
336 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chromedriver-win64.zip") },
337 ],
338 chrome_headless_shell: vec![
339 Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/linux64/chrome-headless-shell-linux64.zip") },
340 Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
341 Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/mac-x64/chrome-headless-shell-mac-x64.zip") },
342 Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win32/chrome-headless-shell-win32.zip") },
343 Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/134.0.6962.0/win64/chrome-headless-shell-win64.zip") },
344 ],
345 },
346 })
347 ]),
348 });
349 }
350}