Parcourir la source

Fixed error with wrong placeholder (#1069)

* Fixed error with wrong placeholder

Add concept of schema, so each test is isolated
C il y a 1 mois
Parent
commit
0bf5325927

+ 7 - 1
crates/cdk-common/src/database/mint/test/mod.rs

@@ -229,6 +229,7 @@ macro_rules! mint_db_test {
             add_mint_quote_only_once,
             register_payments,
             read_mint_from_db_and_tx,
+            get_proofs_by_keyset_id,
             reject_duplicate_payments_same_tx,
             reject_duplicate_payments_diff_tx,
             reject_over_issue_same_tx,
@@ -241,7 +242,12 @@ macro_rules! mint_db_test {
         $(
             #[tokio::test]
             async fn $name() {
-                cdk_common::database::mint::test::$name($make_db_fn().await).await;
+                use std::time::{SystemTime, UNIX_EPOCH};
+                let now = SystemTime::now()
+                    .duration_since(UNIX_EPOCH)
+                    .expect("Time went backwards");
+
+                cdk_common::database::mint::test::$name($make_db_fn(format!("test_{}_{}", now.as_nanos(), stringify!($name))).await).await;
             }
         )+
     };

+ 51 - 1
crates/cdk-common/src/database/mint/test/proofs.rs

@@ -1,11 +1,61 @@
 //! Proofs tests
 
+use std::str::FromStr;
+
 use cashu::secret::Secret;
-use cashu::{Amount, SecretKey};
+use cashu::{Amount, Id, SecretKey};
 
 use crate::database::mint::test::setup_keyset;
 use crate::database::mint::{Database, Error, KeysDatabase, Proof, QuoteId};
 
+/// Test get proofs by keyset id
+pub async fn get_proofs_by_keyset_id<DB>(db: DB)
+where
+    DB: Database<Error> + KeysDatabase<Err = Error>,
+{
+    let keyset_id = setup_keyset(&db).await;
+    let quote_id = QuoteId::new_uuid();
+    let proofs = vec![
+        Proof {
+            amount: Amount::from(100),
+            keyset_id,
+            secret: Secret::generate(),
+            c: SecretKey::generate().public_key(),
+            witness: None,
+            dleq: None,
+        },
+        Proof {
+            amount: Amount::from(200),
+            keyset_id,
+            secret: Secret::generate(),
+            c: SecretKey::generate().public_key(),
+            witness: None,
+            dleq: None,
+        },
+    ];
+
+    // Add proofs to database
+    let mut tx = Database::begin_transaction(&db).await.unwrap();
+    tx.add_proofs(proofs, Some(quote_id)).await.unwrap();
+    assert!(tx.commit().await.is_ok());
+
+    let (proofs, states) = db.get_proofs_by_keyset_id(&keyset_id).await.unwrap();
+    assert_eq!(proofs.len(), 2);
+    assert_eq!(proofs.len(), states.len());
+    assert_eq!(
+        states
+            .into_iter()
+            .map(|s| s.map(|x| x.to_string()).unwrap_or_default())
+            .collect::<Vec<_>>(),
+        vec!["UNSPENT".to_owned(), "UNSPENT".to_owned()]
+    );
+
+    let keyset_id = Id::from_str("00916bbf7ef91a34").unwrap();
+    let (proofs, states) = db.get_proofs_by_keyset_id(&keyset_id).await.unwrap();
+    assert_eq!(proofs.len(), 0);
+    assert_eq!(proofs.len(), states.len());
+}
+
 /// Test the basic storing and retrieving proofs from the database. Probably the database would use
 /// binary/`Vec<u8>` to store data, that's why this test would quickly identify issues before running
 /// other tests

+ 60 - 9
crates/cdk-postgres/src/lib.rs

@@ -54,6 +54,7 @@ impl Debug for SslMode {
 #[derive(Clone, Debug)]
 pub struct PgConfig {
     url: String,
+    schema: Option<String>,
     tls: SslMode,
 }
 
@@ -67,8 +68,29 @@ impl DatabaseConfig for PgConfig {
     }
 }
 
+impl PgConfig {
+    /// strip schema from the connection string
+    fn strip_schema(input: &str) -> (Option<String>, String) {
+        let mut schema: Option<String> = None;
+
+        // Split by whitespace
+        let mut parts = Vec::new();
+        for token in input.split_whitespace() {
+            if let Some(rest) = token.strip_prefix("schema=") {
+                schema = Some(rest.to_string());
+            } else {
+                parts.push(token);
+            }
+        }
+
+        let cleaned = parts.join(" ");
+        (schema, cleaned)
+    }
+}
+
 impl From<&str> for PgConfig {
     fn from(conn_str: &str) -> Self {
+        let (schema, conn_str) = Self::strip_schema(conn_str);
         fn build_tls(accept_invalid_certs: bool, accept_invalid_hostnames: bool) -> SslMode {
             let mut builder = TlsConnector::builder();
             if accept_invalid_certs {
@@ -105,6 +127,7 @@ impl From<&str> for PgConfig {
 
         PgConfig {
             url: conn_str.to_owned(),
+            schema,
             tls,
         }
     }
@@ -149,6 +172,17 @@ impl PostgresConnection {
         let result_clone = result.clone();
         let notify_clone = notify.clone();
 
+        async fn select_schema(conn: &Client, schema: &str) -> Result<(), Error> {
+            conn.batch_execute(&format!(
+                r#"
+                    CREATE SCHEMA IF NOT EXISTS "{schema}";
+                    SET search_path TO "{schema}"
+                    "#
+            ))
+            .await
+            .map_err(|e| Error::Database(Box::new(e)))
+        }
+
         tokio::spawn(async move {
             match config.tls {
                 SslMode::NoTls(tls) => {
@@ -163,11 +197,21 @@ impl PostgresConnection {
                         }
                     };
 
+                    let still_valid_for_spawn = still_valid.clone();
                     tokio::spawn(async move {
                         let _ = connection.await;
-                        still_valid.store(false, std::sync::atomic::Ordering::Release);
+                        still_valid_for_spawn.store(false, std::sync::atomic::Ordering::Release);
                     });
 
+                    if let Some(schema) = config.schema.as_ref() {
+                        if let Err(err) = select_schema(&client, schema).await {
+                            *error_clone.lock().await = Some(err);
+                            still_valid.store(false, std::sync::atomic::Ordering::Release);
+                            notify_clone.notify_waiters();
+                            return;
+                        }
+                    }
+
                     let _ = result_clone.set(client);
                     notify_clone.notify_waiters();
                 }
@@ -183,11 +227,21 @@ impl PostgresConnection {
                         }
                     };
 
+                    let still_valid_for_spawn = still_valid.clone();
                     tokio::spawn(async move {
                         let _ = connection.await;
-                        still_valid.store(false, std::sync::atomic::Ordering::Release);
+                        still_valid_for_spawn.store(false, std::sync::atomic::Ordering::Release);
                     });
 
+                    if let Some(schema) = config.schema.as_ref() {
+                        if let Err(err) = select_schema(&client, schema).await {
+                            *error_clone.lock().await = Some(err);
+                            still_valid.store(false, std::sync::atomic::Ordering::Release);
+                            notify_clone.notify_waiters();
+                            return;
+                        }
+                    }
+
                     let _ = result_clone.set(client);
                     notify_clone.notify_waiters();
                 }
@@ -275,22 +329,19 @@ pub type WalletPgDatabase = SQLWalletDatabase<PgConnectionPool>;
 #[cfg(test)]
 mod test {
     use cdk_common::mint_db_test;
-    use once_cell::sync::Lazy;
-    use tokio::sync::Mutex;
 
     use super::*;
 
-    static MIGRATION_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
-
-    async fn provide_db() -> MintPgDatabase {
-        let m = MIGRATION_LOCK.lock().await;
+    async fn provide_db(test_id: String) -> MintPgDatabase {
         let db_url = std::env::var("CDK_MINTD_DATABASE_URL")
             .or_else(|_| std::env::var("PG_DB_URL")) // Fallback for compatibility
             .unwrap_or("host=localhost user=test password=test dbname=testdb port=5433".to_owned());
+
+        let db_url = format!("{db_url} schema={test_id}");
+
         let db = MintPgDatabase::new(db_url.as_str())
             .await
             .expect("database");
-        drop(m);
         db
     }
 

+ 2 - 2
crates/cdk-sql-common/src/mint/mod.rs

@@ -923,7 +923,7 @@ VALUES (:quote_id, :amount, :timestamp);
         query(
             r#"
             DELETE FROM melt_quote
-            WHERE id=?
+            WHERE id=:id
             "#,
         )?
         .bind("id", quote_id.to_string())
@@ -1452,7 +1452,7 @@ where
             FROM
                 proof
             WHERE
-                keyset_id=?
+                keyset_id=:keyset_id
             "#,
         )?
         .bind("keyset_id", keyset_id.to_string())

+ 1 - 1
crates/cdk-sqlite/src/mint/mod.rs

@@ -25,7 +25,7 @@ mod test {
     use super::*;
     use crate::common::Config;
 
-    async fn provide_db() -> MintSqliteDatabase {
+    async fn provide_db(_test_name: String) -> MintSqliteDatabase {
         memory::empty().await.unwrap()
     }