|
|
@@ -3,7 +3,7 @@
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
use cashu::quote_id::QuoteId;
|
|
|
-use cashu::{Amount, Id, SecretKey};
|
|
|
+use cashu::{Amount, BlindSignature, Id, SecretKey};
|
|
|
|
|
|
use crate::database::mint::test::unique_string;
|
|
|
use crate::database::mint::{Database, Error, KeysDatabase};
|
|
|
@@ -600,3 +600,742 @@ where
|
|
|
assert!(retrieved.is_none());
|
|
|
tx3.commit().await.unwrap();
|
|
|
}
|
|
|
+
|
|
|
+/// Test adding and retrieving melt quotes
|
|
|
+pub async fn add_and_get_melt_quote<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let melt_quote = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add melt quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ assert!(tx.add_melt_quote(melt_quote.clone()).await.is_ok());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Retrieve melt quote
|
|
|
+ let retrieved = db.get_melt_quote(&melt_quote.id).await.unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, melt_quote.id);
|
|
|
+ assert_eq!(retrieved.amount, melt_quote.amount);
|
|
|
+ assert_eq!(retrieved.fee_reserve, melt_quote.fee_reserve);
|
|
|
+}
|
|
|
+
|
|
|
+/// Test adding duplicate melt quotes fails
|
|
|
+pub async fn add_melt_quote_only_once<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let melt_quote = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add first melt quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ assert!(tx.add_melt_quote(melt_quote.clone()).await.is_ok());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Try to add duplicate - should fail
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ assert!(tx.add_melt_quote(melt_quote).await.is_err());
|
|
|
+ tx.rollback().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test updating melt quote state
|
|
|
+pub async fn update_melt_quote_state_transition<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use cashu::MeltQuoteState;
|
|
|
+
|
|
|
+ let melt_quote = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add melt quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_melt_quote(melt_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Update to Pending state
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let (old_state, updated) = tx
|
|
|
+ .update_melt_quote_state(&melt_quote.id, MeltQuoteState::Pending, None)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(old_state, MeltQuoteState::Unpaid);
|
|
|
+ assert_eq!(updated.state, MeltQuoteState::Pending);
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Update to Paid state with payment proof
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let payment_proof = "payment_proof_123".to_string();
|
|
|
+ let (old_state, updated) = tx
|
|
|
+ .update_melt_quote_state(
|
|
|
+ &melt_quote.id,
|
|
|
+ MeltQuoteState::Paid,
|
|
|
+ Some(payment_proof.clone()),
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(old_state, MeltQuoteState::Pending);
|
|
|
+ assert_eq!(updated.state, MeltQuoteState::Paid);
|
|
|
+ // The payment proof is stored in the melt quote (verification depends on implementation)
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test updating melt quote request lookup id
|
|
|
+pub async fn update_melt_quote_request_lookup_id<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let melt_quote = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ Some(PaymentIdentifier::CustomId("old_lookup_id".to_string())),
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add melt quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_melt_quote(melt_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Update request lookup id
|
|
|
+ let new_lookup_id = PaymentIdentifier::CustomId("new_lookup_id".to_string());
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.update_melt_quote_request_lookup_id(&melt_quote.id, &new_lookup_id)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Verify the update
|
|
|
+ let retrieved = db.get_melt_quote(&melt_quote.id).await.unwrap().unwrap();
|
|
|
+ assert_eq!(retrieved.request_lookup_id, Some(new_lookup_id));
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting all mint quotes
|
|
|
+pub async fn get_all_mint_quotes<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let quote1 = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ let quote2 = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 200.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quotes
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(quote1.clone()).await.unwrap();
|
|
|
+ tx.add_mint_quote(quote2.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get all quotes
|
|
|
+ let all_quotes = db.get_mint_quotes().await.unwrap();
|
|
|
+ assert!(all_quotes.len() >= 2);
|
|
|
+ assert!(all_quotes.iter().any(|q| q.id == quote1.id));
|
|
|
+ assert!(all_quotes.iter().any(|q| q.id == quote2.id));
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting all melt quotes
|
|
|
+pub async fn get_all_melt_quotes<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let quote1 = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ let quote2 = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 200.into(),
|
|
|
+ 20.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quotes
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_melt_quote(quote1.clone()).await.unwrap();
|
|
|
+ tx.add_melt_quote(quote2.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get all quotes
|
|
|
+ let all_quotes = db.get_melt_quotes().await.unwrap();
|
|
|
+ assert!(all_quotes.len() >= 2);
|
|
|
+ assert!(all_quotes.iter().any(|q| q.id == quote1.id));
|
|
|
+ assert!(all_quotes.iter().any(|q| q.id == quote2.id));
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting mint quote by request
|
|
|
+pub async fn get_mint_quote_by_request<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let request = unique_string();
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ request.clone(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get by request
|
|
|
+ let retrieved = db.get_mint_quote_by_request(&request).await.unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, mint_quote.id);
|
|
|
+ assert_eq!(retrieved.request, request);
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting mint quote by request lookup id
|
|
|
+pub async fn get_mint_quote_by_request_lookup_id<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let lookup_id = PaymentIdentifier::CustomId(unique_string());
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ lookup_id.clone(),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get by request lookup id
|
|
|
+ let retrieved = db
|
|
|
+ .get_mint_quote_by_request_lookup_id(&lookup_id)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, mint_quote.id);
|
|
|
+ assert_eq!(retrieved.request_lookup_id, lookup_id);
|
|
|
+}
|
|
|
+
|
|
|
+/// Test deleting blinded messages
|
|
|
+pub async fn delete_blinded_messages<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let keyset_id = Id::from_str("001711afb1de20cb").unwrap();
|
|
|
+
|
|
|
+ // Create blinded messages
|
|
|
+ let blinded_secret1 = SecretKey::generate().public_key();
|
|
|
+ let blinded_secret2 = SecretKey::generate().public_key();
|
|
|
+
|
|
|
+ let blinded_message1 = cashu::BlindedMessage {
|
|
|
+ blinded_secret: blinded_secret1,
|
|
|
+ keyset_id,
|
|
|
+ amount: Amount::from(100u64),
|
|
|
+ witness: None,
|
|
|
+ };
|
|
|
+
|
|
|
+ let blinded_message2 = cashu::BlindedMessage {
|
|
|
+ blinded_secret: blinded_secret2,
|
|
|
+ keyset_id,
|
|
|
+ amount: Amount::from(200u64),
|
|
|
+ witness: None,
|
|
|
+ };
|
|
|
+
|
|
|
+ let blinded_messages = vec![blinded_message1.clone(), blinded_message2.clone()];
|
|
|
+
|
|
|
+ // Add blinded messages
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_blinded_messages(None, &blinded_messages, &Operation::new_mint())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Delete one blinded message
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.delete_blinded_messages(&[blinded_secret1])
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Try to add same blinded messages again - first should succeed, second should fail
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ assert!(tx
|
|
|
+ .add_blinded_messages(None, &[blinded_message1], &Operation::new_mint())
|
|
|
+ .await
|
|
|
+ .is_ok());
|
|
|
+ assert!(tx
|
|
|
+ .add_blinded_messages(None, &[blinded_message2], &Operation::new_mint())
|
|
|
+ .await
|
|
|
+ .is_err());
|
|
|
+ tx.rollback().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test incrementing mint quote amount paid
|
|
|
+pub async fn increment_mint_quote_amount_paid<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 1000.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Increment amount paid first time
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_paid(&mint_quote.id, 300.into(), "payment_1".to_string())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 300.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Increment amount paid second time
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_paid(&mint_quote.id, 200.into(), "payment_2".to_string())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 500.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Verify final state
|
|
|
+ let retrieved = db.get_mint_quote(&mint_quote.id).await.unwrap().unwrap();
|
|
|
+ assert_eq!(retrieved.amount_paid(), 500.into());
|
|
|
+}
|
|
|
+
|
|
|
+/// Test incrementing mint quote amount issued
|
|
|
+pub async fn increment_mint_quote_amount_issued<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 1000.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // First increment amount_paid to allow issuing
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.increment_mint_quote_amount_paid(&mint_quote.id, 1000.into(), "payment_1".to_string())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Increment amount issued first time
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_issued(&mint_quote.id, 400.into())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 400.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Increment amount issued second time
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_issued(&mint_quote.id, 300.into())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 700.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Verify final state
|
|
|
+ let retrieved = db.get_mint_quote(&mint_quote.id).await.unwrap().unwrap();
|
|
|
+ assert_eq!(retrieved.amount_issued(), 700.into());
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting mint quote within transaction (with lock)
|
|
|
+pub async fn get_mint_quote_in_transaction<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "test_request".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get quote within transaction
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let retrieved = tx.get_mint_quote(&mint_quote.id).await.unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, mint_quote.id);
|
|
|
+ assert_eq!(retrieved.request, "test_request");
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting melt quote within transaction (with lock)
|
|
|
+pub async fn get_melt_quote_in_transaction<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ let melt_quote = MeltQuote::new(
|
|
|
+ MeltPaymentRequest::Bolt11 {
|
|
|
+ bolt11: "lnbc330n1p5d85skpp5344v3ktclujsjl3h09wgsfm7zytumr7h7zhrl857f5w8nv0a52zqdqqcqzzsxqyz5vqrzjqvueefmrckfdwyyu39m0lf24sqzcr9vcrmxrvgfn6empxz7phrjxvrttncqq0lcqqyqqqqlgqqqqqqgq2qsp5j3rrg8kvpemqxtf86j8tjm90wq77c7ende4e5qmrerq4xsg02vhq9qxpqysgqjltywgyk6uc5qcgwh8xnzmawl2tjlhz8d28tgp3yx8xwtz76x0jqkfh6mmq70hervjxs0keun7ur0spldgll29l0dnz3md50d65sfqqqwrwpsu".parse().unwrap()
|
|
|
+ },
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ 100.into(),
|
|
|
+ 10.into(),
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_melt_quote(melt_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get quote within transaction
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let retrieved = tx.get_melt_quote(&melt_quote.id).await.unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, melt_quote.id);
|
|
|
+ assert_eq!(retrieved.amount, melt_quote.amount);
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test get mint quote by request within transaction
|
|
|
+pub async fn get_mint_quote_by_request_in_transaction<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let request = unique_string();
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ request.clone(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get by request within transaction
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let retrieved = tx.get_mint_quote_by_request(&request).await.unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, mint_quote.id);
|
|
|
+ assert_eq!(retrieved.request, request);
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test get mint quote by request lookup id within transaction
|
|
|
+pub async fn get_mint_quote_by_request_lookup_id_in_transaction<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let lookup_id = PaymentIdentifier::CustomId(unique_string());
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ lookup_id.clone(),
|
|
|
+ None,
|
|
|
+ 100.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get by request lookup id within transaction
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let retrieved = tx
|
|
|
+ .get_mint_quote_by_request_lookup_id(&lookup_id)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert!(retrieved.is_some());
|
|
|
+ let retrieved = retrieved.unwrap();
|
|
|
+ assert_eq!(retrieved.id, mint_quote.id);
|
|
|
+ assert_eq!(retrieved.request_lookup_id, lookup_id);
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test getting blind signatures within transaction
|
|
|
+pub async fn get_blind_signatures_in_transaction<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use std::str::FromStr;
|
|
|
+
|
|
|
+ let keyset_id = Id::from_str("001711afb1de20cb").unwrap();
|
|
|
+ let blinded_message = SecretKey::generate().public_key();
|
|
|
+
|
|
|
+ let sig = BlindSignature {
|
|
|
+ amount: Amount::from(100u64),
|
|
|
+ keyset_id,
|
|
|
+ c: SecretKey::generate().public_key(),
|
|
|
+ dleq: None,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add blind signature
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_blind_signatures(&[blinded_message], std::slice::from_ref(&sig), None)
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Get blind signature within transaction
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let retrieved = tx.get_blind_signatures(&[blinded_message]).await.unwrap();
|
|
|
+ assert_eq!(retrieved.len(), 1);
|
|
|
+ assert!(retrieved[0].is_some());
|
|
|
+ let retrieved_sig = retrieved[0].as_ref().unwrap();
|
|
|
+ assert_eq!(retrieved_sig.amount, sig.amount);
|
|
|
+ assert_eq!(retrieved_sig.c, sig.c);
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+/// Test that duplicate payment IDs are rejected
|
|
|
+pub async fn reject_duplicate_payment_ids<DB>(db: DB)
|
|
|
+where
|
|
|
+ DB: Database<Error> + KeysDatabase<Err = Error>,
|
|
|
+{
|
|
|
+ use crate::database::mint::test::unique_string;
|
|
|
+
|
|
|
+ let mint_quote = MintQuote::new(
|
|
|
+ None,
|
|
|
+ "".to_owned(),
|
|
|
+ cashu::CurrencyUnit::Sat,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ PaymentIdentifier::CustomId(unique_string()),
|
|
|
+ None,
|
|
|
+ 1000.into(),
|
|
|
+ 0.into(),
|
|
|
+ cashu::PaymentMethod::Bolt11,
|
|
|
+ 0,
|
|
|
+ vec![],
|
|
|
+ vec![],
|
|
|
+ );
|
|
|
+
|
|
|
+ // Add quote
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ tx.add_mint_quote(mint_quote.clone()).await.unwrap();
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // First payment with payment_id "payment_1"
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_paid(&mint_quote.id, 300.into(), "payment_1".to_string())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 300.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Try to add the same payment_id again - should fail with Duplicate error
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let result = tx
|
|
|
+ .increment_mint_quote_amount_paid(&mint_quote.id, 300.into(), "payment_1".to_string())
|
|
|
+ .await;
|
|
|
+
|
|
|
+ assert!(
|
|
|
+ matches!(result.unwrap_err(), Error::Duplicate),
|
|
|
+ "Duplicate payment_id should be rejected"
|
|
|
+ );
|
|
|
+ tx.rollback().await.unwrap();
|
|
|
+
|
|
|
+ // Verify that the amount_paid is still 300 (not 600)
|
|
|
+ let retrieved = db.get_mint_quote(&mint_quote.id).await.unwrap().unwrap();
|
|
|
+ assert_eq!(retrieved.amount_paid(), 300.into());
|
|
|
+
|
|
|
+ // A different payment_id should succeed
|
|
|
+ let mut tx = Database::begin_transaction(&db).await.unwrap();
|
|
|
+ let new_total = tx
|
|
|
+ .increment_mint_quote_amount_paid(&mint_quote.id, 200.into(), "payment_2".to_string())
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert_eq!(new_total, 500.into());
|
|
|
+ tx.commit().await.unwrap();
|
|
|
+
|
|
|
+ // Verify final state
|
|
|
+ let retrieved = db.get_mint_quote(&mint_quote.id).await.unwrap().unwrap();
|
|
|
+ assert_eq!(retrieved.amount_paid(), 500.into());
|
|
|
+}
|