From: Vihiga Tyonum Date: Mon, 6 Jul 2026 15:57:04 +0000 (+0000) Subject: test: add tests for repl, transactions & send_all X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/struct.Script.html?a=commitdiff_plain;h=a040a55ced7b9eda8f20ff7de122c13bd34e0be1;p=bdk-cli test: add tests for repl, transactions & send_all --- diff --git a/tests/integration/offline.rs b/tests/integration/offline.rs index 9beb8ff..f19f623 100644 --- a/tests/integration/offline.rs +++ b/tests/integration/offline.rs @@ -1,6 +1,5 @@ #[cfg(feature = "rpc")] mod test_offline { - use super::*; use crate::common::BdkCli; use assert_cmd::Command; use predicates::prelude::*; @@ -261,4 +260,92 @@ mod test_offline { .success() .stdout(predicate::str::contains("\"valid\": false")); } + + #[test] + fn test_create_tx_send_all_rejects_multiple_recipients() { + let (cli, mut cmd_init) = setup_wallet_config(); + cmd_init.assert().success(); + + // Same address twice => two recipients => must be rejected under --send_all. + let recipient = "tb1p4tp4l6glyr2gs94neqcpr5gha7344nfyznfkc8szkreflscsdkgqsdent4:0"; + cli.wallet_cmd(&[ + "--wallet", + WALLET_NAME, + "create_tx", + "--send_all", + "--to", + recipient, + "--to", + recipient, + ]) + .assert() + .failure() + .stderr(predicate::str::contains( + "Wallet can only be drained to a single output", + )); + } +} + +#[cfg(all(feature = "repl", feature = "electrum"))] +mod repl_tests { + use crate::common::BdkCli; + use serde_json::Value; + use tempfile::TempDir; + + const WALLET_NAME: &str = "repl_test_wallet"; + + fn setup_repl_wallet() -> (BdkCli, TempDir) { + let temp = TempDir::new().unwrap(); + let cli = BdkCli::new("regtest", Some(temp.path().to_path_buf())); + + let desc = cli.cmd("descriptor", &["--type", "tr"]).output().unwrap(); + let v: Value = serde_json::from_slice(&desc.stdout).unwrap(); + let ext = v["private_descriptors"]["external"].as_str().unwrap(); + let int = v["private_descriptors"]["internal"].as_str().unwrap(); + + cli.build_base_cmd() + .args(["wallet", "--wallet", WALLET_NAME, "config"]) + .args(["--ext-descriptor", ext, "--int-descriptor", int]) + .args(["--client-type", "electrum", "--database-type", "sqlite"]) + .args(["--url", "127.0.0.1:1"]) + .assert() + .success(); + + (cli, temp) + } + + #[test] + fn test_repl_executes_commands_and_exits() { + let (cli, _temp) = setup_repl_wallet(); + + let output = cli + .build_base_cmd() + .args(["repl", "--wallet", WALLET_NAME]) + .write_stdin("wallet new_address\nwallet balance\nexit\n") + .output() + .expect("failed to run repl"); + + assert!( + output.status.success(), + "repl exited non-zero: {}", + String::from_utf8_lossy(&output.stderr) + ); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("Entering REPL mode"), + "missing banner:\n{stdout}" + ); + assert!( + stdout.contains("\"address\":"), + "new_address output missing:\n{stdout}" + ); + assert!( + stdout.contains("\"confirmed\":"), + "balance output missing:\n{stdout}" + ); + assert!( + stdout.contains("Exiting..."), + "no exit acknowledgement:\n{stdout}" + ); + } } diff --git a/tests/integration/online.rs b/tests/integration/online.rs index 9474d76..bdeb39f 100644 --- a/tests/integration/online.rs +++ b/tests/integration/online.rs @@ -178,6 +178,108 @@ mod test_online { Txid::from_str(txid).expect("broadcast: invalid txid") } + #[test] + fn test_funded_wallet_unspent_and_transactions() { + let (cli, mut cmd_init, env) = setup_online_wallet(); + cmd_init.assert().success(); + fund_and_sync_wallet(&cli, &env); + + // unspent: exactly one confirmed, unspent 50,000,000 sat UTXO + let unspent = run_wallet_json(&cli, &["unspent"]); + assert_eq!( + unspent["count"].as_u64(), + Some(1), + "funded wallet should report exactly one UTXO, got: {unspent}" + ); + + let utxo = &unspent["items"][0]; + assert_eq!( + utxo["txout"]["value"].as_u64(), + Some(50_000_000), + "the single UTXO should equal the 0.5 BTC funding amount" + ); + assert_eq!( + utxo["is_spent"].as_bool(), + Some(false), + "the funding UTXO must be unspent" + ); + assert!( + utxo["outpoint"].as_str().is_some_and(|o| o.contains(':')), + "UTXO outpoint should be a `txid:vout` string, got: {}", + utxo["outpoint"] + ); + assert!( + utxo["derivation_index"].is_number(), + "UTXO should expose a numeric derivation_index" + ); + + // transactions: exactly one relevant tx, funding our address + let txs = run_wallet_json(&cli, &["transactions"]); + assert_eq!( + txs["count"].as_u64(), + Some(1), + "funded wallet should report exactly one transaction, got: {txs}" + ); + + let tx = &txs["items"][0]; + assert_eq!( + tx["is_coinbase"].as_bool(), + Some(false), + "the funding transaction is a normal send, not a coinbase" + ); + assert!( + tx["txid"].as_str().is_some_and(|t| t.len() == 64), + "transaction should expose a 64-char hex txid, got: {}", + tx["txid"] + ); + // The funding tx must contain the output that paid us 0.5 BTC. + let outputs = tx["outputs"] + .as_array() + .expect("transaction outputs should be a JSON array"); + assert!( + outputs + .iter() + .any(|o| o["value"].as_u64() == Some(50_000_000)), + "funding tx should contain a 50,000,000 sat output to the wallet" + ); + } + + #[test] + fn test_create_tx_send_all_drains_wallet() { + let (cli, mut cmd_init, env) = setup_online_wallet(); + cmd_init.assert().success(); + fund_and_sync_wallet(&cli, &env); + + let unsigned_psbt = run_wallet_json( + &cli, + &["create_tx", "--send_all", "--to", &format!("{RECIPIENT}:0")], + )["psbt"] + .as_str() + .expect("create_tx: missing 'psbt' field") + .to_string(); + + let (signed_psbt, finalized) = cli_sign(&cli, &unsigned_psbt); + assert!(finalized, "drain PSBT should be finalized after signing"); + + let raw_tx = cli_extract_psbt(&cli, &signed_psbt); + let drain_txid = cli_broadcast(&cli, &raw_tx); + env.rpc_client() + .get_mempool_entry(&drain_txid) + .expect("drain tx not found in node mempool"); + + env.mine_blocks(1, None) + .expect("Failed to mine drain confirmation block"); + env.wait_until_electrum_sees_block(Duration::from_secs(10)) + .expect("Electrum did not catch up to drain confirmation"); + + cli_sync(&cli); + assert_eq!( + cli_balance(&cli), + 0, + "send_all should leave a zero confirmed balance" + ); + } + #[test] fn test_full_online_transaction_lifecycle() { let (cli, mut cmd_init, env) = setup_online_wallet();