Skip to content

Commit 32e6115

Browse files
committed
Merge #289: test: Implement integration tests
6000dd5 test: Add tests for dns payment instn feature (Vihiga Tyonum) b4e1081 fix(Justfile): fix test-threads to max 2 (Vihiga Tyonum) f809a7e test: add more tests for createtx, bip322 (Vihiga Tyonum) a993315 test: Add sp createtx test (Vihiga Tyonum) d710838 test: add test for multiple recipients and sp code (Vihiga Tyonum) a040a55 test: add tests for repl, transactions & send_all (Vihiga Tyonum) f17a7ef fix(repl): Remove null printed after repl output (Vihiga Tyonum) d425f5b test: split tests according to typestate (Vihiga Tyonum) 3293764 fix(proxy_opts): add saving and reading proxy_opts (Vihiga Tyonum) 3465513 ref(verbose): Drop `verbose` flag from tests (Vihiga Tyonum) 702d259 test(online): Add test transaction full cycle (Vihiga Tyonum) c013da0 test: Add integration tests for offline wallet ops (Vihiga Tyonum) 0d3c1a9 test: Add wallets, descriptor, compile & config (Vihiga Tyonum) d8e2887 test: Add helper fns & integration tests for key (Vihiga Tyonum) Pull request description: <!-- You can erase any parts of this template not applicable to your Pull Request. --> ### Description <!-- Describe the purpose of this PR, what's being adding and/or fixed --> This PR builds on #278 and introduces integration tests for bdk-cli. It replaces manual `std::process::Command` boilerplate with the `assert_cmd` library, allowing us to perform black-box testing against the compiled binary. Features covered so far include: - key: generate, derive, and restore - wallets: list wallets - wallet config: save config, read config. - descriptor: generate descriptor - compile: policy compiler - offline wallet operations: new_address, unused_address, balance, unspent, transactions, policies, public_descriptor, create_tx, combine_psbt ### Notes to the reviewers <!-- In this section you can include notes directed to the reviewers, like explaining why some parts of the PR were done in a specific way --> ## Changelog notice <!-- Notice the release manager should include in the release tag message changelog --> <!-- See https://keepachangelog.com/en/1.0.0/ for examples --> - Introduces the BdkCli helper struct to inject context state into base commands. - Dropped `verbose` flag from wallets as it was applicable to only `Pbst` ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk-cli/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing Top commit has no ACKs. Tree-SHA512: ea4adf734310f4ec494564f75b904835520b4625547859bf25c529b96f2f7fef823dc220dbe4eaf4856e2dd6fe06daa9f97bddc7a565f5e19185fdeea963202e
2 parents 898d08c + 6000dd5 commit 32e6115

20 files changed

Lines changed: 2488 additions & 351 deletions

Cargo.lock

Lines changed: 502 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ silent-payments = ["dep:bdk_sp"]
7575

7676
[dev-dependencies]
7777
claims = "0.8.0"
78+
predicates = "3.0"
79+
tempfile = "3.8"
80+
assert_cmd = "2.2.2"
81+
bdk_testenv = "0.13.1"

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test:
2828
pre-push:
2929
cargo test --features default
3030
cargo test --no-default-features
31-
cargo test --all-features
31+
cargo test --all-features -- --test-threads=2
3232
cargo clippy --no-default-features --all-targets -- -D warnings
3333
cargo clippy --all-features --all-targets -- -D warnings
3434
cargo fmt --all -- --check

src/commands.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,21 @@ pub struct WalletOpts {
281281
#[cfg(feature = "cbf")]
282282
#[clap(flatten)]
283283
pub compactfilter_opts: CompactFilterOpts,
284+
#[cfg(any(feature = "electrum", feature = "esplora"))]
285+
#[command(flatten)]
286+
pub proxy_opts: ProxyOpts,
284287
}
285288

