proto_node 0.12.2

DEPRECATED: Node.js support for proto.
Documentation
use proto_core::{
    Detector, Downloadable, Executable, Installable, Proto, Resolvable, Tool, Verifiable,
};
use proto_node::NodeLanguage;
use starbase_sandbox::create_empty_sandbox;
use std::env;
use std::{fs, path::Path};

mod node {
    use super::*;

    #[tokio::test]
    async fn downloads_verifies_installs_tool() {
        let fixture = create_empty_sandbox();
        let proto = Proto::from(fixture.path());
        let mut tool = NodeLanguage::new(&proto);

        env::set_var("PROTO_ROOT", fixture.path().to_string_lossy().to_string());

        tool.setup("18.0.0").await.unwrap();

        env::remove_var("PROTO_ROOT");

        assert!(tool.get_install_dir().unwrap().exists());

        let base_dir = proto.tools_dir.join("node/18.0.0");

        if cfg!(windows) {
            assert_eq!(tool.get_bin_path().unwrap(), &base_dir.join("node.exe"));
            assert!(proto.bin_dir.join("node.cmd").exists());
        } else {
            assert_eq!(tool.get_bin_path().unwrap(), &base_dir.join("bin/node"));
            assert!(proto.bin_dir.join("node").exists());
        }
    }

    fn create_node(dir: &Path) -> NodeLanguage {
        let mut tool = NodeLanguage::new(Proto::from(dir));
        tool.version = Some("18.0.0".into());
        tool
    }

    mod detector {
        use super::*;

        #[tokio::test]
        async fn doesnt_match_if_no_files() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            assert_eq!(
                tool.detect_version_from(fixture.path()).await.unwrap(),
                None
            );
        }

        #[tokio::test]
        async fn detects_nvm() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            fixture.create_file(".nvmrc", "1.2.3");

