Files
cdk/crates/cdk-integration-tests/tests/fake_wallet.rs
delcin-raj c45efe42d2 fix: resolve flakey ci tests
Before minting the quote wait till the status of the quote is paid
2024-11-06 07:56:47 +00:00

394 lines
12 KiB
Rust

use std::{sync::Arc, time::Duration};
use anyhow::Result;
use bip39::Mnemonic;
use cdk::{
amount::SplitTarget,
cdk_database::WalletMemoryDatabase,
nuts::{
CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintQuoteState, PreMintSecrets, State,
},
wallet::{
client::{HttpClient, HttpClientMethods},
Wallet,
},
};
use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
use cdk_integration_tests::attempt_to_swap_pending;
use tokio::time::sleep;
const MINT_URL: &str = "http://127.0.0.1:8086";
// If both pay and check return pending input proofs should remain pending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_tokens_pending() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Pending,
check_payment_state: MeltQuoteState::Pending,
pay_err: false,
check_err: false,
};
let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
attempt_to_swap_pending(&wallet).await?;
Ok(())
}
// If the pay error fails and the check returns unknown or failed
// The inputs proofs should be unset as spending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_payment_fail() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Unknown,
check_payment_state: MeltQuoteState::Unknown,
pay_err: true,
check_err: false,
};
let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Failed,
check_payment_state: MeltQuoteState::Failed,
pay_err: true,
check_err: false,
};
let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
// The mint should have unset proofs from pending since payment failed
let all_proof = wallet.get_unspent_proofs().await?;
let states = wallet.check_proofs_spent(all_proof).await?;
for state in states {
assert!(state.state == State::Unspent);
}
let wallet_bal = wallet.total_balance().await?;
assert!(wallet_bal == 100.into());
Ok(())
}
// When both the pay_invoice and check_invoice both fail
// the proofs should remain as pending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_payment_fail_and_check() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Unknown,
check_payment_state: MeltQuoteState::Unknown,
pay_err: true,
check_err: true,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let pending = wallet
.localstore
.get_proofs(None, None, Some(vec![State::Pending]), None)
.await?;
assert!(!pending.is_empty());
Ok(())
}
// In the case that the ln backend returns a failed status but does not error
// The mint should do a second check, then remove proofs from pending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_payment_return_fail_status() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Failed,
check_payment_state: MeltQuoteState::Failed,
pay_err: false,
check_err: false,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Unknown,
check_payment_state: MeltQuoteState::Unknown,
pay_err: false,
check_err: false,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let pending = wallet
.localstore
.get_proofs(None, None, Some(vec![State::Pending]), None)
.await?;
assert!(pending.is_empty());
Ok(())
}
// In the case that the ln backend returns a failed status but does not error
// The mint should do a second check, then remove proofs from pending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_payment_error_unknown() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Failed,
check_payment_state: MeltQuoteState::Unknown,
pay_err: true,
check_err: false,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Unknown,
check_payment_state: MeltQuoteState::Unknown,
pay_err: true,
check_err: false,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
let pending = wallet
.localstore
.get_proofs(None, None, Some(vec![State::Pending]), None)
.await?;
assert!(pending.is_empty());
Ok(())
}
// In the case that the ln backend returns an err
// The mint should do a second check, that returns paid
// Proofs should remain pending
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_payment_err_paid() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription {
pay_invoice_state: MeltQuoteState::Failed,
check_payment_state: MeltQuoteState::Paid,
pay_err: true,
check_err: false,
};
let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
// The melt should error at the payment invoice command
let melt = wallet.melt(&melt_quote.id).await;
assert!(melt.is_err());
attempt_to_swap_pending(&wallet).await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_fake_melt_change_in_quote() -> Result<()> {
let wallet = Wallet::new(
MINT_URL,
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;
let mint_quote = wallet.mint_quote(100.into(), None).await?;
wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
let _mint_amount = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;
let fake_description = FakeInvoiceDescription::default();
let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
let proofs = wallet.get_unspent_proofs().await?;
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
let keyset = wallet.get_active_mint_keyset().await?;
let premint_secrets = PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default())?;
let client = HttpClient::new();
let melt_request = MeltBolt11Request {
quote: melt_quote.id.clone(),
inputs: proofs.clone(),
outputs: Some(premint_secrets.blinded_messages()),
};
let melt_response = client.post_melt(MINT_URL.parse()?, melt_request).await?;
assert!(melt_response.change.is_some());
let check = wallet.melt_quote_status(&melt_quote.id).await?;
let mut melt_change = melt_response.change.unwrap();
melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
let mut check = check.change.unwrap();
check.sort_by(|a, b| a.amount.cmp(&b.amount));
assert_eq!(melt_change, check);
Ok(())
}
// Keep polling the state of the mint quote id until it's paid
async fn wait_for_mint_to_be_paid(wallet: &Wallet, mint_quote_id: &str) -> Result<()> {
loop {
let status = wallet.mint_quote_state(mint_quote_id).await?;
if status.state == MintQuoteState::Paid {
return Ok(());
}
sleep(Duration::from_millis(5)).await;
}
}