Skip to content

Commit a040a55

Browse files
committed
test: add tests for repl, transactions & send_all
1 parent f17a7ef commit a040a55

2 files changed

Lines changed: 190 additions & 1 deletion

File tree

tests/integration/offline.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#[cfg(feature = "rpc")]
22
mod test_offline {
3-
use super::*;
43
use crate::common::BdkCli;
54
use assert_cmd::Command;
65
use predicates::prelude::*;
@@ -261,4 +260,92 @@ mod test_offline {
261260
.success()
262261
.stdout(predicate::str::contains("\"valid\": false"));
263262
}
263+
264+
#[test]
265+
fn test_create_tx_send_all_rejects_multiple_recipients() {
266+
let (cli, mut cmd_init) = setup_wallet_config();
267+
cmd_init.assert().success();
268+
269+
// Same address twice => two recipients => must be rejected under --send_all.
270+
let recipient = "tb1p4tp4l6glyr2gs94neqcpr5gha7344nfyznfkc8szkreflscsdkgqsdent4:0";
271+
cli.wallet_cmd(&[
272+
"--wallet",
273+
WALLET_NAME,
274+
"create_tx",
275+
"--send_all",
276+
"--to",
277+
recipient,
278+
"--to",
279+
recipient,
280+
])
281+
.assert()
282+
.failure()
283+
.stderr(predicate::str::contains(
284+
"Wallet can only be drained to a single output",
285+
));
286+
}
287+
}
288+
289+
#[cfg(all(feature = "repl", feature = "electrum"))]
290+
mod repl_tests {
291+
use crate::common::BdkCli;
292+
use serde_json::Value;
293+
use tempfile::TempDir;
294+
295+
const WALLET_NAME: &str = "repl_test_wallet";
296+
297+
fn setup_repl_wallet() -> (BdkCli, TempDir) {
298+
let temp = TempDir::new().unwrap();
299+
let cli = BdkCli::new("regtest", Some(temp.path().to_path_buf()));
300+
301+
let desc = cli.cmd("descriptor", &["--type", "tr"]).output().unwrap();
302+
let v: Value = serde_json::from_slice(&desc.stdout).unwrap();
303+
let ext = v["private_descriptors"]["external"].as_str().unwrap();
304+
let int = v["private_descriptors"]["internal"].as_str().unwrap();
305+
306+
cli.build_base_cmd()
307+
.args(["wallet", "--wallet", WALLET_NAME, "config"])
308+
.args(["--ext-descriptor", ext, "--int-descriptor", int])
309+
.args(["--client-type", "electrum", "--database-type", "sqlite"])
310+
.args(["--url", "127.0.0.1:1"])
311+
.assert()
312+
.success();
313+
314+
(cli, temp)
315+
}
316+
317+
#[test]
318+
fn test_repl_executes_commands_and_exits() {
319+
let (cli, _temp) = setup_repl_wallet();
320+
321+
let output = cli
322+
.build_base_cmd()
323+
.args(["repl", "--wallet", WALLET_NAME])
324+
.write_stdin("wallet new_address\nwallet balance\nexit\n")
325+
.output()
326+
.expect("failed to run repl");
327+
328+
assert!(
329+
output.status.success(),
330+
"repl exited non-zero: {}",
331+
String::from_utf8_lossy(&output.stderr)
332+
);
333+
let stdout = String::from_utf8_lossy(&output.stdout);
334+
assert!(
335+
stdout.contains("Entering REPL mode"),
336+
"missing banner:\n{stdout}"
337+
);
338+
assert!(
339+
stdout.contains("\"address\":"),
340+
"new_address output missing:\n{stdout}"
341+
);
342+
assert!(
343+
stdout.contains("\"confirmed\":"),
344+
"balance output missing:\n{stdout}"
345+
);
346+
assert!(
347+
stdout.contains("Exiting..."),
348+
"no exit acknowledgement:\n{stdout}"
349+
);
350+
}
264351
}

tests/integration/online.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,108 @@ mod test_online {
178178
Txid::from_str(txid).expect("broadcast: invalid txid")
179179
}
180180

