Skip to main content

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