async_melt.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. //! Async Melt Integration Tests
  2. //!
  3. //! This file contains tests for async melt functionality using the Prefer: respond-async header.
  4. //!
  5. //! Test Scenarios:
  6. //! - Async melt returns PENDING state immediately
  7. //! - Synchronous melt still works correctly (backward compatibility)
  8. //! - Background task completion
  9. //! - Quote polling pattern
  10. use std::sync::Arc;
  11. use bip39::Mnemonic;
  12. use cashu::PaymentMethod;
  13. use cdk::amount::SplitTarget;
  14. use cdk::nuts::{CurrencyUnit, MeltQuoteState};
  15. use cdk::wallet::Wallet;
  16. use cdk::StreamExt;
  17. use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
  18. use cdk_sqlite::wallet::memory;
  19. const MINT_URL: &str = "http://127.0.0.1:8086";
  20. /// Test: Async melt returns PENDING state immediately
  21. ///
  22. /// This test validates that when calling melt with Prefer: respond-async header,
  23. /// the mint returns immediately with PENDING state.
  24. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  25. async fn test_async_melt_returns_pending() {
  26. let wallet = Wallet::new(
  27. MINT_URL,
  28. CurrencyUnit::Sat,
  29. Arc::new(memory::empty().await.unwrap()),
  30. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  31. None,
  32. )
  33. .expect("failed to create new wallet");
  34. // Step 1: Mint some tokens
  35. let mint_quote = wallet.mint_bolt11_quote(100.into(), None).await.unwrap();
  36. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  37. let _proofs = proof_streams
  38. .next()
  39. .await
  40. .expect("payment")
  41. .expect("no error");
  42. let balance = wallet.total_balance().await.unwrap();
  43. assert_eq!(balance, 100.into());
  44. // Step 2: Create a melt quote
  45. let fake_invoice_description = FakeInvoiceDescription {
  46. pay_invoice_state: MeltQuoteState::Paid,
  47. check_payment_state: MeltQuoteState::Paid,
  48. pay_err: false,
  49. check_err: false,
  50. };
  51. let invoice: cashu::Bolt11Invoice = create_fake_invoice(
  52. 50_000, // 50 sats in millisats
  53. serde_json::to_string(&fake_invoice_description).unwrap(),
  54. );
  55. let melt_quote = wallet
  56. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  57. .await
  58. .unwrap();
  59. // Step 3: Call melt (wallet handles proof selection internally)
  60. let start_time = std::time::Instant::now();
  61. // This should complete and return the final state
  62. let prepared = wallet
  63. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  64. .await
  65. .unwrap();
  66. let confirmed = prepared.confirm().await.unwrap();
  67. let elapsed = start_time.elapsed();
  68. // For now, this is synchronous, so it will take longer
  69. println!("Melt took {:?}", elapsed);
  70. // Step 4: Verify the melt completed successfully
  71. assert_eq!(
  72. confirmed.state(),
  73. MeltQuoteState::Paid,
  74. "Melt should complete with PAID state"
  75. );
  76. }
  77. /// Test: Synchronous melt still works correctly
  78. ///
  79. /// This test ensures backward compatibility - melt without Prefer header
  80. /// still blocks until completion and returns the final state.
  81. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  82. async fn test_sync_melt_completes_fully() {
  83. let wallet = Wallet::new(
  84. MINT_URL,
  85. CurrencyUnit::Sat,
  86. Arc::new(memory::empty().await.unwrap()),
  87. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  88. None,
  89. )
  90. .expect("failed to create new wallet");
  91. // Step 1: Mint some tokens
  92. let mint_quote = wallet.mint_bolt11_quote(100.into(), None).await.unwrap();
  93. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  94. let _proofs = proof_streams
  95. .next()
  96. .await
  97. .expect("payment")
  98. .expect("no error");
  99. let balance = wallet.total_balance().await.unwrap();
  100. assert_eq!(balance, 100.into());
  101. // Step 2: Create a melt quote
  102. let fake_invoice_description = FakeInvoiceDescription {
  103. pay_invoice_state: MeltQuoteState::Paid,
  104. check_payment_state: MeltQuoteState::Paid,
  105. pay_err: false,
  106. check_err: false,
  107. };
  108. let invoice = create_fake_invoice(
  109. 50_000, // 50 sats in millisats
  110. serde_json::to_string(&fake_invoice_description).unwrap(),
  111. );
  112. let melt_quote = wallet
  113. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  114. .await
  115. .unwrap();
  116. // Step 3: Call melt with prepare/confirm pattern
  117. let prepared = wallet
  118. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  119. .await
  120. .unwrap();
  121. let confirmed = prepared.confirm().await.unwrap();
  122. // Step 5: Verify response shows payment completed
  123. assert_eq!(
  124. confirmed.state(),
  125. MeltQuoteState::Paid,
  126. "Melt should return PAID state"
  127. );
  128. // Step 6: Verify the quote is PAID in the mint
  129. let quote_state = wallet
  130. .check_melt_quote_status(&melt_quote.id)
  131. .await
  132. .unwrap();
  133. assert_eq!(
  134. quote_state.state,
  135. MeltQuoteState::Paid,
  136. "Quote should be PAID"
  137. );
  138. }