286289
/// Options to configure a SOCKS5 proxy for a blockchain client connection.
287290
#[cfg(any(feature = "electrum", feature = "esplora"))]
288291
#[derive(Debug, Args, Clone, PartialEq, Eq)]
289292
pub struct ProxyOpts {
290293
/// Sets the SOCKS5 proxy for a blockchain client.
291-
#[arg(env = "PROXY_ADDRS:PORT", long = "proxy", short = 'p')]
294+
#[arg(env = "PROXY_ADDRS:PORT", long = "proxy")]
292295
pub proxy: Option<String>,
293296

294297
/// Sets the SOCKS5 proxy credential.
295-
#[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", short='a', value_parser = parse_proxy_auth)]
298+
#[arg(env = "PROXY_USER:PASSWD", long="proxy_auth", value_parser = parse_proxy_auth)]
296299
pub proxy_auth: Option<(String, String)>,
297300

298301
/// Sets the SOCKS5 proxy retries for the blockchain client.

src/config.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ pub struct WalletConfigInner {
5050
pub parallel_requests: Option<usize>,
5151
#[cfg(feature = "rpc")]
5252
pub cookie: Option<String>,
53+
#[cfg(any(feature = "electrum", feature = "esplora"))]
54+
#[serde(default)]
55+
pub proxy: Option<String>,
56+
#[cfg(any(feature = "electrum", feature = "esplora"))]
57+
#[serde(default)]
58+
pub proxy_auth: Option<String>,
59+
#[cfg(any(feature = "electrum", feature = "esplora"))]
60+
#[serde(default)]
61+
pub proxy_retries: Option<u8>,
62+
#[cfg(any(feature = "electrum", feature = "esplora"))]
63+
#[serde(default)]
64+
pub proxy_timeout: Option<u8>,
65+
#[cfg(feature = "cbf")]
66+
#[serde(default)]
67+
pub conn_count: Option<u8>,
5368
}
5469

5570
impl WalletConfig {
@@ -93,7 +108,7 @@ impl TryFrom<&WalletConfigInner> for WalletOpts {
93108
type Error = Error;
94109

95110
fn try_from(config: &WalletConfigInner) -> Result<Self, Self::Error> {
96-
let _network = Network::from_str(&config.network)
111+
Network::from_str(&config.network)
97112
.map_err(|_| Error::Generic("Invalid network".to_string()))?;
98113

99114
#[cfg(any(feature = "sqlite", feature = "redb"))]
@@ -155,8 +170,21 @@ impl TryFrom<&WalletConfigInner> for WalletOpts {
155170
#[cfg(feature = "rpc")]
156171
cookie: config.cookie.clone(),
157172

173+
#[cfg(any(feature = "electrum", feature = "esplora"))]
174+
proxy_opts: crate::commands::ProxyOpts {
175+
proxy: config.proxy.clone(),
176+
proxy_auth: match &config.proxy_auth {
177+
Some(s) => Some(crate::utils::parse_proxy_auth(s)?),
178+
None => None,
179+
},
180+
retries: config.proxy_retries.unwrap_or(5),
181+
timeout: config.proxy_timeout,
182+
},
183+
158184
#[cfg(feature = "cbf")]
159-
compactfilter_opts: crate::commands::CompactFilterOpts { conn_count: 2 },
185+
compactfilter_opts: crate::commands::CompactFilterOpts {
186+
conn_count: config.conn_count.unwrap_or(2),
187+
},
160188
})
161189
}
162190
}
@@ -218,6 +246,16 @@ mod tests {
218246
rpc_password: None,
219247
#[cfg(feature = "rpc")]
220248
cookie: None,
249+
#[cfg(any(feature = "electrum", feature = "esplora"))]
250+
proxy: None,
251+
#[cfg(any(feature = "electrum", feature = "esplora"))]
252+
proxy_auth: None,
253+
#[cfg(any(feature = "electrum", feature = "esplora"))]
254+
proxy_retries: None,
255+
#[cfg(any(feature = "electrum", feature = "esplora"))]
256+
proxy_timeout: None,
257+
#[cfg(feature = "cbf")]
258+
conn_count: None,
221259
};
222260

223261
let opts: WalletOpts = (&wallet_config)
@@ -293,6 +331,16 @@ mod tests {
293331
rpc_password: None,
294332
#[cfg(feature = "rpc")]
295333
cookie: None,
334+
#[cfg(any(feature = "electrum", feature = "esplora"))]
335+
proxy: None,
336+
#[cfg(any(feature = "electrum", feature = "esplora"))]
337+
proxy_auth: None,
338+
#[cfg(any(feature = "electrum", feature = "esplora"))]
339+
proxy_retries: None,
340+
#[cfg(any(feature = "electrum", feature = "esplora"))]
341+
proxy_timeout: None,
342+
#[cfg(feature = "cbf")]
343+
conn_count: None,
296344
};
297345

298346
let result: Result<WalletOpts, Error> = (&inner).try_into();

src/handlers/config.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::config::{WalletConfig, WalletConfigInner};
1212
use crate::error::BDKCliError as Error;
1313
use crate::handlers::Init;
1414
use crate::handlers::{AppCommand, AppContext};
15-
#[cfg(feature = "sqlite")]
15+
#[cfg(any(feature = "sqlite", feature = "redb"))]
1616
use crate::persister::DatabaseType;
1717
use crate::utils::types::{StatusResult, WalletsListResult};
1818
use bdk_wallet::bitcoin::Network;
@@ -95,7 +95,6 @@ impl AppCommand<AppContext<Init>> for SaveConfigCommand {
9595
network: ctx.network.to_string(),
9696
ext_descriptor: self.wallet_opts.ext_descriptor.clone(),
9797
int_descriptor: self.wallet_opts.int_descriptor.clone(),
98-
9998
#[cfg(any(feature = "sqlite", feature = "redb"))]
10099
database_type: match self.wallet_opts.database_type {
101100
#[cfg(feature = "sqlite")]
@@ -125,6 +124,22 @@ impl AppCommand<AppContext<Init>> for SaveConfigCommand {
125124
parallel_requests: Some(self.wallet_opts.parallel_requests),
126125
#[cfg(feature = "rpc")]
127126
cookie: self.wallet_opts.cookie.clone(),
127+
128+
#[cfg(any(feature = "electrum", feature = "esplora"))]
129+
proxy: self.wallet_opts.proxy_opts.proxy.clone(),
130+
#[cfg(any(feature = "electrum", feature = "esplora"))]
131+
proxy_auth: self
132+
.wallet_opts
133+
.proxy_opts
134+
.proxy_auth
135+
.as_ref()
136+
.map(|(u, p)| format!("{u}:{p}")),
137+
#[cfg(any(feature = "electrum", feature = "esplora"))]
138+
proxy_retries: Some(self.wallet_opts.proxy_opts.retries),
139+
#[cfg(any(feature = "electrum", feature = "esplora"))]
140+
proxy_timeout: self.wallet_opts.proxy_opts.timeout,
141+
#[cfg(feature = "cbf")]
142+
conn_count: Some(self.wallet_opts.compactfilter_opts.conn_count),
128143
};
129144

130145
config.wallets.insert(wallet_name.clone(), wallet_config);

src/handlers/offline.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use bdk_wallet::{KeychainKind, SignOptions};
1616
use clap::Parser;
1717
use serde_json::json;
1818
use std::collections::BTreeMap;
19-
use std::str::FromStr;
2019
#[cfg(feature = "silent-payments")]
2120
use {
2221
crate::utils::common::parse_sp_code_value_pairs,
@@ -311,6 +310,8 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CreateTxCommand {
311310

312311
let psbt = tx_builder.finish()?;
313312

313+
// let psbt_base64 = BASE64_STANDARD.encode(psbt.serialize());
314+
314315
Ok(PsbtResult::new(&psbt, Some(false)))
315316
}
316317
}
@@ -540,7 +541,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CreateSpTxCommand {
540541
pub struct BumpFeeCommand {
541542
/// TXID of the transaction to update.
542543
#[arg(env = "TXID", long = "txid")]
543-
pub txid: String,
544+
pub txid: Txid,
544545

545546
/// Allows the wallet to reduce the amount to the specified address in order to increase fees.
546547
#[arg(env = "SHRINK_ADDRESS", long = "shrink", value_parser = parse_address)]
@@ -574,9 +575,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for BumpFeeCommand {
574575
fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<Self::Output, Error> {
575576
let wallet = &mut ctx.state.wallet;
576577

577-
let txid = Txid::from_str(self.txid.as_str())?;
578-
579-
let mut tx_builder = wallet.build_fee_bump(txid)?;
578+
let mut tx_builder = wallet.build_fee_bump(self.txid)?;
580579
let fee_rate =
581580
FeeRate::from_sat_per_vb(self.fee_rate as u64).unwrap_or(FeeRate::BROADCAST_MIN);
582581
tx_builder.fee_rate(fee_rate);
@@ -761,7 +760,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CombinePsbtCommand {
761760
Ok(acc)
762761
})?;
763762

764-
Ok(PsbtResult::new(&final_psbt, None))
763+
Ok(PsbtResult::new(&final_psbt, Some(false)))
765764
}
766765
}
767766

src/handlers/online.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub struct FullScanCommand {
8383
/// Stop searching addresses for transactions after finding an unused gap of this length.
8484
#[arg(env = "STOP_GAP", long = "scan-stop-gap", default_value = "20")]
8585
stop_gap: usize,
86-
// #[clap(long, default_value = "5")]
86+
#[clap(long, default_value = "5")]
8787
pub parallel_request: usize,
8888
}
8989

src/handlers/repl.rs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ pub(crate) async fn respond(
4343
) -> Result<bool, String> {
4444
let args = shlex::split(line).ok_or("error: Invalid quoting".to_string())?;
4545

46-
let mut repl_args = vec!["repl".to_string()];
47-
repl_args.extend(args);
48-
49-
let repl_subcommand = match ReplSubCommand::try_parse_from(&repl_args) {
46+
let repl_subcommand = match ReplSubCommand::try_parse_from(&args) {
5047
Ok(cmd) => cmd,
5148
Err(e) => {
5249
writeln!(std::io::stdout(), "{}", e).map_err(|e| e.to_string())?;
@@ -58,10 +55,7 @@ pub(crate) async fn respond(
5855
ReplSubCommand::Wallet { subcommand } => match subcommand {
5956
WalletSubCommand::OfflineWalletSubCommand(cmd) => {
6057
let mut ctx = AppContext::new_offline_wallet(network, datadir, wallet);
61-
cmd.execute(&mut ctx)
62-
.map_err(|e| e.to_string())?
63-
.write_out(std::io::stdout())
64-
.map_err(|e| e.to_string())?;
58+
cmd.execute(&mut ctx).map_err(|e| e.to_string())?;
6559
Some(())
6660
}
6761
#[cfg(any(
@@ -80,20 +74,16 @@ pub(crate) async fn respond(
8074
wallet_name.to_string(),
8175
);
8276

83-
cmd.execute(&mut ctx)
84-
.await
85-
.map_err(|e| e.to_string())?
86-
.write_out(std::io::stdout())
87-
.map_err(|e| e.to_string())?;
77+
cmd.execute(&mut ctx).await.map_err(|e| e.to_string())?;
8878
Some(())
8979
}
90-
WalletSubCommand::Config(config_cmd) => {
91-
let mut ctx = AppContext::new(network, datadir);
92-
config_cmd
93-
.execute(&mut ctx)
94-
.map_err(|e| e.to_string())?
95-
.write_out(std::io::stdout())
96-
.map_err(|e| e.to_string())?;
80+
WalletSubCommand::Config(_) => {
81+
writeln!(
82+
std::io::stdout(),
83+
"`config` is not available in REPL mode — the wallet for this session \
84+
is already loaded. Exit and run `bdk-cli wallet --wallet <name> config ...`."
85+
)
86+
.map_err(|e| e.to_string())?;
9787
Some(())
9888
}
9989
},

src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::handlers::{AppCommand, AppContext};
2929
use crate::utils::output::FormatOutput;
3030
use crate::utils::runtime::WalletRuntime;
3131
use crate::utils::{command_requires_db, prepare_home_dir};
32-
use clap::Parser;
32+
use clap::{CommandFactory, Parser};
3333

3434
#[tokio::main]
3535
async fn main() {
@@ -199,8 +199,14 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
199199

200200
cmd.execute(&mut ctx)?.write_out(std::io::stdout())?;
201201
}
202-
CliSubCommand::Completions { shell: _ } => unimplemented!(),
203-
202+
CliSubCommand::Completions { shell } => {
203+
clap_complete::generate(
204+
shell,
205+
&mut CliOpts::command(),
206+
"bdk-cli",
207+
&mut std::io::stdout(),
208+
);
209+
}
204210
#[cfg(feature = "silent-payments")]
205211
CliSubCommand::SilentPaymentCode(cmd) => {
206212
let mut ctx = AppContext::new(cli_opts.network, home_dir);

0 commit comments

Comments
 (0)