use anyhow::{Context, Result}; use assert_cmd::prelude::*; use httpmock::Method::POST; use httpmock::{Mock, MockServer}; use indoc::indoc; use predicates::{prelude::*, str::ContainsPredicate}; use serde_json::json; use serial_test::serial; use std::fs::File; use std::{fs, path::PathBuf, process::Command}; #[test] #[serial] fn prints_error_if_move_toml_file_is_not_found() -> Result<()> { let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains("Move.toml file was not found")); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_not_properly_formatted() -> Result<()> { create_move_file("Some invalid text")?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains("Unexpected content of Move.toml file")); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_valid_but_the_dependencies_key_does_not_match_with_the_cli_command_argument( ) -> Result<()> { create_move_file( r#" [dependencies] [dependencies.asdf] resolver = "movey" [dependencies.asdf.packages] A = "a-namingscheme" B = { scheme = "b-namingscheme", version = ">=1.0.0" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dev-dependencies") // The right command should be --resolve-move-dependencies .arg("movey") .assert() .failure() .stderr(contains( "Missing field [dev-dependencies] in the Move.toml file", )); clean_up_move_file(); // Another round create_move_file( r#" [dev-dependencies] [dev-dependencies.asdf] resolver = "movey" [dev-dependencies.asdf.packages] A = "a-namingscheme" B = { scheme = "b-namingscheme", version = ">=1.0.0" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") // The right command should be --resolve-move-dev-dependencies .arg("movey") .assert() .failure() .stderr(contains( "Missing field [dependencies] in the Move.toml file", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_valid_but_entry_name_from_the_arg_does_not_match() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.asdf] resolver = "movey" [dependencies.asdf.packages] A = "a-namingscheme" B = { scheme = "b-namingscheme", version = ">=1.0.0" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "Missing entry [dependencies.movey] in the Move.toml file", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_valid_but_entry_name_for_packages_is_missing() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "Missing entry [dependencies.movey.packages] in the Move.toml file", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_valid_but_scheme_identifier_is_not_provided() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = "a-namingscheme" B = { version = ">=1.0.0" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "Missing \"scheme\" field in the package declaration: B = { version = \">=1.0.0\" }", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_move_toml_file_is_valid_but_unknown_identifier_is_detected() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = "a-namingscheme" B = { name = "B" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "Invalid field \"name\" was found in the package declaration: B = { name = \"B\" }", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_addr_subst_is_not_an_object() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = { scheme = "a-namingsheme", addr_subst = "address" } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "\"addr_subst\" must be an Object in the package declaration: A = {", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_scheme_is_not_a_string() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = { scheme = {} } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "\"scheme\" must be a string in the package declaration: A = { ", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_version_is_not_a_string() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = { scheme = "a-namingscheme", version = {} } "#, )?; let mut cmd = command()?; cmd.arg("--resolve-move-dependencies") .arg("movey") .assert() .failure() .stderr(contains( "\"version\" must be a string in the package declaration: A = { ", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn generates_proper_output_if_move_toml_file_is_valid() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = { scheme = "a-namingscheme", version = ">=1.0.0", addr_subst = { "RootA" = "A" } } B = { scheme = "b-namingscheme", addr_subst = { "RootB" = "B" } } C = { scheme = "c-namingscheme" } D = "d-namingscheme" "#, )?; let server = MockServer::start(); let api = mock_api( &server, 200, json!({ "a-namingscheme": ">=1.0.0", "b-namingscheme": "", "c-namingscheme": "", "d-namingscheme": "", }), &json!({ "a-namingscheme": { "name": "A", "git": "a-git", "rev": "a-rev", "subdir": "a-subdir", "dependencies": [ { "name": "X", "addr_subst": { "AX": "X" }, "digest": "x-digest" }, { "name": "Y", "addr_subst": { "AY": "Y" }, "digest": "y-digest" } ], "dev_dependencies": [] }, "x-namingscheme": { "name": "X", "git": "x-git", "rev": "x-rev", "dependencies": [], "dev_dependencies": [] }, "y-namingscheme": { "name": "Y", "git": "y-git", "rev": "y-rev", "subdir": "y-subdir", "dependencies": [], "dev_dependencies": [] }, "b-namingscheme": { "name": "B", "git": "b-git", "rev": "b-rev", "subdir": "b-subdir", "dependencies": [], "dev_dependencies": [] }, "c-namingscheme": { "name": "C", "git": "c-git", "rev": "c-rev", "subdir": "c-subdir", "dependencies": [], "dev_dependencies": [] }, "d-namingscheme": { "name": "D", "git": "d-git", "rev": "d-rev", "subdir": "d-subdir", "dependencies": [], "dev_dependencies": [] }, }) .to_string(), ); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dependencies") .arg("movey") .assert() .success() .stdout(contains("version = 0")) .stdout(contains(r#"{ name = "A", addr_subst = { RootA = "A" } }"#)) .stdout(contains(r#"{ name = "B", addr_subst = { RootB = "B" } }"#)) .stdout(contains(r#"{ name = "C" }"#)) .stdout(contains(r#"{ name = "D" }"#)) .stdout(contains(indoc! {r#" [[move.package]] name = "A" source = { git = "a-git", rev = "a-rev", subdir = "a-subdir" } dependencies = [{ name = "X", addr_subst = { AX = "X" }, digest = "x-digest" }, { name = "Y", addr_subst = { AY = "Y" }, digest = "y-digest" }] dev-dependencies = [] "#})) .stdout(contains(indoc! {r#" [[move.package]] name = "X" source = { git = "x-git", rev = "x-rev" } dependencies = [] dev-dependencies = [] "#})) .stdout(contains(indoc! {r#" [[move.package]] name = "Y" source = { git = "y-git", rev = "y-rev", subdir = "y-subdir" } dependencies = [] dev-dependencies = [] "#})) .stdout(contains(indoc! {r#" [[move.package]] name = "B" source = { git = "b-git", rev = "b-rev", subdir = "b-subdir" } dependencies = [] dev-dependencies = [] "#})) .stdout(contains(indoc! {r#" [[move.package]] name = "C" source = { git = "c-git", rev = "c-rev", subdir = "c-subdir" } dependencies = [] dev-dependencies = [] "#})) .stdout(contains(indoc! {r#" [[move.package]] name = "D" source = { git = "d-git", rev = "d-rev", subdir = "d-subdir" } dependencies = [] dev-dependencies = [] "#})); api.assert(); clean_up_move_file(); // One more round for the dev-dependencies create_move_file( r#" [dev-dependencies] [dev-dependencies.movey] resolver = "movey" [dev-dependencies.movey.packages] B = { scheme = "b-namingscheme", addr_subst = { RootB = "B" } } "#, )?; let api = mock_api( &server, 200, json!({ "b-namingscheme": "", }), &json!({ "b-namingscheme": { "name": "B", "git": "b-git", "rev": "b-rev", "dependencies": [], "dev_dependencies": [] }, }) .to_string(), ); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dev-dependencies") .arg("movey") .assert() .success() .stdout(contains("version = 0")) .stdout(contains( r#"dev-dependencies = [{ name = "B", addr_subst = { RootB = "B" } }]"#, )) .stdout(contains(indoc! {r#" [[move.package]] name = "B" source = { git = "b-git", rev = "b-rev" } dependencies = [] dev-dependencies = [] "#})); api.assert(); clean_up_move_file(); Ok(()) } #[test] #[serial] fn generates_empty_output_if_move_toml_file_contains_no_movey_packages() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] "#, )?; let server = MockServer::start(); let api = mock_api(&server, 200, json!({}), "{}"); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dependencies") .arg("movey") .assert() .success() .stdout("[move]\nversion = 0\n"); api.assert(); clean_up_move_file(); Ok(()) } #[test] #[serial] fn generates_only_movey_output_if_move_toml_file_contains_both_normal_and_movey_packages( ) -> Result<()> { create_move_file( r#" [dependencies.movey] resolver = "movey" [dependencies.movey.packages] B = { scheme = "b-namingscheme", addr_subst = { "RootB" = "B" } } [dependencies] Sui = { git = "sui-git-url", subdir = "sui-sub-dir", rev = "mainnet-rev" } MoveStdlib = { local = "../local/movestdlib" } NonExistentPackage1 = 1 NonExistentPackage2 = "just another string" [dependencies.AptosFramework] git = "aptos-git-url" subdir = "aptos-sub-dir" rev = "mainnet-rev" addr_subst = { "std" = "Std", "aptos" = "0x0" } [dev-dependencies] Sui = { git = "sui-git-url", subdir = "sui-sub-dir", rev = "testnet-rev" } AptosFramework = { git = "aptos-git-url", subdir = "aptos-sub-dir", rev = "devnet-rev", addr_subst = { "std" = "Std", "aptos" = "0x0" } } "#, )?; let server = MockServer::start(); let api = mock_api( &server, 200, json!({ "b-namingscheme": "", }), &json!({ "b-namingscheme": { "name": "B", "git": "b-git", "rev": "b-rev", "dependencies": [], "dev_dependencies": [], }, }) .to_string(), ); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dependencies") .arg("movey") .assert() .success() .stdout(contains("version = 0")) .stdout(contains( r#"dependencies = [{ name = "B", addr_subst = { RootB = "B" } }]"#, )) .stdout(contains(indoc! {r#" [[move.package]] name = "B" source = { git = "b-git", rev = "b-rev" } dependencies = [] dev-dependencies = [] "#})); api.assert(); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_api_returns_non_200_status_code_with_error_message() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = "a-namingscheme" "#, )?; let server = MockServer::start(); mock_api( &server, 412, json!({ "a-namingscheme": "" }), &json!({ "errors": "Unexpected error occurred. We're looking into it." }) .to_string(), ); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dependencies") .assert() .failure() .stderr(contains( "Error: Unexpected error occurred. We're looking into it.", )); clean_up_move_file(); Ok(()) } #[test] #[serial] fn prints_error_if_api_returns_non_200_status_code_without_error_message() -> Result<()> { create_move_file( r#" [dependencies] [dependencies.movey] resolver = "movey" [dependencies.movey.packages] A = "a-namingscheme" "#, )?; let server = MockServer::start(); mock_api( &server, 503, // Service unavailable json!({ "a-namingscheme": "" }), "\n\n...", ); let mut cmd = command()?; cmd.env("MOVEY_URL", server.base_url()) .arg("--resolve-move-dependencies") .assert() .failure() .stderr(contains( "An unexpected error occurred. Please try again later", )); clean_up_move_file(); Ok(()) } fn command() -> Result { Command::cargo_bin("movey").context("Unable to find movey executable binary") } fn contains(pattern: &str) -> ContainsPredicate { predicate::str::contains(pattern) } fn create_move_file(content: &str) -> Result<()> { let path = PathBuf::from("Move.toml"); File::create(&path).context("Failed to create Move.toml file")?; return fs::write(&path, content).context("Failed to write content to Move.toml file"); } fn clean_up_move_file() { let _ = fs::remove_file("Move.toml"); } fn mock_api<'a>( server: &'a MockServer, status_code: u16, request_body: serde_json::Value, response_body: &str, ) -> Mock<'a> { server.mock(|when, then| { when.method(POST) .path("/api/v1/packages/info") .header("content-type", "application/json") .json_body(request_body); then.status(status_code).body(response_body); }) }