From c74b98e7097406cfd5965f187db9679d5fd601d2 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Mon, 23 Dec 2024 20:22:42 -0500 Subject: [PATCH] fix: itests --- .../tests/fake_wallet.rs | 8 +- crates/cdk-integration-tests/tests/mod.rs | 1 + .../tests/pure_tests/mod.rs | 310 ++++++++++++++++++ crates/cdk-integration-tests/tests/regtest.rs | 4 +- 4 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 crates/cdk-integration-tests/tests/mod.rs create mode 100644 crates/cdk-integration-tests/tests/pure_tests/mod.rs diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index 279f0eb65..6c0e67adf 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -390,6 +390,7 @@ async fn test_fake_mint_with_witness() -> Result<()> { Arc::new(WalletMemoryDatabase::default()), &Mnemonic::generate(12)?.to_seed_normalized(""), None, + None, )?; let mint_quote = wallet.mint_quote(100.into(), None).await?; @@ -412,6 +413,7 @@ async fn test_fake_mint_without_witness() -> Result<()> { Arc::new(WalletMemoryDatabase::default()), &Mnemonic::generate(12)?.to_seed_normalized(""), None, + None, )?; let mint_quote = wallet.mint_quote(100.into(), None).await?; @@ -431,7 +433,7 @@ async fn test_fake_mint_without_witness() -> Result<()> { signature: None, }; - let response = http_client.post_mint(request.clone()).await; + let response = http_client.post_mint(request.clone(), None).await; match response { Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()), @@ -440,7 +442,6 @@ async fn test_fake_mint_without_witness() -> Result<()> { } } -// TODO: Rewrite this test to include witness wrong #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_fake_mint_with_wrong_witness() -> Result<()> { let wallet = Wallet::new( @@ -449,6 +450,7 @@ async fn test_fake_mint_with_wrong_witness() -> Result<()> { Arc::new(WalletMemoryDatabase::default()), &Mnemonic::generate(12)?.to_seed_normalized(""), None, + None, )?; let mint_quote = wallet.mint_quote(100.into(), None).await?; @@ -472,7 +474,7 @@ async fn test_fake_mint_with_wrong_witness() -> Result<()> { request.sign(secret_key)?; - let response = http_client.post_mint(request.clone()).await; + let response = http_client.post_mint(request.clone(), None).await; match response { Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()), diff --git a/crates/cdk-integration-tests/tests/mod.rs b/crates/cdk-integration-tests/tests/mod.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/crates/cdk-integration-tests/tests/mod.rs @@ -0,0 +1 @@ + diff --git a/crates/cdk-integration-tests/tests/pure_tests/mod.rs b/crates/cdk-integration-tests/tests/pure_tests/mod.rs new file mode 100644 index 000000000..a36eb1d23 --- /dev/null +++ b/crates/cdk-integration-tests/tests/pure_tests/mod.rs @@ -0,0 +1,310 @@ +use std::assert_eq; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::str::FromStr; +use std::sync::Arc; + +use async_trait::async_trait; +use cdk::amount::SplitTarget; +use cdk::cdk_database::mint_memory::MintMemoryDatabase; +use cdk::cdk_database::WalletMemoryDatabase; +use cdk::nuts::nutxx1::MintAuthRequest; +use cdk::nuts::{ + AuthToken, CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse, + MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, + MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, MintQuoteState, + Nuts, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse, +}; +use cdk::types::QuoteTTL; +use cdk::util::unix_time; +use cdk::wallet::MintConnector; +use cdk::{Amount, Error, Mint, Wallet}; +use cdk_integration_tests::create_backends_fake_wallet; +use rand::random; +use tokio::sync::Notify; +use uuid::Uuid; + +struct DirectMintConnection { + mint: Arc, +} + +impl Debug for DirectMintConnection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DirectMintConnection {{ mint_info: {:?} }}", + self.mint.mint_info + ) + } +} + +/// Implements the generic [MintConnector] (i.e. use the interface that expects to communicate +/// to a generic mint, where we don't know that quote ID's are [Uuid]s) for [DirectMintConnection], +/// where we know we're dealing with a mint that uses [Uuid]s for quotes. +/// Convert the requests and responses between the [String] and [Uuid] variants as necessary. +#[async_trait] +impl MintConnector for DirectMintConnection { + async fn get_mint_keys(&self, _auth_token: Option) -> Result, Error> { + self.mint.pubkeys().await.map(|pks| pks.keysets) + } + + async fn get_mint_keyset( + &self, + keyset_id: Id, + _auth_token: Option, + ) -> Result { + self.mint + .keyset(&keyset_id) + .await + .and_then(|res| res.ok_or(Error::UnknownKeySet)) + } + + async fn get_mint_keysets( + &self, + _auth_token: Option, + ) -> Result { + self.mint.keysets().await + } + + async fn post_mint_quote( + &self, + request: MintQuoteBolt11Request, + _auth_token: Option, + ) -> Result, Error> { + self.mint + .get_mint_bolt11_quote(None, request) + .await + .map(Into::into) + } + + async fn get_mint_quote_status( + &self, + quote_id: &str, + _auth_token: Option, + ) -> Result, Error> { + let quote_id_uuid = Uuid::from_str(quote_id).unwrap(); + self.mint + .check_mint_quote(None, "e_id_uuid) + .await + .map(Into::into) + } + + async fn post_mint( + &self, + request: MintBolt11Request, + _auth_token: Option, + ) -> Result { + let request_uuid = request.try_into().unwrap(); + self.mint.process_mint_request(None, request_uuid).await + } + + async fn post_melt_quote( + &self, + request: MeltQuoteBolt11Request, + _auth_token: Option, + ) -> Result, Error> { + self.mint + .get_melt_bolt11_quote(None, &request) + .await + .map(Into::into) + } + + async fn get_melt_quote_status( + &self, + quote_id: &str, + _auth_token: Option, + ) -> Result, Error> { + let quote_id_uuid = Uuid::from_str(quote_id).unwrap(); + self.mint + .check_melt_quote(None, "e_id_uuid) + .await + .map(Into::into) + } + + async fn post_melt( + &self, + request: MeltBolt11Request, + _auth_token: Option, + ) -> Result, Error> { + let request_uuid = request.try_into().unwrap(); + self.mint + .melt_bolt11(None, &request_uuid) + .await + .map(Into::into) + } + + async fn post_swap( + &self, + swap_request: SwapRequest, + _auth_token: Option, + ) -> Result { + self.mint.process_swap_request(None, swap_request).await + } + + async fn get_mint_info(&self) -> Result { + Ok(self.mint.mint_info().clone().time(unix_time())) + } + + async fn post_check_state( + &self, + request: CheckStateRequest, + _auth_token: Option, + ) -> Result { + self.mint.check_state(&request).await + } + + async fn post_restore( + &self, + request: RestoreRequest, + _auth_token: Option, + ) -> Result { + self.mint.restore(request).await + } + + /// Get Blind Auth keys + async fn get_mint_blind_auth_keys(&self) -> Result, Error> { + todo!(); + } + /// Get Blind Auth Keyset + async fn get_mint_blind_auth_keyset(&self, _keyset_id: Id) -> Result { + todo!(); + } + /// Get Blind Auth keysets + async fn get_mint_blind_auth_keysets(&self) -> Result { + todo!(); + } + /// Post mint blind auth + async fn post_mint_blind_auth( + &self, + _request: MintAuthRequest, + ) -> Result { + todo!(); + } +} + +fn get_mint_connector(mint: Arc) -> DirectMintConnection { + DirectMintConnection { mint } +} + +async fn create_and_start_test_mint() -> anyhow::Result> { + let fee: u64 = 0; + let mut supported_units = HashMap::new(); + supported_units.insert(CurrencyUnit::Sat, (fee, 32)); + + let nuts = Nuts::new() + .nut07(true) + .nut08(true) + .nut09(true) + .nut10(true) + .nut11(true) + .nut12(true) + .nut14(true); + + let mint_info = MintInfo::new().nuts(nuts); + + let quote_ttl = QuoteTTL::new(10000, 10000); + + let mint_url = "http://aaa"; + + let seed = random::<[u8; 32]>(); + let mint: Mint = Mint::new( + mint_url, + &seed, + mint_info, + quote_ttl, + Arc::new(MintMemoryDatabase::default()), + create_backends_fake_wallet(), + supported_units, + HashMap::new(), + HashMap::new(), + ) + .await?; + + let mint_arc = Arc::new(mint); + + let mint_arc_clone = Arc::clone(&mint_arc); + let shutdown = Arc::new(Notify::new()); + tokio::spawn({ + let shutdown = Arc::clone(&shutdown); + async move { mint_arc_clone.wait_for_paid_invoices(shutdown).await } + }); + + Ok(mint_arc) +} + +fn create_test_wallet_for_mint(mint: Arc) -> anyhow::Result> { + let connector = get_mint_connector(mint); + + let seed = random::<[u8; 32]>(); + let mint_url = connector.mint.mint_url.to_string(); + let unit = CurrencyUnit::Sat; + + let localstore = WalletMemoryDatabase::default(); + let mut wallet = Wallet::new(&mint_url, unit, Arc::new(localstore), &seed, None, None)?; + + wallet.set_client(connector); + + Ok(Arc::new(wallet)) +} + +/// Creates a mint quote for the given amount and checks its state in a loop. Returns when +/// amount is minted. +async fn receive(wallet: Arc, amount: u64) -> anyhow::Result { + let desired_amount = Amount::from(amount); + let quote = wallet.mint_quote(desired_amount, None).await?; + + loop { + let status = wallet.mint_quote_state("e.id).await?; + if status.state == MintQuoteState::Paid { + break; + } + } + + wallet + .mint("e.id, SplitTarget::default(), None) + .await + .map_err(Into::into) +} + +mod nut03 { + use cdk::nuts::nut00::ProofsMethods; + use cdk::wallet::SendKind; + + use crate::integration_tests_pure::*; + + #[tokio::test] + async fn test_swap_to_send() -> anyhow::Result<()> { + let mint_bob = create_and_start_test_mint().await?; + let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())?; + + // Alice gets 64 sats + receive(wallet_alice.clone(), 64).await?; + let balance_alice = wallet_alice.total_balance().await?; + assert_eq!(Amount::from(64), balance_alice); + + // Alice wants to send 40 sats, which internally swaps + let token = wallet_alice + .send( + Amount::from(40), + None, + None, + &SplitTarget::None, + &SendKind::OnlineExact, + false, + ) + .await?; + assert_eq!(Amount::from(40), token.proofs().total_amount()?); + assert_eq!(Amount::from(24), wallet_alice.total_balance().await?); + + // Alice sends cashu, Carol receives + let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())?; + let received_amount = wallet_carol + .receive_proofs(token.proofs(), SplitTarget::None, &[], &[]) + .await?; + + assert_eq!(Amount::from(40), received_amount); + assert_eq!(Amount::from(40), wallet_carol.total_balance().await?); + + Ok(()) + } +} diff --git a/crates/cdk-integration-tests/tests/regtest.rs b/crates/cdk-integration-tests/tests/regtest.rs index 918a502ae..49d57ffc0 100644 --- a/crates/cdk-integration-tests/tests/regtest.rs +++ b/crates/cdk-integration-tests/tests/regtest.rs @@ -398,8 +398,8 @@ async fn test_cached_mint() -> Result<()> { request.sign(secret_key.expect("Secret key on quote"))?; - let response = http_client.post_mint(request.clone()).await?; - let response1 = http_client.post_mint(request).await?; + let response = http_client.post_mint(request.clone(), None).await?; + let response1 = http_client.post_mint(request, None).await?; assert!(response == response1); Ok(())