181+
#[test]
182+
fn test_funded_wallet_unspent_and_transactions() {
183+
let (cli, mut cmd_init, env) = setup_online_wallet();
184+
cmd_init.assert().success();
185+
fund_and_sync_wallet(&cli, &env);
186+
187+
// unspent: exactly one confirmed, unspent 50,000,000 sat UTXO
188+
let unspent = run_wallet_json(&cli, &["unspent"]);
189+
assert_eq!(
190+
unspent["count"].as_u64(),
191+
Some(1),
192+
"funded wallet should report exactly one UTXO, got: {unspent}"
193+
);
194+
195+
let utxo = &unspent["items"][0];
196+
assert_eq!(
197+
utxo["txout"]["value"].as_u64(),
198+
Some(50_000_000),
199+
"the single UTXO should equal the 0.5 BTC funding amount"
200+
);
201+
assert_eq!(
202+
utxo["is_spent"].as_bool(),
203+
Some(false),
204+
"the funding UTXO must be unspent"
205+
);
206+
assert!(
207+
utxo["outpoint"].as_str().is_some_and(|o| o.contains(':')),
208+
"UTXO outpoint should be a `txid:vout` string, got: {}",
209+
utxo["outpoint"]
210+
);
211+
assert!(
212+
utxo["derivation_index"].is_number(),
213+
"UTXO should expose a numeric derivation_index"
214+
);
215+
216+
// transactions: exactly one relevant tx, funding our address
217+
let txs = run_wallet_json(&cli, &["transactions"]);
218+
assert_eq!(
219+
txs["count"].as_u64(),
220+
Some(1),
221+
"funded wallet should report exactly one transaction, got: {txs}"
222+
);
223+
224+
let tx = &txs["items"][0];
225+
assert_eq!(
226+
tx["is_coinbase"].as_bool(),
227+
Some(false),
228+
"the funding transaction is a normal send, not a coinbase"
229+
);
230+
assert!(
231+
tx["txid"].as_str().is_some_and(|t| t.len() == 64),
232+
"transaction should expose a 64-char hex txid, got: {}",
233+
tx["txid"]
234+
);
235+
// The funding tx must contain the output that paid us 0.5 BTC.
236+
let outputs = tx["outputs"]
237+
.as_array()
238+
.expect("transaction outputs should be a JSON array");
239+
assert!(
240+
outputs
241+
.iter()
242+
.any(|o| o["value"].as_u64() == Some(50_000_000)),
243+
"funding tx should contain a 50,000,000 sat output to the wallet"
244+
);
245+
}
246+
247+
#[test]
248+
fn test_create_tx_send_all_drains_wallet() {
249+
let (cli, mut cmd_init, env) = setup_online_wallet();
250+
cmd_init.assert().success();
251+
fund_and_sync_wallet(&cli, &env);
252+
253+
let unsigned_psbt = run_wallet_json(
254+
&cli,
255+
&["create_tx", "--send_all", "--to", &format!("{RECIPIENT}:0")],
256+
)["psbt"]
257+
.as_str()
258+
.expect("create_tx: missing 'psbt' field")
259+
.to_string();
260+
261+
let (signed_psbt, finalized) = cli_sign(&cli, &unsigned_psbt);
262+
assert!(finalized, "drain PSBT should be finalized after signing");
263+
264+
let raw_tx = cli_extract_psbt(&cli, &signed_psbt);
265+
let drain_txid = cli_broadcast(&cli, &raw_tx);
266+
env.rpc_client()
267+
.get_mempool_entry(&drain_txid)
268+
.expect("drain tx not found in node mempool");
269+
270+
env.mine_blocks(1, None)
271+
.expect("Failed to mine drain confirmation block");
272+
env.wait_until_electrum_sees_block(Duration::from_secs(10))
273+
.expect("Electrum did not catch up to drain confirmation");
274+
275+
cli_sync(&cli);
276+
assert_eq!(
277+
cli_balance(&cli),
278+
0,
279+
"send_all should leave a zero confirmed balance"
280+
);
281+
}
282+
181283
#[test]
182284
fn test_full_online_transaction_lifecycle() {
183285
let (cli, mut cmd_init, env) = setup_online_wallet();

0 commit comments

Comments
 (0)