Skip to main content

chrome_for_testing/api/
last_known_good_versions.rs

1use crate::api::channel::Channel;
2use crate::api::platform::Platform;
3use crate::api::version::Version;
4use crate::api::{API_BASE_URL, Download, DownloadsByPlatform, fetch_endpoint};
5use serde::de::Error as DeError;
6use serde::{Deserialize, Serialize};
7use std::borrow::Borrow;
8use std::collections::HashMap;
9
10/// JSON Example:
11/// ```json
12/// {
13///     "timestamp": "2025-01-05T22:09:08.729Z",
14///     "channels": {
15///         "Stable": {
16///             "channel": "Stable",
17///             "version": "131.0.6778.204",
18///             "revision": "1368529",
19///             "downloads": {
20///                 "chrome": [
21///                     {
22///                         "platform": "linux64",
23///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chrome-linux64.zip"
24///                     },
25///                     {
26///                         "platform": "mac-arm64",
27///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/mac-arm64/chrome-mac-arm64.zip"
28///                     },
29///                     {
30///                         "platform": "mac-x64",
31///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/mac-x64/chrome-mac-x64.zip"
32///                     },
33///                     {
34///                         "platform": "win32",
35///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/win32/chrome-win32.zip"
36///                     },
37///                     {
38///                         "platform": "win64",
39///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/win64/chrome-win64.zip"
40///                     }
41///                 ],
42///                 "chromedriver": [
43///                     {
44///                         "platform": "linux64",
45///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chromedriver-linux64.zip"
46///                     },
47///                     ...
48///                 ],
49///                 "chrome-headless-shell": [
50///                     {
51///                         "platform": "linux64",
52///                         "url": "https://storage.googleapis.com/chrome-for-testing-public/131.0.6778.204/linux64/chrome-headless-shell-linux64.zip"
53///                     },
54///                     ...
55///                 ]
56///             }
57///         },
58///         "Beta": {
59///             "channel": "Beta",
60///             "version": "132.0.6834.57",
61///             "revision": "1381561",
62///             "downloads": {
63///                 "chrome": [
64///                    ...
65///                 ],
66///                 "chromedriver": [
67///                     ...
68///                 ],
69///                 "chrome-headless-shell": [
70///                     ...
71///                 ]
72///             }
73///         },
74///         "Dev": { ... },
75///         "Canary": { ... }
76///     }
77/// }
78/// ```
79const LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH: &str =
80    "/chrome-for-testing/last-known-good-versions-with-downloads.json";
81
82/// Download links for Chrome, `ChromeDriver`, and Chrome Headless Shell binaries for various
83/// platforms.
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct Downloads {
86    /// Download links for Chrome binaries for various platforms.
87    pub chrome: Vec<Download>,
88
89    /// Download links for `ChromeDriver` binaries for various platforms.
90    pub chromedriver: Vec<Download>,
91
92    /// The "chrome-headless-shell" binary provides the "old" headless mode of Chrome, as described
93    /// in [this blog post](https://developer.chrome.com/blog/chrome-headless-shell).
94    /// For standard automated web-ui testing, you should pretty much always use the regular
95    /// `chrome` binary instead.
96    #[serde(rename = "chrome-headless-shell")]
97    pub chrome_headless_shell: Vec<Download>,
98}
99
100impl Downloads {
101    /// Returns the Chrome download entry for the given platform, if available.
102    #[must_use]
103    pub fn chrome_for_platform(&self, platform: Platform) -> Option<&Download> {
104        self.chrome.for_platform(platform)
105    }
106
107    /// Returns the `ChromeDriver` download entry for the given platform, if available.
108    #[must_use]
109    pub fn chromedriver_for_platform(&self, platform: Platform) -> Option<&Download> {
110        self.chromedriver.for_platform(platform)
111    }
112
113    /// Returns the Chrome Headless Shell download entry for the given platform, if available.
114    #[must_use]
115    pub fn chrome_headless_shell_for_platform(&self, platform: Platform) -> Option<&Download> {
116        self.chrome_headless_shell.for_platform(platform)
117    }
118}
119
120/// A Chrome version entry with channel information.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct VersionInChannel {
123    /// The release channel this version belongs to.
124    pub channel: Channel,
125
126    /// The version identifier.
127    pub version: Version,
128
129    /// The Chromium revision number.
130    pub revision: String,
131
132    /// Available downloads for this version.
133    pub downloads: Downloads,
134}
135
136fn deserialize_channels<'de, D>(
137    deserializer: D,
138) -> Result<HashMap<Channel, VersionInChannel>, D::Error>
139where
140    D: serde::Deserializer<'de>,
141{
142    let channels = HashMap::<Channel, VersionInChannel>::deserialize(deserializer)?;
143
144    for (key, value) in &channels {
145        if key != &value.channel {
146            return Err(D::Error::custom(format!(
147                "expected channels.{key}.channel to be {key}, got {}",
148                value.channel
149            )));
150        }
151    }
152
153    Ok(channels)
154}
155
156/// Response structure for the "last known good versions" API endpoint.
157///
158/// Contains the most recent version for each Chrome release channel (Stable, Beta, Dev, Canary).
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160pub struct LastKnownGoodVersions {
161    /// When this data was last updated.
162    #[serde(with = "time::serde::rfc3339")]
163    pub timestamp: time::OffsetDateTime,
164
165    /// The latest known good version for each release channel.
166    ///
167    /// The Chrome for Testing docs currently define Stable, Beta, Dev, and Canary for this
168    /// endpoint, but this remains a map so the crate can preserve newly-added upstream channels
169    /// instead of discarding them.
170    #[serde(deserialize_with = "deserialize_channels")]
171    channels: HashMap<Channel, VersionInChannel>,
172}
173
174impl LastKnownGoodVersions {
175    /// Fetches the last known good versions from the Chrome for Testing API.
176    ///
177    /// Returns the most recent version for each Chrome release channel (Stable, Beta, Dev, Canary).
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the HTTP request fails, the response has an unsuccessful status, or
182    /// deserialization fails.
183    pub async fn fetch(client: &reqwest::Client) -> crate::Result<Self> {
184        Self::fetch_with_base_url(client, &API_BASE_URL).await
185    }
186
187    /// Fetches from a custom base URL (useful for testing).
188    ///
189    /// # Errors
190    ///
191    /// Returns an error if the HTTP request fails, the response has an unsuccessful status, or
192    /// deserialization fails.
193    pub async fn fetch_with_base_url(
194        client: &reqwest::Client,
195        base_url: &reqwest::Url,
196    ) -> crate::Result<LastKnownGoodVersions> {
197        fetch_endpoint::<Self>(
198            client,
199            base_url,
200            LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH,
201            "LastKnownGoodVersions",
202        )
203        .await
204    }
205
206    /// Returns the version info for the given channel.
207    #[must_use]
208    pub fn channel(&self, channel: impl Borrow<Channel>) -> Option<&VersionInChannel> {
209        self.channels.get(channel.borrow())
210    }
211
212    /// Returns the latest known good versions by release channel.
213    #[must_use]
214    pub fn channels(&self) -> &HashMap<Channel, VersionInChannel> {
215        &self.channels
216    }
217
218    /// Returns the Stable channel version info, if present.
219    #[must_use]
220    pub fn stable(&self) -> Option<&VersionInChannel> {
221        self.channel(Channel::Stable)
222    }
223
224    /// Returns the Beta channel version info, if present.
225    #[must_use]
226    pub fn beta(&self) -> Option<&VersionInChannel> {
227        self.channel(Channel::Beta)
228    }
229
230    /// Returns the Dev channel version info, if present.
231    #[must_use]
232    pub fn dev(&self) -> Option<&VersionInChannel> {
233        self.channel(Channel::Dev)
234    }
235
236    /// Returns the Canary channel version info, if present.
237    #[must_use]
238    pub fn canary(&self) -> Option<&VersionInChannel> {
239        self.channel(Channel::Canary)
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use crate::api::Download;
246    use crate::api::channel::Channel;
247    use crate::api::last_known_good_versions::{
248        Downloads, LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH, LastKnownGoodVersions,
249        VersionInChannel,
250    };
251    use crate::api::platform::Platform;
252    use crate::api::version::Version;
253    use crate::error::Error;
254    use assertr::prelude::*;
255    use std::collections::HashMap;
256    use time::macros::datetime;
257    use url::Url;
258
259    // This test should not be `#[ignore]`, even though it hits the Chrome For Testing API.
260    #[tokio::test]
261    async fn can_request_from_real_world_endpoint() {
262        let result = LastKnownGoodVersions::fetch(&reqwest::Client::new()).await;
263        assert_that!(result).is_ok();
264    }
265
266    //noinspection DuplicatedCode
267    #[tokio::test]
268    #[allow(clippy::too_many_lines)]
269    async fn can_query_last_known_good_versions_api_endpoint_and_deserialize_response() {
270        let mut server = mockito::Server::new_async().await;
271        let _mock = server
272            .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
273            .with_status(200)
274            .with_header("content-type", "application/json")
275            .with_body(include_str!(
276                "./../../test-data/last_known_good_versions_with_downloads_test_response.json"
277            ))
278            .create();
279
280        let url: Url = server.url().parse().unwrap();
281
282        let data = LastKnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
283            .await
284            .unwrap();
285
286        assert_that!(data).is_equal_to(LastKnownGoodVersions {
287            timestamp: datetime!(2026-04-13 08:53:52.841 UTC),
288            channels: HashMap::from([
289                (
290                    Channel::Stable,
291                    VersionInChannel {
292                    channel: Channel::Stable,
293                    version: Version { major: 147, minor: 0, patch: 7727, build: 56 },
294                    revision: String::from("1596535"),
295                    downloads: Downloads {
296                        chrome: vec![
297                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/linux64/chrome-linux64.zip") },
298                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-arm64/chrome-mac-arm64.zip") },
299                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-x64/chrome-mac-x64.zip") },
300                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/win32/chrome-win32.zip") },
301                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/win64/chrome-win64.zip") },
302                        ],
303                        chromedriver: vec![
304                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/linux64/chromedriver-linux64.zip") },
305                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-arm64/chromedriver-mac-arm64.zip") },
306                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-x64/chromedriver-mac-x64.zip") },
307                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/win32/chromedriver-win32.zip") },
308                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/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/147.0.7727.56/linux64/chrome-headless-shell-linux64.zip") },
312                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
313                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/mac-x64/chrome-headless-shell-mac-x64.zip") },
314                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/win32/chrome-headless-shell-win32.zip") },
315                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/147.0.7727.56/win64/chrome-headless-shell-win64.zip") },
316                        ],
317                    },
318                    }
319                ),
320                (Channel::Beta, VersionInChannel {
321                    channel: Channel::Beta,
322                    version: Version { major: 148, minor: 0, patch: 7778, build: 5 },
323                    revision: String::from("1610480"),
324                    downloads: Downloads {
325                        chrome: vec![
326                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/linux64/chrome-linux64.zip") },
327                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-arm64/chrome-mac-arm64.zip") },
328                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-x64/chrome-mac-x64.zip") },
329                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win32/chrome-win32.zip") },
330                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win64/chrome-win64.zip") },
331                        ],
332                        chromedriver: vec![
333                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/linux64/chromedriver-linux64.zip") },
334                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-arm64/chromedriver-mac-arm64.zip") },
335                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-x64/chromedriver-mac-x64.zip") },
336                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win32/chromedriver-win32.zip") },
337                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win64/chromedriver-win64.zip") },
338                        ],
339                        chrome_headless_shell: vec![
340                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/linux64/chrome-headless-shell-linux64.zip") },
341                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
342                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/mac-x64/chrome-headless-shell-mac-x64.zip") },
343                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win32/chrome-headless-shell-win32.zip") },
344                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.5/win64/chrome-headless-shell-win64.zip") },
345                        ],
346                    },
347                }),
348                (Channel::Dev, VersionInChannel {
349                    channel: Channel::Dev,
350                    version: Version { major: 148, minor: 0, patch: 7766, build: 3 },
351                    revision: String::from("1607787"),
352                    downloads: Downloads {
353                        chrome: vec![
354                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/linux64/chrome-linux64.zip") },
355                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-arm64/chrome-mac-arm64.zip") },
356                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-x64/chrome-mac-x64.zip") },
357                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win32/chrome-win32.zip") },
358                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win64/chrome-win64.zip") },
359                        ],
360                        chromedriver: vec![
361                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/linux64/chromedriver-linux64.zip") },
362                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-arm64/chromedriver-mac-arm64.zip") },
363                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-x64/chromedriver-mac-x64.zip") },
364                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win32/chromedriver-win32.zip") },
365                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win64/chromedriver-win64.zip") },
366                        ],
367                        chrome_headless_shell: vec![
368                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/linux64/chrome-headless-shell-linux64.zip") },
369                            Download { platform: Platform::MacArm64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-arm64/chrome-headless-shell-mac-arm64.zip") },
370                            Download { platform: Platform::MacX64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/mac-x64/chrome-headless-shell-mac-x64.zip") },
371                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win32/chrome-headless-shell-win32.zip") },
372                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/148.0.7766.3/win64/chrome-headless-shell-win64.zip") },
373                        ],
374                    },
375                }),
376                (Channel::Canary, VersionInChannel {
377                    channel: Channel::Canary,
378                    version: Version { major: 149, minor: 0, patch: 7789, build: 0 },
379                    revision: String::from("1613465"),
380                    downloads: Downloads {
381                        chrome: vec![
382                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chrome-linux64.zip") },
383                            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") },
384                            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") },
385                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chrome-win32.zip") },
386                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chrome-win64.zip") },
387                        ],
388                        chromedriver: vec![
389                            Download { platform: Platform::Linux64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/linux64/chromedriver-linux64.zip") },
390                            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") },
391                            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") },
392                            Download { platform: Platform::Win32, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win32/chromedriver-win32.zip") },
393                            Download { platform: Platform::Win64, url: String::from("https://storage.googleapis.com/chrome-for-testing-public/149.0.7789.0/win64/chromedriver-win64.zip") },
394                        ],
395                        chrome_headless_shell: vec![
396                            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") },
397                            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") },
398                            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") },
399                            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") },
400                            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") },
401                        ],
402                    },
403                }),
404            ]),
405        });
406    }
407
408    #[tokio::test]
409    async fn unsuccessful_http_status_is_reported_as_request_error() {
410        let mut server = mockito::Server::new_async().await;
411        let _mock = server
412            .mock("GET", LAST_KNOWN_GOOD_VERSIONS_WITH_DOWNLOADS_JSON_PATH)
413            .with_status(500)
414            .with_header("content-type", "application/json")
415            .with_body(include_str!(
416                "./../../test-data/last_known_good_versions_with_downloads_test_response.json"
417            ))
418            .create();
419
420        let url: Url = server.url().parse().unwrap();
421
422        let err = LastKnownGoodVersions::fetch_with_base_url(&reqwest::Client::new(), &url)
423            .await
424            .unwrap_err();
425
426        let Error::Request(request_error) = err.current_context() else {
427            panic!("expected request error, got: {:?}", err.current_context());
428        };
429
430        assert_that!(request_error.status())
431            .is_equal_to(Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR));
432    }
433
434    #[test]
435    fn deserialization_rejects_channel_mismatch() {
436        let json = include_str!(
437            "./../../test-data/last_known_good_versions_with_downloads_test_response.json"
438        )
439        .replacen(r#""channel": "Stable""#, r#""channel": "Beta""#, 1);
440
441        let result = serde_json::from_str::<LastKnownGoodVersions>(&json);
442
443        assert_that!(result)
444            .is_err()
445            .derive(|it| it.to_string())
446            .contains("expected channels.Stable.channel to be Stable, got Beta");
447    }
448
449    #[test]
450    fn deserialization_preserves_unknown_channels() {
451        let json = include_str!(
452            "./../../test-data/last_known_good_versions_with_downloads_test_response.json"
453        )
454        .replacen(r#""Canary": {"#, r#""Extended": {"#, 1)
455        .replacen(r#""channel": "Canary""#, r#""channel": "Extended""#, 1);
456
457        let data = serde_json::from_str::<LastKnownGoodVersions>(&json).unwrap();
458        let extended = Channel::Other(String::from("Extended"));
459
460        assert_that!(data.canary()).is_none();
461        assert_that!(data.channel(&extended))
462            .is_some()
463            .derive(|it| it.channel.clone())
464            .is_equal_to(extended);
465    }
466}