diff --git a/Cargo.lock b/Cargo.lock index b7dff06..86e9ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" +dependencies = [ + "block-sys", + "objc2", +] + [[package]] name = "bs58" version = "0.5.0" @@ -531,9 +550,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -541,9 +560,33 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] [[package]] name = "counter" @@ -889,6 +932,23 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enigo" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0087a01fc8591217447d28005379fb5a183683cc83f0a4707af28cc6603f70fb" +dependencies = [ + "core-graphics", + "foreign-types-shared", + "icrate", + "libc", + "log", + "objc2", + "windows", + "xkbcommon", + "xkeysym", +] + [[package]] name = "enum-iterator" version = "1.5.0" @@ -1022,6 +1082,7 @@ version = "0.8.0" dependencies = [ "anyhow", "clap", + "enigo", "eth-keystore", "forc-tracing", "fuel-crypto", @@ -1031,15 +1092,44 @@ dependencies = [ "futures", "hex", "home", + "lazy_static", "rand", "rpassword", "serde_json", + "tempfile", "termion", "tiny-bip39", "tokio", "url", ] +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1841,7 +1931,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.51.1", ] [[package]] @@ -1853,6 +1943,16 @@ dependencies = [ "cc", ] +[[package]] +name = "icrate" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" +dependencies = [ + "block2", + "objc2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2085,6 +2185,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -2167,6 +2276,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + [[package]] name = "object" version = "0.32.1" @@ -3847,6 +3978,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", + "windows-targets 0.52.5", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -3856,6 +3997,49 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3871,7 +4055,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -3891,17 +4075,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -3912,9 +4097,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -3924,9 +4109,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -3936,9 +4121,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -3948,9 +4139,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -3960,9 +4151,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -3972,9 +4163,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -3984,9 +4175,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -4016,6 +4207,23 @@ dependencies = [ "tap", ] +[[package]] +name = "xkbcommon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "zerocopy" version = "0.7.26" diff --git a/Cargo.toml b/Cargo.toml index fe4e7db..ef9cc29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,11 @@ tiny-bip39 = "1.0" tokio = { version = "1.10.1", features = ["full"] } url = "2.3" +[dev-dependencies] +enigo = "0.2.1" +lazy_static = "1.4" +tempfile = "3" + [lib] name = "forc_wallet" path = "src/lib.rs" diff --git a/src/account.rs b/src/account.rs index 7463732..a59fae6 100644 --- a/src/account.rs +++ b/src/account.rs @@ -123,14 +123,14 @@ pub(crate) struct Transfer { maturity: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] enum To { Bech32Address(Bech32Address), HexAddress(fuel_types::Address), } impl FromStr for To { - type Err = &'static str; + type Err = String; fn from_str(s: &str) -> std::result::Result { if let Ok(bech32_address) = Bech32Address::from_str(s) { @@ -139,7 +139,10 @@ impl FromStr for To { return Ok(Self::HexAddress(hex_address)); } - Err("Invalid address '{}': address must either be in bech32 or hex") + Err(format!( + "Invalid address '{}': address must either be in bech32 or hex", + s + )) } } @@ -621,9 +624,15 @@ pub(crate) fn read_cached_addresses(wallet_ciphertext: &[u8]) -> Result, } @@ -25,13 +25,10 @@ pub fn new_wallet_cli(wallet_path: &Path, new: New) -> anyhow::Result<()> { ensure_no_wallet_exists(wallet_path, new.force, stdin().lock())?; let password = request_new_password(); // Generate a random mnemonic phrase. - let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 24)?; - write_wallet_from_mnemonic_and_password(wallet_path, &mnemonic, &password)?; - - derive_and_cache_addresses( - &load_wallet(wallet_path)?, - &mnemonic, - 0..new.cache_accounts.unwrap_or(DEFAULT_CACHE_ACCOUNTS), + let mnemonic = new_wallet( + wallet_path, + &password, + new.cache_accounts.unwrap_or(DEFAULT_CACHE_ACCOUNTS), )?; let mnemonic_string = format!("Wallet mnemonic phrase: {mnemonic}\n"); @@ -41,3 +38,14 @@ pub fn new_wallet_cli(wallet_path: &Path, new: New) -> anyhow::Result<()> { )?; Ok(()) } + +pub fn new_wallet( + wallet_path: &Path, + password: &str, + cache_count: usize, +) -> anyhow::Result { + let mnemonic = generate_mnemonic_phrase(&mut rand::thread_rng(), 24)?; + write_wallet_from_mnemonic_and_password(wallet_path, &mnemonic, &password)?; + derive_and_cache_addresses(&load_wallet(wallet_path)?, &mnemonic, 0..cache_count)?; + Ok(mnemonic) +} diff --git a/src/utils.rs b/src/utils.rs index 562d825..cc24fd7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -266,7 +266,7 @@ mod tests { } #[test] - fn test_ensure_no_wallet_exists_no_wallet() { + fn ensure_no_wallet_exists_no_wallet() { with_tmp_dir(|tmp_dir| { let wallet_path = tmp_dir.join("wallet.json"); remove_wallet(&wallet_path); @@ -276,7 +276,7 @@ mod tests { #[test] #[should_panic] - fn test_ensure_no_wallet_exists_throws_err() { + fn ensure_no_wallet_exists_throws_err() { with_tmp_dir(|tmp_dir| { let wallet_path = tmp_dir.join("wallet.json"); create_wallet(&wallet_path); @@ -285,7 +285,7 @@ mod tests { } #[test] - fn test_ensure_no_wallet_exists_exists_wallet() { + fn ensure_no_wallet_exists_exists_wallet() { // case: wallet path exist without --force and input[yes] with_tmp_dir(|tmp_dir| { let wallet_path = tmp_dir.join("wallet.json"); @@ -306,6 +306,36 @@ mod tests { ensure_no_wallet_exists(&diff_wallet_path, false, &INPUT_NOP[..]).unwrap(); }); } + + #[test] + fn load_wallet_fails_without_wallet() { + with_tmp_dir(|tmp_dir| { + let wallet_path = tmp_dir.join("wallet.json"); + let err = load_wallet(&wallet_path).unwrap_err(); + let root_cause = format!("{}", err.root_cause()); + let expected_error_msg_start = format!("Failed to load a wallet from {wallet_path:?}"); + let expected_error_msg_end = r"Please be sure to initialize a wallet before creating an account. +To initialize a wallet, use `forc-wallet new`"; + assert!(root_cause.starts_with(&expected_error_msg_start)); + assert!(root_cause.ends_with(&expected_error_msg_end)); + }) + } + + #[test] + fn wallet_deserialization_fails() { + with_tmp_dir(|tmp_dir| { + let wallet_path = tmp_dir.join("wallet.json"); + std::fs::write(&wallet_path, "this is an invalid wallet json file").unwrap(); + let err = load_wallet(&wallet_path).unwrap_err(); + let root_cause = format!("{}", err.root_cause()); + let expected_error_msg_start = + format!("Failed to deserialize keystore from {wallet_path:?}"); + let expected_error_msg_end = + format!("Please ensure that {wallet_path:?} is a valid wallet file."); + assert!(root_cause.starts_with(&expected_error_msg_start)); + assert!(root_cause.ends_with(&expected_error_msg_end)); + }) + } } #[cfg(test)] diff --git a/tests/account.rs b/tests/account.rs new file mode 100644 index 0000000..ea58b72 --- /dev/null +++ b/tests/account.rs @@ -0,0 +1,18 @@ +use anyhow::Result; +use testcfg::ForcWalletState; +pub mod testcfg; + +#[test] +fn new_creates_accounts_by_default() -> Result<()> { + testcfg::setup(ForcWalletState::Initialized, &|cfg| { + let path = format!("{}", cfg.wallet_path.display()); + let output = cfg.exec(&["--path", &path, "accounts", "--unverified"], &|| {}); + + let expected = "Account addresses (unverified, printed from cache):\n[0]"; + let output_stdout = output.stdout; + dbg!(&output_stdout); + let success = output_stdout.starts_with(expected); + assert!(success) + })?; + Ok(()) +} diff --git a/tests/new.rs b/tests/new.rs new file mode 100644 index 0000000..2583db7 --- /dev/null +++ b/tests/new.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use enigo::{Enigo, Settings}; +use std::{thread, time::Duration}; +use testcfg::ForcWalletState; + +use crate::testcfg::input_utils::enter_password; + +pub mod testcfg; + +#[test] +#[ignore] +fn new_creates_accounts_by_default() -> Result<()> { + testcfg::setup(ForcWalletState::Initialized, &|cfg| { + let output = cfg.exec(&["accounts", "--unverified"], &|| {}); + + let expected = "Account addresses (unverified, printed from cache):\n[0]"; + let output_stdout = output.stdout; + dbg!(&output_stdout); + let success = output_stdout.starts_with(expected); + assert!(success) + })?; + Ok(()) +} + +#[test] +fn new_shows_mnemonic() -> Result<()> { + testcfg::setup(ForcWalletState::NotInitialized, &|cfg| { + let output = cfg.exec( + &["--path", &format!("{}", cfg.wallet_path.display()), "new"], + &|| { + thread::sleep(Duration::from_millis(1000)); + let mut enigo = Enigo::new(&Settings::default()).unwrap(); + // First password + enter_password(&mut enigo).unwrap(); + // Verify password + thread::sleep(Duration::from_millis(100)); + enter_password(&mut enigo).unwrap(); + }, + ); + + let expected = "Wallet mnemonic phrase:"; + let output_stdout = output.stdout; + dbg!(&output_stdout); + let success = output_stdout.contains(expected); + assert!(success) + })?; + Ok(()) +} diff --git a/tests/testcfg/mod.rs b/tests/testcfg/mod.rs new file mode 100644 index 0000000..f48c358 --- /dev/null +++ b/tests/testcfg/mod.rs @@ -0,0 +1,168 @@ +use anyhow::Result; +use forc_wallet::{new::new_wallet, DEFAULT_CACHE_ACCOUNTS}; +use lazy_static::lazy_static; +use std::{ + env, fs, + io::Read, + path::{Path, PathBuf}, + process::{Command, ExitStatus, Stdio}, +}; +use tempfile::tempdir; + +/// Default password to use while creating a wallet. +pub(crate) const DEFAULT_PASSWORD: &str = "1234"; + +pub enum ForcWalletState { + /// Wallet is not initialized yet. No wallet file is present at the target folder. + NotInitialized, + /// A wallet is initialized, by default we need to have at least `DEFAULT_CACHE_ACCOUNTS` accounts derived. + Initialized, +} + +#[derive(Debug)] +pub struct TestOutput { + pub stdout: String, + pub stderr: String, + pub status: ExitStatus, +} + +#[derive(Debug)] +pub struct TestCfg { + /// The path to the test environment's forc-wallet executable. This should usually be + /// /forc-wallet. This should be used to execute forc-wallet in the test + /// environment. + pub forc_wallet_bin_path: PathBuf, + /// The path to the test environment's wallet directory. This should usually be + /// /.forc/wallet/. + pub wallet_path: PathBuf, +} + +lazy_static! { + static ref WALLET_BINARY_PATH: PathBuf = { + // Build the binary + Command::new("cargo") + .args(&["build", "--bin", "forc-wallet"]) + .status() + .expect("Failed to build binary"); + + // Construct the binary path using CARGO_MANIFEST_DIR + let manifest_dir = env::var("CARGO_MANIFEST_DIR") + .expect("CARGO_MANIFEST_DIR is not set"); + let mut binary_path = PathBuf::from(manifest_dir); + binary_path.push("target"); + binary_path.push("debug"); + binary_path.push("forc-wallet"); + + if !binary_path.exists() { + panic!("Binary not found at {:?}", binary_path); + } + + binary_path + }; +} + +impl TestCfg { + pub fn new(forc_wallet_bin_path: PathBuf, wallet_path: PathBuf) -> Self { + Self { + forc_wallet_bin_path, + wallet_path, + } + } + + /// A function for executing forc wallet with given args at the given path. + pub fn exec(&mut self, args: &[&str], f: &dyn Fn()) -> TestOutput { + const PROC_NAME: &str = "forc-wallet"; + let mut cmd = Command::new(PROC_NAME) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .args(args) + .current_dir(&self.forc_wallet_bin_path) + .spawn() + .expect("Failed to execute command"); + + let _ = cmd.stdin.take(); + + f(); + + let mut cmd_stdout = cmd.stdout.take().unwrap(); + let mut cmd_stderr = cmd.stderr.take().unwrap(); + + let status = cmd.wait().expect("failed to wait"); + + let mut stdout = String::new(); + cmd_stdout.read_to_string(&mut stdout).unwrap(); + let mut stderr = String::new(); + cmd_stderr.read_to_string(&mut stderr).unwrap(); + TestOutput { + stdout, + stderr, + status, + } + } +} + +fn setup_new_wallet(path: &Path) -> Result<()> { + new_wallet(path, DEFAULT_PASSWORD, DEFAULT_CACHE_ACCOUNTS)?; + Ok(()) +} + +fn place_wallet_binary(target_path: &Path) -> Result<()> { + let wallet_bin = WALLET_BINARY_PATH.to_str().unwrap(); + + let target_bin_path = target_path.join("forc-wallet"); + // Create a symlink from the binary to the target path + #[cfg(target_family = "unix")] + { + std::os::unix::fs::symlink(wallet_bin, target_bin_path).expect("Failed to create symlink"); + } + + #[cfg(target_family = "windows")] + { + std::os::windows::fs::symlink_file(wallet_bin, target_bin_path) + .expect("Failed to create symlink"); + } + Ok(()) +} + +pub(crate) fn setup(state: ForcWalletState, f: &dyn Fn(&mut TestCfg)) -> Result<()> { + let testdir = tempdir().unwrap(); + let tmp_home = testdir.path().canonicalize()?; + let tmp_forc = tmp_home.join(".forc"); + place_wallet_binary(&tmp_home)?; + fs::create_dir_all(&tmp_forc).unwrap(); + + let tmp_forc_wallet_path = tmp_forc.join("wallet"); + match state { + ForcWalletState::NotInitialized => {} + ForcWalletState::Initialized => { + setup_new_wallet(&tmp_forc_wallet_path)?; + } + } + + f(&mut TestCfg::new(tmp_home, tmp_forc_wallet_path)); + Ok(()) +} + +pub mod input_utils { + use anyhow::Result; + use enigo::{Direction, Enigo, Key, Keyboard}; + + use super::DEFAULT_PASSWORD; + pub fn write_to_stdin(input: &str, enigo: &mut Enigo) -> Result<()> { + enigo.text(input).unwrap(); + Ok(()) + } + + pub fn send_enter(enigo: &mut Enigo) -> Result<()> { + enigo.key(Key::Return, Direction::Press)?; + enigo.key(Key::Return, Direction::Release)?; + Ok(()) + } + + pub fn enter_password(enigo: &mut Enigo) -> Result<()> { + write_to_stdin(DEFAULT_PASSWORD, enigo)?; + send_enter(enigo)?; + Ok(()) + } +}