| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849 |
- //! Integration tests for WalletRepository
- //!
- //! These tests verify the WalletRepository functionality including:
- //! - Basic mint/melt operations across multiple mints
- //! - Token receive and send operations
- //! - Automatic mint selection for melts
- //! - Cross-mint transfers
- //!
- //! Tests use the fake wallet backend for deterministic behavior.
- use std::env;
- use std::path::PathBuf;
- use std::str::FromStr;
- use std::sync::Arc;
- use bip39::Mnemonic;
- use cdk::amount::{Amount, SplitTarget};
- use cdk::mint_url::MintUrl;
- use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
- use cdk::nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, PaymentMethod, Token};
- use cdk::wallet::{ReceiveOptions, SendOptions, WalletRepository, WalletRepositoryBuilder};
- use cdk_common::wallet::WalletKey;
- use cdk_integration_tests::{create_invoice_for_env, get_mint_url_from_env, pay_if_regtest};
- use cdk_sqlite::wallet::memory;
- use lightning_invoice::Bolt11Invoice;
- // Helper function to get temp directory from environment or fallback
- fn get_test_temp_dir() -> PathBuf {
- match env::var("CDK_ITESTS_DIR") {
- Ok(dir) => PathBuf::from(dir),
- Err(_) => panic!("Unknown test dir"),
- }
- }
- // Helper to create a WalletRepository with a fresh seed and in-memory database
- async fn create_test_wallet_repository() -> cdk::wallet::WalletRepository {
- let seed = Mnemonic::generate(12).unwrap().to_seed_normalized("");
- let localstore = Arc::new(memory::empty().await.unwrap());
- WalletRepositoryBuilder::new()
- .localstore(localstore)
- .seed(seed)
- .build()
- .await
- .expect("failed to create wallet repository")
- }
- /// Helper to fund a WalletRepository at a specific mint
- async fn fund_wallet_repository(
- repo: &WalletRepository,
- mint_url: &MintUrl,
- amount: Amount,
- ) -> Amount {
- let wallet = repo
- .get_wallet(mint_url, &CurrencyUnit::Sat)
- .await
- .expect("wallet not found");
- let mint_quote = wallet
- .mint_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- Some(amount),
- None,
- None,
- )
- .await
- .unwrap();
- let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
- pay_if_regtest(&get_test_temp_dir(), &invoice)
- .await
- .unwrap();
- let proofs = wallet
- .wait_and_mint_quote(
- mint_quote,
- SplitTarget::default(),
- None,
- std::time::Duration::from_secs(60),
- )
- .await
- .expect("mint failed");
- proofs.total_amount().unwrap()
- }
- /// Test the direct mint() function on WalletRepository
- ///
- /// This test verifies:
- /// 1. Create a mint quote
- /// 2. Pay the invoice
- /// 3. Poll until quote is paid (like a real wallet would)
- /// 4. Call mint() directly (not wait_for_mint_quote)
- /// 5. Verify tokens are received
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_mint() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .expect("failed to get wallet");
- // Create mint quote
- let mint_quote = wallet
- .mint_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- Some(100.into()),
- None,
- None,
- )
- .await
- .unwrap();
- // Pay the invoice (in regtest mode) - for fake wallet, payment is simulated automatically
- let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
- pay_if_regtest(&get_test_temp_dir(), &invoice)
- .await
- .unwrap();
- // Poll for quote to be paid (like a real wallet would)
- let mut quote_status = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- let timeout = tokio::time::Duration::from_secs(30);
- let start = tokio::time::Instant::now();
- while quote_status.state != MintQuoteState::Paid && quote_status.state != MintQuoteState::Issued
- {
- if start.elapsed() > timeout {
- panic!(
- "Timeout waiting for quote to be paid, state: {:?}",
- quote_status.state
- );
- }
- tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
- quote_status = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- }
- tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
- let _ = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- // Call mint() directly (quote should be Paid at this point)
- let proofs = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await
- .unwrap();
- let minted_amount = proofs.total_amount().unwrap();
- assert_eq!(minted_amount, 100.into(), "Should mint exactly 100 sats");
- // Verify balance
- let balances = wallet_repository.total_balance().await.unwrap();
- let balance = balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert_eq!(balance, 100.into(), "Total balance should be 100 sats");
- }
- /// Test the melt() function with automatic mint selection
- ///
- /// This test verifies:
- /// 1. Fund wallet at a mint
- /// 2. Call melt() without specifying mint (auto-selection)
- /// 3. Verify payment is made
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_melt_auto_select() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Fund the wallet
- let funded_amount = fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- assert_eq!(funded_amount, 100.into());
- // Create an invoice to pay
- let invoice = create_invoice_for_env(Some(50)).await.unwrap();
- // Get wallet and call melt
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let melt_quote = wallet
- .melt_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- invoice.to_string(),
- None,
- None,
- )
- .await
- .unwrap();
- let melt_result = wallet
- .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
- .await
- .unwrap()
- .confirm()
- .await
- .unwrap();
- assert_eq!(
- melt_result.state(),
- MeltQuoteState::Paid,
- "Melt should be paid"
- );
- assert_eq!(melt_result.amount(), 50.into(), "Should melt 50 sats");
- // Verify balance
- let balances = wallet_repository.total_balance().await.unwrap();
- let balance = balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert!(
- balance < 100.into(),
- "Balance should be less than 100 after melt"
- );
- }
- /// Test the receive() function on WalletRepository
- ///
- /// This test verifies:
- /// 1. Create a token from a wallet
- /// 2. Receive the token in a different WalletRepository
- /// 3. Verify the token value is received
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_receive() {
- // Create sender wallet and fund it
- let sender_repo = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- sender_repo
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- let funded_amount = fund_wallet_repository(&sender_repo, &mint_url, 100.into()).await;
- assert_eq!(funded_amount, 100.into());
- // Create a token to send
- let sender_wallet = sender_repo
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let prepared_send = sender_wallet
- .prepare_send(50.into(), SendOptions::default())
- .await
- .unwrap();
- let token = prepared_send.confirm(None).await.unwrap();
- let token_string = token.to_string();
- // Create receiver wallet
- let receiver_repo = create_test_wallet_repository().await;
- // Add the same mint as trusted
- receiver_repo
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Receive the token
- let receiver_wallet = receiver_repo
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let received_amount = receiver_wallet
- .receive(&token_string, ReceiveOptions::default())
- .await
- .unwrap();
- // Note: received amount may be slightly less due to fees
- assert!(
- received_amount > Amount::ZERO,
- "Should receive some amount, got {:?}",
- received_amount
- );
- // Verify receiver balance
- let receiver_balances = receiver_repo.total_balance().await.unwrap();
- let receiver_balance = receiver_balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert!(
- receiver_balance > Amount::ZERO,
- "Receiver should have balance"
- );
- // Verify sender balance decreased
- let sender_balances = sender_repo.total_balance().await.unwrap();
- let sender_balance = sender_balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert!(
- sender_balance < 100.into(),
- "Sender balance should be less than 100 after send"
- );
- }
- /// Test the receive() function with allow_untrusted option
- ///
- /// This test verifies:
- /// 1. Create a token from a known mint
- /// 2. Receive with a wallet that doesn't have the mint added
- /// 3. With allow_untrusted=true, the mint should be added automatically
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_receive_untrusted() {
- // Create sender wallet and fund it
- let sender_repo = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- sender_repo
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- let funded_amount = fund_wallet_repository(&sender_repo, &mint_url, 100.into()).await;
- assert_eq!(funded_amount, 100.into());
- // Create a token to send
- let sender_wallet = sender_repo
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let prepared_send = sender_wallet
- .prepare_send(50.into(), SendOptions::default())
- .await
- .unwrap();
- let token = prepared_send.confirm(None).await.unwrap();
- let token_string = token.to_string();
- // Create receiver wallet WITHOUT adding the mint
- let receiver_repo = create_test_wallet_repository().await;
- // Add the mint first, then receive (untrusted receive would require the
- // WalletRepository to auto-add mints, which it doesn't support directly)
- receiver_repo
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Now receive
- let receiver_wallet = receiver_repo
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let received_amount = receiver_wallet
- .receive(&token_string, ReceiveOptions::default())
- .await
- .unwrap();
- assert!(received_amount > Amount::ZERO, "Should receive some amount");
- // Verify the mint is in the wallet
- assert!(
- receiver_repo.has_mint(&mint_url).await,
- "Mint should be in wallet"
- );
- }
- /// Test prepare_send() happy path
- ///
- /// This test verifies:
- /// 1. Fund wallet
- /// 2. Call prepare_send() successfully
- /// 3. Confirm the send and get a token
- /// 4. Verify the token is valid
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_prepare_send_happy_path() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Fund the wallet
- let funded_amount = fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- assert_eq!(funded_amount, 100.into());
- // Prepare send
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let prepared_send = wallet
- .prepare_send(50.into(), SendOptions::default())
- .await
- .unwrap();
- // Get the token
- let token = prepared_send.confirm(None).await.unwrap();
- let token_string = token.to_string();
- // Verify the token can be parsed back
- let parsed_token = Token::from_str(&token_string).unwrap();
- let token_mint_url = parsed_token.mint_url().unwrap();
- assert_eq!(token_mint_url, mint_url, "Token mint URL should match");
- // Get token data to verify value
- let token_data = wallet_repository
- .get_token_data(&parsed_token)
- .await
- .unwrap();
- assert_eq!(token_data.value, 50.into(), "Token value should be 50 sats");
- // Verify wallet balance decreased
- let balances = wallet_repository.total_balance().await.unwrap();
- let balance = balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert_eq!(balance, 50.into(), "Remaining balance should be 50 sats");
- }
- /// Test get_balances() across multiple operations
- ///
- /// This test verifies:
- /// 1. Empty wallet has zero balances
- /// 2. After minting, balance is updated
- /// 3. get_balances() returns per-mint breakdown
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_get_balances() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Check initial balances
- let balances = wallet_repository.get_balances().await.unwrap();
- let initial_balance = balances
- .get(&WalletKey::new(mint_url.clone(), CurrencyUnit::Sat))
- .cloned()
- .unwrap_or(Amount::ZERO);
- assert_eq!(initial_balance, Amount::ZERO, "Initial balance should be 0");
- // Fund the wallet
- fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- // Check balances again
- let balances = wallet_repository.get_balances().await.unwrap();
- let balance = balances
- .get(&WalletKey::new(mint_url.clone(), CurrencyUnit::Sat))
- .cloned()
- .unwrap_or(Amount::ZERO);
- assert_eq!(balance, 100.into(), "Balance should be 100 sats");
- // Verify total_balance matches
- let total_balances = wallet_repository.total_balance().await.unwrap();
- let total = total_balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert_eq!(total, 100.into(), "Total balance should match");
- }
- /// Test list_proofs() function
- ///
- /// This test verifies:
- /// 1. Empty wallet has no proofs
- /// 2. After minting, proofs are listed correctly
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_list_proofs() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Check initial proofs
- let proofs = wallet_repository.list_proofs().await.unwrap();
- let mint_proofs = proofs
- .get(&WalletKey::new(mint_url.clone(), CurrencyUnit::Sat))
- .cloned()
- .unwrap_or_default();
- assert!(mint_proofs.is_empty(), "Should have no proofs initially");
- // Fund the wallet
- fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- // Check proofs again
- let proofs = wallet_repository.list_proofs().await.unwrap();
- let mint_proofs = proofs
- .get(&WalletKey::new(mint_url.clone(), CurrencyUnit::Sat))
- .cloned()
- .unwrap_or_default();
- assert!(!mint_proofs.is_empty(), "Should have proofs after minting");
- // Verify proof total matches balance
- let proof_total: Amount = mint_proofs.total_amount().unwrap();
- assert_eq!(proof_total, 100.into(), "Proof total should be 100 sats");
- }
- /// Test mint management functions (add_mint, remove_wallet, has_mint)
- ///
- /// This test verifies:
- /// 1. has_mint returns false for unknown mints
- /// 2. add_mint adds the mint
- /// 3. has_mint returns true after adding
- /// 4. remove_wallet removes the mint
- /// 5. has_mint returns false after removal
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_mint_management() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- // Initially mint should not be in wallet
- assert!(
- !wallet_repository.has_mint(&mint_url).await,
- "Mint should not be in wallet initially"
- );
- // Add the mint
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Now mint should be in wallet
- assert!(
- wallet_repository.has_mint(&mint_url).await,
- "Mint should be in wallet after adding"
- );
- // Get wallets should include this mint
- let wallets = wallet_repository.get_wallets().await;
- assert!(!wallets.is_empty(), "Should have at least one wallet");
- // Get specific wallet
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await;
- assert!(wallet.is_ok(), "Should be able to get wallet for mint");
- // Get wallets for this mint
- let mint_wallets = wallet_repository.get_wallets_for_mint(&mint_url).await;
- // Remove all wallets for the mint
- for wallet in mint_wallets {
- wallet_repository
- .remove_wallet(mint_url.clone(), wallet.unit.clone())
- .await
- .unwrap();
- }
- // Now mint should not be in wallet
- assert!(
- !wallet_repository.has_mint(&mint_url).await,
- "Mint should not be in wallet after removal"
- );
- }
- /// Test check_all_mint_quotes() function
- ///
- /// This test verifies:
- /// 1. Create a mint quote
- /// 2. Pay the quote
- /// 3. Poll until quote is paid (like a real wallet would)
- /// 4. check_all_mint_quotes() processes paid quotes
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_check_all_mint_quotes() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- // Create a mint quote
- let mint_quote = wallet
- .mint_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- Some(100.into()),
- None,
- None,
- )
- .await
- .unwrap();
- // Pay the invoice (in regtest mode) - for fake wallet, payment is simulated automatically
- let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
- pay_if_regtest(&get_test_temp_dir(), &invoice)
- .await
- .unwrap();
- // Poll for quote to be paid (like a real wallet would)
- let mut quote_status = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- let timeout = tokio::time::Duration::from_secs(30);
- let start = tokio::time::Instant::now();
- while quote_status.state != MintQuoteState::Paid && quote_status.state != MintQuoteState::Issued
- {
- if start.elapsed() > timeout {
- panic!(
- "Timeout waiting for quote to be paid, state: {:?}",
- quote_status.state
- );
- }
- tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
- quote_status = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- }
- tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
- let _ = wallet
- .refresh_mint_quote_status(&mint_quote.id)
- .await
- .unwrap();
- // Check all mint quotes - this should find the paid quote and mint
- let minted_amount = wallet_repository
- .check_all_mint_quotes(Some(mint_url.clone()))
- .await
- .unwrap();
- assert_eq!(
- minted_amount,
- 100.into(),
- "Should mint 100 sats from paid quote"
- );
- // Verify balance
- let balances = wallet_repository.total_balance().await.unwrap();
- let balance = balances
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert_eq!(balance, 100.into(), "Balance should be 100 sats");
- }
- /// Test restore() function
- ///
- /// This test verifies:
- /// 1. Create and fund a wallet with a specific seed
- /// 2. Create a new wallet with the same seed
- /// 3. Call restore() to recover the proofs
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_restore() {
- let seed = Mnemonic::generate(12).unwrap().to_seed_normalized("");
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- // Create first wallet and fund it
- {
- let localstore = Arc::new(memory::empty().await.unwrap());
- let wallet1 = WalletRepositoryBuilder::new()
- .localstore(localstore)
- .seed(seed)
- .build()
- .await
- .expect("failed to create wallet");
- wallet1
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- let funded = fund_wallet_repository(&wallet1, &mint_url, 100.into()).await;
- assert_eq!(funded, 100.into());
- }
- // wallet1 goes out of scope
- // Create second wallet with same seed but fresh storage
- let localstore2 = Arc::new(memory::empty().await.unwrap());
- let wallet2 = WalletRepositoryBuilder::new()
- .localstore(localstore2)
- .seed(seed)
- .build()
- .await
- .expect("failed to create wallet");
- wallet2
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Initially should have no balance
- let balances_before = wallet2.total_balance().await.unwrap();
- let balance_before = balances_before
- .get(&CurrencyUnit::Sat)
- .copied()
- .unwrap_or(Amount::ZERO);
- assert_eq!(balance_before, Amount::ZERO, "Should start with no balance");
- // Restore from mint using the individual wallet
- let wallet = wallet2
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- let restored = wallet.restore().await.unwrap();
- assert_eq!(restored.unspent, 100.into(), "Should restore 100 sats");
- }
- /// Test melt_with_mint() with explicit mint selection
- ///
- /// This test verifies:
- /// 1. Fund wallet
- /// 2. Create melt quote at specific mint
- /// 3. Execute melt()
- /// 4. Verify payment succeeded
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_melt_with_mint() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Fund the wallet
- fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- // Create an invoice to pay
- let invoice = create_invoice_for_env(Some(50)).await.unwrap();
- // Get wallet for operations
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- // Create melt quote at specific mint
- let melt_quote = wallet
- .melt_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- invoice.to_string(),
- None,
- None,
- )
- .await
- .unwrap();
- let melt_result = wallet
- .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
- .await
- .unwrap()
- .confirm()
- .await
- .unwrap();
- assert_eq!(
- melt_result.state(),
- MeltQuoteState::Paid,
- "Melt should be paid"
- );
- // Check melt quote status
- let quote_status = wallet
- .check_melt_quote_status(&melt_quote.id)
- .await
- .unwrap();
- assert_eq!(
- quote_status.state,
- MeltQuoteState::Paid,
- "Quote status should be paid"
- );
- }
- /// Test list_transactions() function
- ///
- /// This test verifies:
- /// 1. Initially no transactions
- /// 2. After minting, transaction is recorded
- /// 3. After melting, transaction is recorded
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_wallet_repository_list_transactions() {
- let wallet_repository = create_test_wallet_repository().await;
- let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
- wallet_repository
- .add_wallet(mint_url.clone())
- .await
- .expect("failed to add mint");
- // Fund the wallet (this creates a mint transaction)
- fund_wallet_repository(&wallet_repository, &mint_url, 100.into()).await;
- // List all transactions
- let transactions = wallet_repository.list_transactions(None).await.unwrap();
- assert!(
- !transactions.is_empty(),
- "Should have at least one transaction after minting"
- );
- // Get wallet for melt operations
- let wallet = wallet_repository
- .get_wallet(&mint_url, &CurrencyUnit::Sat)
- .await
- .unwrap();
- // Create an invoice and melt (this creates a melt transaction)
- let invoice = create_invoice_for_env(Some(50)).await.unwrap();
- let melt_quote = wallet
- .melt_quote(
- PaymentMethod::Known(KnownMethod::Bolt11),
- invoice.to_string(),
- None,
- None,
- )
- .await
- .unwrap();
- wallet
- .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
- .await
- .unwrap()
- .confirm()
- .await
- .unwrap();
- // List transactions again
- let transactions_after = wallet_repository.list_transactions(None).await.unwrap();
- assert!(
- transactions_after.len() > transactions.len(),
- "Should have more transactions after melt"
- );
- }
|