            assert_eq!(
                tool.detect_version_from(fixture.path()).await.unwrap(),
                Some("1.2.3".into())
            );
        }

        #[tokio::test]
        async fn detects_nodenv() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            fixture.create_file(".node-version", "4.5.6\n");

            assert_eq!(
                tool.detect_version_from(fixture.path()).await.unwrap(),
                Some("4.5.6".into())
            );
        }

        #[tokio::test]
        async fn detects_engines() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            fixture.create_file("package.json", r#"{"engines":{"node":"16.2.*"}}"#);

            assert_eq!(
                tool.detect_version_from(fixture.path()).await.unwrap(),
                Some("16.2.*".into())
            );
        }

        #[tokio::test]
        async fn matches_engines_star() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            fixture.create_file("package.json", r#"{"engines":{"node":"16.*"}}"#);

            assert_eq!(
                tool.detect_version_from(fixture.path()).await.unwrap(),
                Some("16.*".into())
            );
        }
    }

    mod downloader {
        use super::*;
        use proto_node::download::get_archive_file;

        #[tokio::test]
        async fn sets_path_to_temp() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            assert_eq!(
                tool.get_download_path().unwrap(),
                Proto::from(fixture.path())
                    .temp_dir
                    .join("node")
                    .join(get_archive_file("18.0.0").unwrap())
            );
        }

        #[tokio::test]
        async fn downloads_to_temp() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let to_file = tool.get_download_path().unwrap();

            assert!(!to_file.exists());

            tool.download(&to_file, None).await.unwrap();

            assert!(to_file.exists());
        }

        #[tokio::test]
        async fn doesnt_download_if_file_exists() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let to_file = tool.get_download_path().unwrap();

            assert!(tool.download(&to_file, None).await.unwrap());
            assert!(!tool.download(&to_file, None).await.unwrap());
        }
    }

    mod installer {
        use super::*;

        #[tokio::test]
        async fn sets_dir_to_tools() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            assert_eq!(
                tool.get_install_dir().unwrap(),
                Proto::from(fixture.path())
                    .tools_dir
                    .join("node")
                    .join("18.0.0")
            );
        }

        #[tokio::test]
        #[should_panic(expected = "InstallMissingDownload(\"Node.js\")")]
        async fn errors_for_missing_download() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let dir = tool.get_install_dir().unwrap();

            tool.install(&dir, &tool.get_download_path().unwrap())
                .await
                .unwrap();
        }

        #[tokio::test]
        async fn doesnt_install_if_dir_exists() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let dir = tool.get_install_dir().unwrap();

            fs::create_dir_all(&dir).unwrap();

            assert!(!tool
                .install(&dir, &tool.get_download_path().unwrap())
                .await
                .unwrap());
        }
    }

    mod resolver {
        use super::*;

        #[tokio::test]
        async fn updates_struct_version() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(tool.resolve_version("node").await.unwrap(), "node");
            assert_ne!(tool.get_resolved_version(), "node");
        }

        #[tokio::test]
        async fn resolve_latest() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(tool.resolve_version("latest").await.unwrap(), "latest");
        }

        #[tokio::test]
        async fn resolve_stable() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(tool.resolve_version("stable").await.unwrap(), "stable");
        }

        #[tokio::test]
        async fn resolve_lts_wild() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(tool.resolve_version("lts-*").await.unwrap(), "lts-*");
        }

        #[tokio::test]
        async fn resolve_lts_dash() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(
                tool.resolve_version("lts-gallium").await.unwrap(),
                "lts-gallium"
            );
        }

        #[tokio::test]
        async fn resolve_lts_slash() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(
                tool.resolve_version("lts/gallium").await.unwrap(),
                "lts/gallium"
            );
        }

        #[tokio::test]
        async fn resolve_alias() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_ne!(tool.resolve_version("Gallium").await.unwrap(), "Gallium");
        }

        #[tokio::test]
        async fn resolve_version() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_eq!(tool.resolve_version("18.0.0").await.unwrap(), "18.0.0");
        }

        #[tokio::test]
        async fn resolve_partial_version() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_eq!(tool.resolve_version("10.1").await.unwrap(), "10.1.0");
        }

        #[tokio::test]
        async fn resolve_custom_alias() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            fixture.create_file(
                "tools/node/manifest.json",
                r#"{"aliases":{"example":"10.0.0"}}"#,
            );

            assert_eq!(tool.resolve_version("example").await.unwrap(), "10.0.0");
        }

        #[tokio::test]
        async fn resolve_version_with_prefix() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            assert_eq!(tool.resolve_version("v18.0.0").await.unwrap(), "18.0.0");
        }

        #[tokio::test]
        #[should_panic(expected = "VersionUnknownAlias(\"unknown\")")]
        async fn errors_invalid_lts() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            tool.resolve_version("lts-unknown").await.unwrap();
        }

        #[tokio::test]
        #[should_panic(expected = "VersionUnknownAlias(\"unknown\")")]
        async fn errors_invalid_alias() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            tool.resolve_version("unknown").await.unwrap();
        }

        #[tokio::test]
        #[should_panic(expected = "VersionResolveFailed(\"99.99.99\")")]
        async fn errors_invalid_version() {
            let fixture = create_empty_sandbox();
            let mut tool = NodeLanguage::new(Proto::from(fixture.path()));

            tool.resolve_version("99.99.99").await.unwrap();
        }
    }

    mod verifier {
        use super::*;

        #[tokio::test]
        async fn sets_path_to_temp() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            assert_eq!(
                tool.get_checksum_path().unwrap(),
                Proto::from(fixture.path())
                    .temp_dir
                    .join("node")
                    .join("v18.0.0-SHASUMS256.txt")
            );
        }

        #[tokio::test]
        async fn downloads_to_temp() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let to_file = tool.get_checksum_path().unwrap();

            assert!(!to_file.exists());

            tool.download_checksum(&to_file, None).await.unwrap();

            assert!(to_file.exists());
        }

        #[tokio::test]
        async fn doesnt_download_if_file_exists() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());

            let to_file = tool.get_checksum_path().unwrap();

            assert!(tool.download_checksum(&to_file, None).await.unwrap());
            assert!(!tool.download_checksum(&to_file, None).await.unwrap());
        }

        #[tokio::test]
        #[should_panic(expected = "VerifyInvalidChecksum")]
        async fn errors_for_checksum_mismatch() {
            let fixture = create_empty_sandbox();
            let tool = create_node(fixture.path());
            let dl_path = tool.get_download_path().unwrap();
            let cs_path = tool.get_checksum_path().unwrap();

            tool.download(&dl_path, None).await.unwrap();
            tool.download_checksum(&cs_path, None).await.unwrap();

            // Empty the checksum file
            fs::write(&cs_path, "").unwrap();

            tool.verify_checksum(&cs_path, &dl_path).await.unwrap();
        }
    }
}