node_js_release_info/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod arch;
4mod error;
5mod ext;
6mod os;
7mod specs;
8mod url;
9
10pub use crate::arch::NodeJSArch;
11pub use crate::error::NodeJSRelInfoError;
12pub use crate::ext::NodeJSPkgExt;
13pub use crate::os::NodeJSOS;
14use crate::url::NodeJSURLFormatter;
15#[cfg(feature = "json")]
16use serde::{Deserialize, Serialize};
17use std::string::ToString;
18
19#[derive(Clone, Debug, Default, PartialEq)]
20#[cfg_attr(feature = "json", derive(Deserialize, Serialize))]
21pub struct NodeJSRelInfo {
22    /// The operating system for the Node.js distributable you are targeting
23    pub os: NodeJSOS,
24    /// The CPU architecture for the Node.js distributable you are targeting
25    pub arch: NodeJSArch,
26    /// The file extension for the Node.js distributable you are targeting
27    pub ext: NodeJSPkgExt,
28    /// The version of Node.js you are targeting as a [semver](https://semver.org) string
29    pub version: String,
30    /// The filename of the Node.js distributable (populated after fetching)
31    pub filename: String,
32    /// The hash for the Node.js distributable (populated after fetching)
33    pub sha256: String,
34    /// The fully qualified url for the Node.js distributable (populated after fetching)
35    pub url: String,
36    #[cfg_attr(feature = "json", serde(skip))]
37    url_fmt: NodeJSURLFormatter,
38}
39
40impl NodeJSRelInfo {
41    /// Creates a new instance using default settings
42    ///
43    /// # Arguments
44    ///
45    /// * `semver` - The Node.js version you are targeting (`String` / `&str`)
46    ///
47    /// # Examples
48    ///
49    /// ```rust
50    /// use node_js_release_info::NodeJSRelInfo;
51    /// let info = NodeJSRelInfo::new("20.6.1");
52    /// ```
53    pub fn new<T: AsRef<str>>(semver: T) -> Self {
54        NodeJSRelInfo {
55            version: semver.as_ref().to_owned(),
56            ..Default::default()
57        }
58    }
59
60    /// Creates a new instance mirroring current environment based on `std::env::consts::OS` and `std::env::consts::ARCH`
61    ///
62    /// # Arguments
63    ///
64    /// * `semver` - The Node.js version you are targeting (`String` / `&str`)
65    ///
66    /// # Examples
67    ///
68    /// ```rust
69    /// use node_js_release_info::NodeJSRelInfo;
70    /// let info = NodeJSRelInfo::from_env("20.6.1");
71    /// ```
72    pub fn from_env<T: AsRef<str>>(semver: T) -> Result<NodeJSRelInfo, NodeJSRelInfoError> {
73        let mut info = NodeJSRelInfo::new(semver);
74        info.os = NodeJSOS::from_env()?;
75        info.arch = NodeJSArch::from_env()?;
76        info.ext = match info.os {
77            NodeJSOS::Windows => NodeJSPkgExt::Zip,
78            _ => NodeJSPkgExt::Targz,
79        };
80        Ok(info)
81    }
82
83    /// Sets instance `os` field to `darwin`
84    ///
85    /// # Examples
86    ///
87    /// ```rust
88    /// use node_js_release_info::NodeJSRelInfo;
89    /// let info = NodeJSRelInfo::new("20.6.1").macos();
90    /// ```
91    pub fn macos(&mut self) -> &mut Self {
92        self.os = NodeJSOS::Darwin;
93        self
94    }
95
96    /// Sets instance `os` field to `linux`
97    ///
98    /// # Examples
99    ///
100    /// ```rust
101    /// use node_js_release_info::NodeJSRelInfo;
102    /// let info = NodeJSRelInfo::new("20.6.1").linux();
103    /// ```
104    pub fn linux(&mut self) -> &mut Self {
105        self.os = NodeJSOS::Linux;
106        self
107    }
108
109    /// Sets instance `os` field to `windows`
110    ///
111    /// # Examples
112    ///
113    /// ```rust
114    /// use node_js_release_info::NodeJSRelInfo;
115    /// let info = NodeJSRelInfo::new("20.6.1").windows();
116    /// ```
117    pub fn windows(&mut self) -> &mut Self {
118        self.os = NodeJSOS::Windows;
119        self
120    }
121
122    /// Sets instance `os` field to `aix`
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use node_js_release_info::NodeJSRelInfo;
128    /// let info = NodeJSRelInfo::new("20.6.1").aix();
129    /// ```
130    pub fn aix(&mut self) -> &mut Self {
131        self.os = NodeJSOS::AIX;
132        self
133    }
134
135    /// Sets instance `arch` field to `x64`
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use node_js_release_info::NodeJSRelInfo;
141    /// let info = NodeJSRelInfo::new("20.6.1").x64();
142    /// ```
143    pub fn x64(&mut self) -> &mut Self {
144        self.arch = NodeJSArch::X64;
145        self
146    }
147
148    /// Sets instance `arch` field to `x86`
149    ///
150    /// # Examples
151    ///
152    /// ```rust
153    /// use node_js_release_info::NodeJSRelInfo;
154    /// let info = NodeJSRelInfo::new("20.6.1").x86();
155    /// ```
156    pub fn x86(&mut self) -> &mut Self {
157        self.arch = NodeJSArch::X86;
158        self
159    }
160
161    /// Sets instance `arch` field to `arm64`
162    ///
163    /// # Examples
164    ///
165    /// ```rust
166    /// use node_js_release_info::NodeJSRelInfo;
167    /// let info = NodeJSRelInfo::new("20.6.1").arm64();
168    /// ```
169    pub fn arm64(&mut self) -> &mut Self {
170        self.arch = NodeJSArch::ARM64;
171        self
172    }
173
174    /// Sets instance `arch` field to `armv7l`
175    ///
176    /// # Examples
177    ///
178    /// ```rust
179    /// use node_js_release_info::NodeJSRelInfo;
180    /// let info = NodeJSRelInfo::new("20.6.1").armv7l();
181    /// ```
182    pub fn armv7l(&mut self) -> &mut Self {
183        self.arch = NodeJSArch::ARMV7L;
184        self
185    }
186
187    /// Sets instance `arch` field to `ppc64`
188    ///
189    /// # Examples
190    ///
191    /// ```rust
192    /// use node_js_release_info::NodeJSRelInfo;
193    /// let info = NodeJSRelInfo::new("20.6.1").ppc64();
194    /// ```
195    pub fn ppc64(&mut self) -> &mut Self {
196        self.arch = NodeJSArch::PPC64;
197        self
198    }
199
200    /// Sets instance `arch` field to `ppc64le`
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// use node_js_release_info::NodeJSRelInfo;
206    /// let info = NodeJSRelInfo::new("20.6.1").ppc64le();
207    /// ```
208    pub fn ppc64le(&mut self) -> &mut Self {
209        self.arch = NodeJSArch::PPC64LE;
210        self
211    }
212
213    /// Sets instance `arch` field to `s390x`
214    ///
215    /// # Examples
216    ///
217    /// ```rust
218    /// use node_js_release_info::NodeJSRelInfo;
219    /// let info = NodeJSRelInfo::new("20.6.1").s390x();
220    /// ```
221    pub fn s390x(&mut self) -> &mut Self {
222        self.arch = NodeJSArch::S390X;
223        self
224    }
225
226    /// Sets instance `ext` field to `tar.gz`
227    ///
228    /// # Examples
229    ///
230    /// ```rust
231    /// use node_js_release_info::NodeJSRelInfo;
232    /// let info = NodeJSRelInfo::new("20.6.1").tar_gz();
233    /// ```
234    pub fn tar_gz(&mut self) -> &mut Self {
235        self.ext = NodeJSPkgExt::Targz;
236        self
237    }
238
239    /// Sets instance `ext` field to `tar.xz`
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use node_js_release_info::NodeJSRelInfo;
245    /// let info = NodeJSRelInfo::new("20.6.1").tar_xz();
246    /// ```
247    pub fn tar_xz(&mut self) -> &mut Self {
248        self.ext = NodeJSPkgExt::Tarxz;
249        self
250    }
251
252    /// Sets instance `ext` field to `zip`
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use node_js_release_info::NodeJSRelInfo;
258    /// let info = NodeJSRelInfo::new("20.6.1").zip();
259    /// ```
260    pub fn zip(&mut self) -> &mut Self {
261        self.ext = NodeJSPkgExt::Zip;
262        self
263    }
264
265    /// Sets instance `ext` field to `7z`
266    ///
267    /// # Examples
268    ///
269    /// ```rust
270    /// use node_js_release_info::NodeJSRelInfo;
271    /// let info = NodeJSRelInfo::new("20.6.1").s7z();
272    /// ```
273    pub fn s7z(&mut self) -> &mut Self {
274        self.ext = NodeJSPkgExt::S7z;
275        self
276    }
277
278    /// Sets instance `ext` field to `msi`
279    ///
280    /// # Examples
281    ///
282    /// ```rust
283    /// use node_js_release_info::NodeJSRelInfo;
284    /// let info = NodeJSRelInfo::new("20.6.1").msi();
285    /// ```
286    pub fn msi(&mut self) -> &mut Self {
287        self.ext = NodeJSPkgExt::Msi;
288        self
289    }
290
291    /// Creates owned data from reference for convenience when chaining
292    ///
293    /// # Examples
294    ///
295    /// ```rust
296    /// use node_js_release_info::NodeJSRelInfo;
297    /// let info = NodeJSRelInfo::new("20.6.1").windows().x64().zip().to_owned();
298    /// ```
299    pub fn to_owned(&self) -> Self {
300        self.clone()
301    }
302
303    /// Fetches Node.js metadata for specified configuration from the
304    /// [releases download server](https://nodejs.org/download/release/)
305    ///
306    /// # Examples
307    ///
308    /// ```rust
309    /// use node_js_release_info::{NodeJSRelInfo, NodeJSRelInfoError};
310    ///
311    /// #[tokio::main]
312    /// async fn main() -> Result<(), NodeJSRelInfoError> {
313    ///   let info = NodeJSRelInfo::new("20.6.1").macos().arm64().fetch().await?;
314    ///   assert_eq!(info.version, "20.6.1");
315    ///   assert_eq!(info.filename, "node-v20.6.1-darwin-arm64.tar.gz");
316    ///   assert_eq!(info.sha256, "d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46");
317    ///   assert_eq!(info.url, "https://nodejs.org/download/release/v20.6.1/node-v20.6.1-darwin-arm64.tar.gz");
318    ///   Ok(())
319    /// }
320    /// ```
321    pub async fn fetch(&mut self) -> Result<Self, NodeJSRelInfoError> {
322        let version = specs::validate_version(self.version.as_str())?;
323        let specs = specs::fetch(&version, &self.url_fmt).await?;
324        let filename = self.filename();
325        let info = specs.lines().find(|&line| line.contains(filename.as_str()));
326
327        let mut specs = match info {
328            None => return Err(NodeJSRelInfoError::UnrecognizedConfiguration(filename))?,
329            Some(s) => s.split_whitespace(),
330        };
331
332        self.filename = filename;
333        self.sha256 = specs.nth(0).unwrap().to_string();
334        self.url = self.url_fmt.pkg(&self.version, &self.filename);
335        Ok(self.to_owned())
336    }
337
338    /// Fetches Node.js metadata for all supported configurations from the
339    /// [releases download server](https://nodejs.org/download/release/)
340    ///
341    /// # Examples
342    ///
343    /// ```rust
344    /// use node_js_release_info::{NodeJSRelInfo, NodeJSRelInfoError};
345    ///
346    /// #[tokio::main]
347    /// async fn main() -> Result<(), NodeJSRelInfoError> {
348    ///   let info = NodeJSRelInfo::new("20.6.1");
349    ///   let all = info.fetch_all().await?;
350    ///   assert_eq!(all.len(), 24);
351    ///   assert_eq!(all[2].version, "20.6.1");
352    ///   assert_eq!(all[2].filename, "node-v20.6.1-darwin-arm64.tar.gz");
353    ///   assert_eq!(all[2].sha256, "d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46");
354    ///   assert_eq!(all[2].url, "https://nodejs.org/download/release/v20.6.1/node-v20.6.1-darwin-arm64.tar.gz");
355    ///   Ok(())
356    /// }
357    /// ```
358    pub async fn fetch_all(&self) -> Result<Vec<NodeJSRelInfo>, NodeJSRelInfoError> {
359        let version = specs::validate_version(self.version.as_str())?;
360        let specs = specs::fetch(&version, &self.url_fmt).await?;
361        let specs = match specs::parse(&version, specs) {
362            Some(s) => s,
363            None => {
364                return Err(NodeJSRelInfoError::UnrecognizedVersion(version.clone()));
365            }
366        };
367
368        let mut all: Vec<NodeJSRelInfo> = vec![];
369        for (os, arch, ext, sha256, filename) in specs.into_iter() {
370            let version = version.clone();
371            let mut info = NodeJSRelInfo {
372                os,
373                arch,
374                version,
375                ext,
376                filename,
377                sha256,
378                ..Default::default()
379            };
380
381            info.url = info.url_fmt.pkg(&info.version, &info.filename);
382            all.push(info);
383        }
384
385        Ok(all)
386    }
387
388    fn filename(&self) -> String {
389        let arch = self.arch.to_string();
390        let ext = self.ext.to_string();
391
392        if self.ext == NodeJSPkgExt::Msi {
393            return format!("node-v{}-{}.{}", self.version, arch, ext);
394        }
395
396        format!("node-v{}-{}-{}.{}", self.version, self.os, arch, ext)
397    }
398}
399
400// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405    use mockito::Server;
406
407    fn is_thread_safe<T: Sized + Send + Sync + Unpin>() {}
408
409    #[test]
410    fn it_initializes() {
411        let info = NodeJSRelInfo::new("1.0.0");
412        assert_eq!(info.os, NodeJSOS::Linux);
413        assert_eq!(info.arch, NodeJSArch::X64);
414        assert_eq!(info.ext, NodeJSPkgExt::Targz);
415        assert_eq!(info.version, "1.0.0".to_string());
416        assert_eq!(info.filename, "".to_string());
417        assert_eq!(info.sha256, "".to_string());
418        assert_eq!(info.url, "".to_string());
419        is_thread_safe::<NodeJSRelInfo>();
420    }
421
422    #[test]
423    fn it_initializes_with_defaults() {
424        let info = NodeJSRelInfo::default();
425        assert_eq!(info.os, NodeJSOS::Linux);
426        assert_eq!(info.arch, NodeJSArch::X64);
427        assert_eq!(info.ext, NodeJSPkgExt::Targz);
428        assert_eq!(info.version, "".to_string());
429        assert_eq!(info.filename, "".to_string());
430        assert_eq!(info.sha256, "".to_string());
431        assert_eq!(info.url, "".to_string());
432    }
433
434    #[test]
435    #[cfg_attr(not(target_os = "macos"), ignore)]
436    fn it_initializes_using_current_environment_on_macos() {
437        let info = NodeJSRelInfo::from_env("1.0.0").unwrap();
438        assert_eq!(info.ext, NodeJSPkgExt::Targz);
439    }
440
441    #[test]
442    #[cfg_attr(not(target_os = "linux"), ignore)]
443    fn it_initializes_using_current_environment_on_linux() {
444        let info = NodeJSRelInfo::from_env("1.0.0").unwrap();
445        assert_eq!(info.ext, NodeJSPkgExt::Targz);
446    }
447
448    #[test]
449    #[cfg_attr(not(target_os = "windows"), ignore)]
450    fn it_initializes_using_current_environment_on_windows() {
451        let info = NodeJSRelInfo::from_env("1.0.0").unwrap();
452        assert_eq!(info.ext, NodeJSPkgExt::Zip);
453    }
454
455    #[test]
456    fn it_sets_os() {
457        let mut info = NodeJSRelInfo::new("1.0.0");
458
459        assert_eq!(info.os, NodeJSOS::Linux);
460
461        info.windows();
462
463        assert_eq!(info.os, NodeJSOS::Windows);
464
465        info.macos();
466
467        assert_eq!(info.os, NodeJSOS::Darwin);
468
469        info.linux();
470
471        assert_eq!(info.os, NodeJSOS::Linux);
472
473        info.aix();
474
475        assert_eq!(info.os, NodeJSOS::AIX);
476    }
477
478    #[test]
479    fn it_sets_arch() {
480        let mut info = NodeJSRelInfo::new("1.0.0");
481
482        info.x86();
483
484        assert_eq!(info.arch, NodeJSArch::X86);
485
486        info.x64();
487
488        assert_eq!(info.arch, NodeJSArch::X64);
489
490        info.arm64();
491
492        assert_eq!(info.arch, NodeJSArch::ARM64);
493
494        info.armv7l();
495
496        assert_eq!(info.arch, NodeJSArch::ARMV7L);
497
498        info.ppc64();
499
500        assert_eq!(info.arch, NodeJSArch::PPC64);
501
502        info.ppc64le();
503
504        assert_eq!(info.arch, NodeJSArch::PPC64LE);
505
506        info.s390x();
507
508        assert_eq!(info.arch, NodeJSArch::S390X);
509    }
510
511    #[test]
512    fn it_sets_ext() {
513        let mut info = NodeJSRelInfo::new("1.0.0");
514
515        info.zip();
516
517        assert_eq!(info.ext, NodeJSPkgExt::Zip);
518
519        info.tar_gz();
520
521        assert_eq!(info.ext, NodeJSPkgExt::Targz);
522
523        info.tar_xz();
524
525        assert_eq!(info.ext, NodeJSPkgExt::Tarxz);
526
527        info.msi();
528
529        assert_eq!(info.ext, NodeJSPkgExt::Msi);
530
531        info.s7z();
532
533        assert_eq!(info.ext, NodeJSPkgExt::S7z);
534    }
535
536    #[test]
537    fn it_gets_owned_copy() {
538        let mut info1 = NodeJSRelInfo::new("1.0.0");
539        let info2 = info1.to_owned();
540
541        assert_eq!(info1, info2);
542
543        info1.windows();
544
545        assert_ne!(info1, info2);
546    }
547
548    #[test]
549    fn it_formats_filename() {
550        let info = NodeJSRelInfo::new("1.0.0").macos().x64().zip().to_owned();
551
552        assert_eq!(info.filename(), "node-v1.0.0-darwin-x64.zip");
553
554        let info = NodeJSRelInfo::new("1.0.0").windows().x64().msi().to_owned();
555
556        assert_eq!(info.filename(), "node-v1.0.0-x64.msi");
557    }
558
559    #[test]
560    fn it_serializes_and_deserializes() {
561        let version = "20.6.1".to_string();
562        let filename = "node-v20.6.1-darwin-arm64.tar.gz".to_string();
563        let sha256 = "d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46".to_string();
564        let url = "https://nodejs.org/download/release/v20.6.1/node-v20.6.1-darwin-arm64.tar.gz"
565            .to_string();
566        let info_orig = NodeJSRelInfo {
567            os: NodeJSOS::Darwin,
568            arch: NodeJSArch::ARM64,
569            ext: NodeJSPkgExt::Targz,
570            version: version.clone(),
571            filename: filename.clone(),
572            sha256: sha256.clone(),
573            url: url.clone(),
574            ..Default::default()
575        };
576        let info_json = serde_json::to_string(&info_orig).unwrap();
577        let info: NodeJSRelInfo = serde_json::from_str(&info_json).unwrap();
578        assert_eq!(info.os, NodeJSOS::Darwin);
579        assert_eq!(info.arch, NodeJSArch::ARM64);
580        assert_eq!(info.ext, NodeJSPkgExt::Targz);
581        assert_eq!(info.version, "20.6.1".to_string());
582        assert_eq!(
583            info.filename,
584            "node-v20.6.1-darwin-arm64.tar.gz".to_string()
585        );
586        assert_eq!(
587            info.sha256,
588            "d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46".to_string()
589        );
590        assert_eq!(
591            info.url,
592            "https://nodejs.org/download/release/v20.6.1/node-v20.6.1-darwin-arm64.tar.gz"
593                .to_string()
594        );
595    }
596
597    #[tokio::test]
598    #[should_panic(
599        expected = "called `Result::unwrap()` on an `Err` value: InvalidVersion(\"NOPE!\")"
600    )]
601    async fn it_fails_to_fetch_info_when_version_is_invalid() {
602        let mut info = NodeJSRelInfo::new("NOPE!");
603        info.fetch().await.unwrap();
604    }
605
606    #[tokio::test]
607    #[should_panic(
608        expected = "called `Result::unwrap()` on an `Err` value: UnrecognizedVersion(\"1.0.0\")"
609    )]
610    async fn it_fails_to_fetch_info_when_version_is_unrecognized() {
611        let mut info = NodeJSRelInfo::new("1.0.0");
612        let mut server = Server::new_async().await;
613        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
614            .with_body(specs::get_fake_specs())
615            .with_status(404)
616            .create_async()
617            .await;
618
619        info.fetch().await.unwrap();
620        mock.assert_async().await;
621    }
622
623    #[tokio::test]
624    #[should_panic(
625        expected = "called `Result::unwrap()` on an `Err` value: UnrecognizedConfiguration(\"node-v20.6.1-linux-x64.zip\")"
626    )]
627    async fn it_fails_to_fetch_info_when_configuration_is_unrecognized() {
628        let mut server = Server::new_async().await;
629        let mut info = NodeJSRelInfo::new("20.6.1").linux().zip().to_owned();
630        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
631            .with_body(specs::get_fake_specs())
632            .create_async()
633            .await;
634
635        info.fetch().await.unwrap();
636        mock.assert_async().await;
637    }
638
639    #[tokio::test]
640    async fn it_fetches_node_js_release_info() {
641        let mut info = NodeJSRelInfo::new("20.6.1");
642        let mut server = Server::new_async().await;
643        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
644            .with_body(specs::get_fake_specs())
645            .create_async()
646            .await;
647
648        info.fetch().await.unwrap();
649        mock.assert_async().await;
650
651        assert_eq!(info.filename, "node-v20.6.1-linux-x64.tar.gz");
652        assert_eq!(
653            info.url,
654            format!(
655                "{}{}",
656                server.url(),
657                "/download/release/v20.6.1/node-v20.6.1-linux-x64.tar.gz"
658            )
659        );
660        assert_eq!(
661            info.sha256,
662            "26dd13a6f7253f0ab9bcab561353985a297d927840771d905566735b792868da"
663        );
664    }
665
666    #[tokio::test]
667    async fn it_fetches_node_js_release_info_when_ext_is_msi() {
668        let mut info = NodeJSRelInfo::new("20.6.1").arm64().msi().to_owned();
669        let mut server = Server::new_async().await;
670        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
671            .with_body(specs::get_fake_specs())
672            .create_async()
673            .await;
674
675        info.fetch().await.unwrap();
676        mock.assert_async().await;
677
678        assert_eq!(info.filename, "node-v20.6.1-arm64.msi");
679        assert_eq!(
680            info.url,
681            format!(
682                "{}{}",
683                server.url(),
684                "/download/release/v20.6.1/node-v20.6.1-arm64.msi"
685            )
686        );
687        assert_eq!(
688            info.sha256,
689            "9471bd6dc491e09c31b0f831f5953284b8a6842ed4ccb98f5c62d13e6086c471"
690        );
691    }
692
693    #[tokio::test]
694    async fn it_fetches_all_supported_node_js_configurations() {
695        let mut info = NodeJSRelInfo::new("20.6.1");
696        let mut server = Server::new_async().await;
697        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
698            .with_body(specs::get_fake_specs())
699            .create_async()
700            .await;
701
702        let all = info.fetch_all().await.unwrap();
703        mock.assert_async().await;
704
705        assert_eq!(all.len(), 24);
706        assert_eq!(all[2].version, "20.6.1");
707        assert_eq!(all[2].os, NodeJSOS::Darwin);
708        assert_eq!(all[2].arch, NodeJSArch::ARM64);
709        assert_eq!(all[2].ext, NodeJSPkgExt::Targz);
710        assert_eq!(all[2].filename, "node-v20.6.1-darwin-arm64.tar.gz");
711        assert_eq!(
712            all[2].sha256,
713            "d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46"
714        );
715        assert_eq!(
716            all[2].url,
717            "https://nodejs.org/download/release/v20.6.1/node-v20.6.1-darwin-arm64.tar.gz"
718        );
719    }
720
721    #[tokio::test]
722    #[should_panic(
723        expected = "called `Result::unwrap()` on an `Err` value: UnrecognizedVersion(\"1.0.0\")"
724    )]
725    async fn it_fails_to_fetch_all_supported_node_js_configurations_when_version_is_unrecognized() {
726        let mut info = NodeJSRelInfo::new("1.0.0");
727        let mut server = Server::new_async().await;
728        let mock = specs::setup_server_mock(&info.version, &mut info.url_fmt, &mut server)
729            .with_body(String::from(""))
730            .create_async()
731            .await;
732
733        info.fetch_all().await.unwrap();
734        mock.assert_async().await;
735    }
736}