async_melt.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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
  36. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  37. .await
  38. .unwrap();
  39. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  40. let _proofs = proof_streams
  41. .next()
  42. .await
  43. .expect("payment")
  44. .expect("no error");
  45. let balance = wallet.total_balance().await.unwrap();
  46. assert_eq!(balance, 100.into());
  47. // Step 2: Create a melt quote
  48. let fake_invoice_description = FakeInvoiceDescription {
  49. pay_invoice_state: MeltQuoteState::Paid,
  50. check_payment_state: MeltQuoteState::Paid,
  51. pay_err: false,
  52. check_err: false,
  53. };
  54. let invoice: cashu::Bolt11Invoice = create_fake_invoice(
  55. 50_000, // 50 sats in millisats
  56. serde_json::to_string(&fake_invoice_description).unwrap(),
  57. );
  58. let melt_quote = wallet
  59. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  60. .await
  61. .unwrap();
  62. // Step 3: Call melt (wallet handles proof selection internally)
  63. let start_time = std::time::Instant::now();
  64. // This should complete and return the final state
  65. let prepared = wallet
  66. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  67. .await
  68. .unwrap();
  69. let confirmed = prepared.confirm().await.unwrap();
  70. let elapsed = start_time.elapsed();
  71. // For now, this is synchronous, so it will take longer
  72. println!("Melt took {:?}", elapsed);
  73. // Step 4: Verify the melt completed successfully
  74. assert_eq!(
  75. confirmed.state(),
  76. MeltQuoteState::Paid,
  77. "Melt should complete with PAID state"
  78. );
  79. }
  80. /// Test: Synchronous melt still works correctly
  81. ///
  82. /// This test ensures backward compatibility - melt without Prefer header
  83. /// still blocks until completion and returns the final state.
  84. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  85. async fn test_sync_melt_completes_fully() {
  86. let wallet = Wallet::new(
  87. MINT_URL,
  88. CurrencyUnit::Sat,
  89. Arc::new(memory::empty().await.unwrap()),
  90. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  91. None,
  92. )
  93. .expect("failed to create new wallet");
  94. // Step 1: Mint some tokens
  95. let mint_quote = wallet
  96. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  97. .await
  98. .unwrap();
  99. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  100. let _proofs = proof_streams
  101. .next()
  102. .await
  103. .expect("payment")
  104. .expect("no error");
  105. let balance = wallet.total_balance().await.unwrap();
  106. assert_eq!(balance, 100.into());
  107. // Step 2: Create a melt quote
  108. let fake_invoice_description = FakeInvoiceDescription {
  109. pay_invoice_state: MeltQuoteState::Paid,
  110. check_payment_state: MeltQuoteState::Paid,
  111. pay_err: false,
  112. check_err: false,
  113. };
  114. let invoice = create_fake_invoice(
  115. 50_000, // 50 sats in millisats
  116. serde_json::to_string(&fake_invoice_description).unwrap(),
  117. );
  118. let melt_quote = wallet
  119. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  120. .await
  121. .unwrap();
  122. // Step 3: Call melt with prepare/confirm pattern
  123. let prepared = wallet
  124. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  125. .await
  126. .unwrap();
  127. let confirmed = prepared.confirm().await.unwrap();
  128. // Step 5: Verify response shows payment completed
  129. assert_eq!(
  130. confirmed.state(),
  131. MeltQuoteState::Paid,
  132. "Melt should return PAID state"
  133. );
  134. // Step 6: Verify the quote is PAID in the mint
  135. let quote_state = wallet
  136. .check_melt_quote_status(&melt_quote.id)
  137. .await
  138. .unwrap();
  139. assert_eq!(
  140. quote_state.state,
  141. MeltQuoteState::Paid,
  142. "Quote should be PAID"
  143. );
  144. }