breez_sdk_spark/persist/
mod.rs

1pub(crate) mod path;
2#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
3pub(crate) mod sqlite;
4
5use std::{collections::HashMap, sync::Arc};
6
7use macros::async_trait;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::{
12    ConversionInfo, DepositClaimError, DepositInfo, LightningAddressInfo, ListPaymentsRequest,
13    LnurlPayInfo, LnurlWithdrawInfo, TokenBalance, TokenMetadata, models::Payment,
14};
15
16const ACCOUNT_INFO_KEY: &str = "account_info";
17const LIGHTNING_ADDRESS_KEY: &str = "lightning_address";
18const LNURL_METADATA_UPDATED_AFTER_KEY: &str = "lnurl_metadata_updated_after";
19const SYNC_OFFSET_KEY: &str = "sync_offset";
20const TX_CACHE_KEY: &str = "tx_cache";
21const STATIC_DEPOSIT_ADDRESS_CACHE_KEY: &str = "static_deposit_address";
22const TOKEN_METADATA_KEY_PREFIX: &str = "token_metadata_";
23const PAYMENT_METADATA_KEY_PREFIX: &str = "payment_metadata";
24const SPARK_PRIVATE_MODE_INITIALIZED_KEY: &str = "spark_private_mode_initialized";
25
26#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
27pub enum UpdateDepositPayload {
28    ClaimError {
29        error: DepositClaimError,
30    },
31    Refund {
32        refund_txid: String,
33        refund_tx: String,
34    },
35}
36
37#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
38pub struct SetLnurlMetadataItem {
39    pub payment_hash: String,
40    pub sender_comment: Option<String>,
41    pub nostr_zap_request: Option<String>,
42    pub nostr_zap_receipt: Option<String>,
43}
44
45impl From<lnurl_models::ListMetadataMetadata> for SetLnurlMetadataItem {
46    fn from(value: lnurl_models::ListMetadataMetadata) -> Self {
47        SetLnurlMetadataItem {
48            payment_hash: value.payment_hash,
49            sender_comment: value.sender_comment,
50            nostr_zap_request: value.nostr_zap_request,
51            nostr_zap_receipt: value.nostr_zap_receipt,
52        }
53    }
54}
55
56/// Errors that can occur during storage operations
57#[derive(Debug, Error, Clone)]
58#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
59pub enum StorageError {
60    #[error("Underline implementation error: {0}")]
61    Implementation(String),
62
63    /// Database initialization error
64    #[error("Failed to initialize database: {0}")]
65    InitializationError(String),
66
67    #[error("Failed to serialize/deserialize data: {0}")]
68    Serialization(String),
69}
70
71impl From<serde_json::Error> for StorageError {
72    fn from(e: serde_json::Error) -> Self {
73        StorageError::Serialization(e.to_string())
74    }
75}
76
77/// Metadata associated with a payment that cannot be extracted from the Spark operator.
78#[derive(Clone, Default, Deserialize, Serialize)]
79#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
80pub struct PaymentMetadata {
81    pub parent_payment_id: Option<String>,
82    pub lnurl_pay_info: Option<LnurlPayInfo>,
83    pub lnurl_withdraw_info: Option<LnurlWithdrawInfo>,
84    pub lnurl_description: Option<String>,
85    pub conversion_info: Option<ConversionInfo>,
86}
87
88/// Trait for persistent storage
89#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
90#[async_trait]
91pub trait Storage: Send + Sync {
92    async fn delete_cached_item(&self, key: String) -> Result<(), StorageError>;
93    async fn get_cached_item(&self, key: String) -> Result<Option<String>, StorageError>;
94    async fn set_cached_item(&self, key: String, value: String) -> Result<(), StorageError>;
95    /// Lists payments with optional filters and pagination
96    ///
97    /// # Arguments
98    ///
99    /// * `list_payments_request` - The request to list payments
100    ///
101    /// # Returns
102    ///
103    /// A vector of payments or a `StorageError`
104    async fn list_payments(
105        &self,
106        request: ListPaymentsRequest,
107    ) -> Result<Vec<Payment>, StorageError>;
108
109    /// Inserts a payment into storage
110    ///
111    /// # Arguments
112    ///
113    /// * `payment` - The payment to insert
114    ///
115    /// # Returns
116    ///
117    /// Success or a `StorageError`
118    async fn insert_payment(&self, payment: Payment) -> Result<(), StorageError>;
119
120    /// Inserts payment metadata into storage
121    ///
122    /// # Arguments
123    ///
124    /// * `payment_id` - The ID of the payment
125    /// * `metadata` - The metadata to insert
126    ///
127    /// # Returns
128    ///
129    /// Success or a `StorageError`
130    async fn set_payment_metadata(
131        &self,
132        payment_id: String,
133        metadata: PaymentMetadata,
134    ) -> Result<(), StorageError>;
135
136    /// Gets a payment by its ID
137    /// # Arguments
138    ///
139    /// * `id` - The ID of the payment to retrieve
140    ///
141    /// # Returns
142    ///
143    /// The payment if found or None if not found
144    async fn get_payment_by_id(&self, id: String) -> Result<Payment, StorageError>;
145
146    /// Gets a payment by its invoice
147    /// # Arguments
148    ///
149    /// * `invoice` - The invoice of the payment to retrieve
150    /// # Returns
151    ///
152    /// The payment if found or None if not found
153    async fn get_payment_by_invoice(
154        &self,
155        invoice: String,
156    ) -> Result<Option<Payment>, StorageError>;
157
158    /// Add a deposit to storage
159    /// # Arguments
160    ///
161    /// * `txid` - The transaction ID of the deposit
162    /// * `vout` - The output index of the deposit
163    /// * `amount_sats` - The amount of the deposit in sats
164    ///
165    /// # Returns
166    ///
167    /// Success or a `StorageError`
168    async fn add_deposit(
169        &self,
170        txid: String,
171        vout: u32,
172        amount_sats: u64,
173    ) -> Result<(), StorageError>;
174
175    /// Removes an unclaimed deposit from storage
176    /// # Arguments
177    ///
178    /// * `txid` - The transaction ID of the deposit
179    /// * `vout` - The output index of the deposit
180    ///
181    /// # Returns
182    ///
183    /// Success or a `StorageError`
184    async fn delete_deposit(&self, txid: String, vout: u32) -> Result<(), StorageError>;
185
186    /// Lists all unclaimed deposits from storage
187    /// # Returns
188    ///
189    /// A vector of `DepositInfo` or a `StorageError`
190    async fn list_deposits(&self) -> Result<Vec<DepositInfo>, StorageError>;
191
192    /// Updates or inserts unclaimed deposit details
193    /// # Arguments
194    ///
195    /// * `txid` - The transaction ID of the deposit
196    /// * `vout` - The output index of the deposit
197    /// * `payload` - The payload for the update
198    ///
199    /// # Returns
200    ///
201    /// Success or a `StorageError`
202    async fn update_deposit(
203        &self,
204        txid: String,
205        vout: u32,
206        payload: UpdateDepositPayload,
207    ) -> Result<(), StorageError>;
208
209    async fn set_lnurl_metadata(
210        &self,
211        metadata: Vec<SetLnurlMetadataItem>,
212    ) -> Result<(), StorageError>;
213}
214
215pub(crate) struct ObjectCacheRepository {
216    storage: Arc<dyn Storage>,
217}
218
219impl ObjectCacheRepository {
220    pub(crate) fn new(storage: Arc<dyn Storage>) -> Self {
221        ObjectCacheRepository { storage }
222    }
223
224    pub(crate) async fn save_account_info(
225        &self,
226        value: &CachedAccountInfo,
227    ) -> Result<(), StorageError> {
228        self.storage
229            .set_cached_item(ACCOUNT_INFO_KEY.to_string(), serde_json::to_string(value)?)
230            .await?;
231        Ok(())
232    }
233
234    pub(crate) async fn fetch_account_info(
235        &self,
236    ) -> Result<Option<CachedAccountInfo>, StorageError> {
237        let value = self
238            .storage
239            .get_cached_item(ACCOUNT_INFO_KEY.to_string())
240            .await?;
241        match value {
242            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
243            None => Ok(None),
244        }
245    }
246
247    pub(crate) async fn save_sync_info(&self, value: &CachedSyncInfo) -> Result<(), StorageError> {
248        self.storage
249            .set_cached_item(SYNC_OFFSET_KEY.to_string(), serde_json::to_string(value)?)
250            .await?;
251        Ok(())
252    }
253
254    pub(crate) async fn fetch_sync_info(&self) -> Result<Option<CachedSyncInfo>, StorageError> {
255        let value = self
256            .storage
257            .get_cached_item(SYNC_OFFSET_KEY.to_string())
258            .await?;
259        match value {
260            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
261            None => Ok(None),
262        }
263    }
264
265    pub(crate) async fn save_tx(&self, txid: &str, value: &CachedTx) -> Result<(), StorageError> {
266        self.storage
267            .set_cached_item(
268                format!("{TX_CACHE_KEY}-{txid}"),
269                serde_json::to_string(value)?,
270            )
271            .await?;
272        Ok(())
273    }
274
275    pub(crate) async fn fetch_tx(&self, txid: &str) -> Result<Option<CachedTx>, StorageError> {
276        let value = self
277            .storage
278            .get_cached_item(format!("{TX_CACHE_KEY}-{txid}"))
279            .await?;
280        match value {
281            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
282            None => Ok(None),
283        }
284    }
285
286    pub(crate) async fn save_static_deposit_address(
287        &self,
288        value: &StaticDepositAddress,
289    ) -> Result<(), StorageError> {
290        self.storage
291            .set_cached_item(
292                STATIC_DEPOSIT_ADDRESS_CACHE_KEY.to_string(),
293                serde_json::to_string(value)?,
294            )
295            .await?;
296        Ok(())
297    }
298
299    pub(crate) async fn fetch_static_deposit_address(
300        &self,
301    ) -> Result<Option<StaticDepositAddress>, StorageError> {
302        let value = self
303            .storage
304            .get_cached_item(STATIC_DEPOSIT_ADDRESS_CACHE_KEY.to_string())
305            .await?;
306        match value {
307            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
308            None => Ok(None),
309        }
310    }
311
312    pub(crate) async fn save_lightning_address(
313        &self,
314        value: &LightningAddressInfo,
315    ) -> Result<(), StorageError> {
316        self.storage
317            .set_cached_item(
318                LIGHTNING_ADDRESS_KEY.to_string(),
319                serde_json::to_string(value)?,
320            )
321            .await?;
322        Ok(())
323    }
324
325    pub(crate) async fn delete_lightning_address(&self) -> Result<(), StorageError> {
326        self.storage
327            .delete_cached_item(LIGHTNING_ADDRESS_KEY.to_string())
328            .await?;
329        Ok(())
330    }
331
332    pub(crate) async fn fetch_lightning_address(
333        &self,
334    ) -> Result<Option<LightningAddressInfo>, StorageError> {
335        let value = self
336            .storage
337            .get_cached_item(LIGHTNING_ADDRESS_KEY.to_string())
338            .await?;
339        match value {
340            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
341            None => Ok(None),
342        }
343    }
344
345    pub(crate) async fn save_token_metadata(
346        &self,
347        value: &TokenMetadata,
348    ) -> Result<(), StorageError> {
349        self.storage
350            .set_cached_item(
351                format!("{TOKEN_METADATA_KEY_PREFIX}{}", value.identifier),
352                serde_json::to_string(value)?,
353            )
354            .await?;
355        Ok(())
356    }
357
358    pub(crate) async fn fetch_token_metadata(
359        &self,
360        identifier: &str,
361    ) -> Result<Option<TokenMetadata>, StorageError> {
362        let value = self
363            .storage
364            .get_cached_item(format!("{TOKEN_METADATA_KEY_PREFIX}{identifier}"))
365            .await?;
366        match value {
367            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
368            None => Ok(None),
369        }
370    }
371
372    pub(crate) async fn save_payment_metadata(
373        &self,
374        identifier: &str,
375        value: &PaymentMetadata,
376    ) -> Result<(), StorageError> {
377        self.storage
378            .set_cached_item(
379                format!("{PAYMENT_METADATA_KEY_PREFIX}-{identifier}"),
380                serde_json::to_string(value)?,
381            )
382            .await?;
383        Ok(())
384    }
385
386    pub(crate) async fn fetch_payment_metadata(
387        &self,
388        identifier: &str,
389    ) -> Result<Option<PaymentMetadata>, StorageError> {
390        let value = self
391            .storage
392            .get_cached_item(format!("{PAYMENT_METADATA_KEY_PREFIX}-{identifier}",))
393            .await?;
394        match value {
395            Some(value) => Ok(Some(serde_json::from_str(&value)?)),
396            None => Ok(None),
397        }
398    }
399
400    pub(crate) async fn delete_payment_metadata(
401        &self,
402        identifier: &str,
403    ) -> Result<(), StorageError> {
404        self.storage
405            .delete_cached_item(format!("{PAYMENT_METADATA_KEY_PREFIX}-{identifier}",))
406            .await?;
407        Ok(())
408    }
409
410    pub(crate) async fn save_spark_private_mode_initialized(&self) -> Result<(), StorageError> {
411        self.storage
412            .set_cached_item(
413                SPARK_PRIVATE_MODE_INITIALIZED_KEY.to_string(),
414                "true".to_string(),
415            )
416            .await?;
417        Ok(())
418    }
419
420    pub(crate) async fn fetch_spark_private_mode_initialized(&self) -> Result<bool, StorageError> {
421        let value = self
422            .storage
423            .get_cached_item(SPARK_PRIVATE_MODE_INITIALIZED_KEY.to_string())
424            .await?;
425        match value {
426            Some(value) => Ok(value == "true"),
427            None => Ok(false),
428        }
429    }
430
431    pub(crate) async fn save_lnurl_metadata_updated_after(
432        &self,
433        offset: i64,
434    ) -> Result<(), StorageError> {
435        self.storage
436            .set_cached_item(
437                LNURL_METADATA_UPDATED_AFTER_KEY.to_string(),
438                offset.to_string(),
439            )
440            .await?;
441        Ok(())
442    }
443
444    pub(crate) async fn fetch_lnurl_metadata_updated_after(&self) -> Result<i64, StorageError> {
445        let value = self
446            .storage
447            .get_cached_item(LNURL_METADATA_UPDATED_AFTER_KEY.to_string())
448            .await?;
449        match value {
450            Some(value) => Ok(value.parse().map_err(|_| {
451                StorageError::Serialization("invalid lnurl_metadata_updated_after".to_string())
452            })?),
453            None => Ok(0),
454        }
455    }
456}
457
458#[derive(Serialize, Deserialize, Default)]
459pub(crate) struct CachedAccountInfo {
460    pub(crate) balance_sats: u64,
461    #[serde(default)]
462    pub(crate) token_balances: HashMap<String, TokenBalance>,
463}
464
465#[derive(Serialize, Deserialize, Default)]
466pub(crate) struct CachedSyncInfo {
467    pub(crate) offset: u64,
468    pub(crate) last_synced_final_token_payment_id: Option<String>,
469}
470
471#[derive(Serialize, Deserialize, Default)]
472pub(crate) struct CachedTx {
473    pub(crate) raw_tx: String,
474}
475
476#[derive(Serialize, Deserialize, Default)]
477pub(crate) struct StaticDepositAddress {
478    pub(crate) address: String,
479}
480
481#[cfg(feature = "test-utils")]
482pub mod tests {
483    use chrono::Utc;
484
485    use crate::{
486        DepositClaimError, ListPaymentsRequest, LnurlWithdrawInfo, Payment, PaymentDetails,
487        PaymentMetadata, PaymentMethod, PaymentStatus, PaymentType, SparkHtlcDetails,
488        SparkHtlcStatus, Storage, UpdateDepositPayload,
489        persist::ObjectCacheRepository,
490        sync_storage::{Record, RecordId, SyncStorage, UnversionedRecordChange},
491    };
492
493    #[allow(clippy::too_many_lines)]
494    pub async fn test_sqlite_sync_storage(storage: Box<dyn SyncStorage>) {
495        use std::collections::HashMap;
496
497        // Test 1: Initial state - get_last_revision should return 0
498        let last_revision = storage.get_last_revision().await.unwrap();
499        assert_eq!(last_revision, 0, "Initial last revision should be 0");
500
501        // Test 2: No pending outgoing changes initially
502        let pending = storage.get_pending_outgoing_changes(10).await.unwrap();
503        assert_eq!(pending.len(), 0, "Should have no pending outgoing changes");
504
505        // Test 3: No incoming records initially
506        let incoming = storage.get_incoming_records(10).await.unwrap();
507        assert_eq!(incoming.len(), 0, "Should have no incoming records");
508
509        // Test 4: No latest outgoing change initially
510        let latest = storage.get_latest_outgoing_change().await.unwrap();
511        assert!(latest.is_none(), "Should have no latest outgoing change");
512
513        // Test 5: Add outgoing change (create new record)
514        let mut updated_fields = HashMap::new();
515        updated_fields.insert("name".to_string(), "\"Alice\"".to_string());
516        updated_fields.insert("age".to_string(), "30".to_string());
517
518        let change1 = UnversionedRecordChange {
519            id: RecordId::new("user".to_string(), "user1".to_string()),
520            schema_version: "1.0.0".to_string(),
521            updated_fields: updated_fields.clone(),
522        };
523
524        let revision1 = storage.add_outgoing_change(change1).await.unwrap();
525        assert!(revision1 > 0, "First revision should be greater than 0");
526
527        // Test 6: Check pending outgoing changes
528        let pending = storage.get_pending_outgoing_changes(10).await.unwrap();
529        assert_eq!(pending.len(), 1, "Should have 1 pending outgoing change");
530        assert_eq!(pending[0].change.id.r#type, "user");
531        assert_eq!(pending[0].change.id.data_id, "user1");
532        assert_eq!(pending[0].change.revision, revision1);
533        assert_eq!(pending[0].change.schema_version, "1.0.0");
534        assert!(
535            pending[0].parent.is_none(),
536            "First change should have no parent"
537        );
538
539        // Test 7: Get latest outgoing change
540        let latest = storage.get_latest_outgoing_change().await.unwrap();
541        assert!(latest.is_some());
542        let latest = latest.unwrap();
543        assert_eq!(latest.change.id.r#type, "user");
544        assert_eq!(latest.change.revision, revision1);
545
546        // Test 8: Complete outgoing sync (moves to sync_state)
547        let mut complete_data = HashMap::new();
548        complete_data.insert("name".to_string(), "\"Alice\"".to_string());
549        complete_data.insert("age".to_string(), "30".to_string());
550
551        let completed_record = Record {
552            id: RecordId::new("user".to_string(), "user1".to_string()),
553            revision: revision1,
554            schema_version: "1.0.0".to_string(),
555            data: complete_data,
556        };
557
558        storage
559            .complete_outgoing_sync(completed_record.clone())
560            .await
561            .unwrap();
562
563        // Test 9: Pending changes should now be empty
564        let pending = storage.get_pending_outgoing_changes(10).await.unwrap();
565        assert_eq!(
566            pending.len(),
567            0,
568            "Should have no pending changes after completion"
569        );
570
571        // Test 10: Last revision should be updated
572        let last_revision = storage.get_last_revision().await.unwrap();
573        assert_eq!(
574            last_revision, revision1,
575            "Last revision should match completed revision"
576        );
577
578        // Test 11: Add another outgoing change (update existing record)
579        let mut updated_fields2 = HashMap::new();
580        updated_fields2.insert("age".to_string(), "31".to_string());
581
582        let change2 = UnversionedRecordChange {
583            id: RecordId::new("user".to_string(), "user1".to_string()),
584            schema_version: "1.0.0".to_string(),
585            updated_fields: updated_fields2,
586        };
587
588        let revision2 = storage.add_outgoing_change(change2).await.unwrap();
589        assert!(
590            revision2 > revision1,
591            "Second revision should be greater than first"
592        );
593
594        // Test 12: Check pending changes now includes parent
595        let pending = storage.get_pending_outgoing_changes(10).await.unwrap();
596        assert_eq!(pending.len(), 1, "Should have 1 pending change");
597        assert!(
598            pending[0].parent.is_some(),
599            "Update should have parent record"
600        );
601        let parent = pending[0].parent.as_ref().unwrap();
602        assert_eq!(parent.revision, revision1);
603        assert_eq!(parent.id.r#type, "user");
604
605        // Test 13: Insert incoming records
606        let mut incoming_data1 = HashMap::new();
607        incoming_data1.insert("title".to_string(), "\"Post 1\"".to_string());
608        incoming_data1.insert("content".to_string(), "\"Hello World\"".to_string());
609
610        let incoming_record1 = Record {
611            id: RecordId::new("post".to_string(), "post1".to_string()),
612            revision: 100,
613            schema_version: "1.0.0".to_string(),
614            data: incoming_data1,
615        };
616
617        let mut incoming_data2 = HashMap::new();
618        incoming_data2.insert("title".to_string(), "\"Post 2\"".to_string());
619
620        let incoming_record2 = Record {
621            id: RecordId::new("post".to_string(), "post2".to_string()),
622            revision: 101,
623            schema_version: "1.0.0".to_string(),
624            data: incoming_data2,
625        };
626
627        storage
628            .insert_incoming_records(vec![incoming_record1.clone(), incoming_record2.clone()])
629            .await
630            .unwrap();
631
632        // Test 14: Get incoming records
633        let incoming = storage.get_incoming_records(10).await.unwrap();
634        assert_eq!(incoming.len(), 2, "Should have 2 incoming records");
635        assert_eq!(incoming[0].new_state.id.r#type, "post");
636        assert_eq!(incoming[0].new_state.revision, 100);
637        assert!(
638            incoming[0].old_state.is_none(),
639            "New incoming record should have no old state"
640        );
641
642        // Test 15: Update record from incoming (moves to sync_state)
643        storage
644            .update_record_from_incoming(incoming_record1.clone())
645            .await
646            .unwrap();
647
648        // Test 16: Delete incoming record
649        storage
650            .delete_incoming_record(incoming_record1.clone())
651            .await
652            .unwrap();
653
654        // Test 17: Check incoming records after deletion
655        let incoming = storage.get_incoming_records(10).await.unwrap();
656        assert_eq!(incoming.len(), 1, "Should have 1 incoming record remaining");
657        assert_eq!(incoming[0].new_state.id.data_id, "post2");
658
659        // Test 18: Insert incoming record that updates existing state
660        let mut updated_incoming_data = HashMap::new();
661        updated_incoming_data.insert("title".to_string(), "\"Post 1 Updated\"".to_string());
662        updated_incoming_data.insert("content".to_string(), "\"Updated content\"".to_string());
663
664        let updated_incoming_record = Record {
665            id: RecordId::new("post".to_string(), "post1".to_string()),
666            revision: 102,
667            schema_version: "1.0.0".to_string(),
668            data: updated_incoming_data,
669        };
670
671        storage
672            .insert_incoming_records(vec![updated_incoming_record.clone()])
673            .await
674            .unwrap();
675
676        // Test 19: Get incoming records with old_state
677        let incoming = storage.get_incoming_records(10).await.unwrap();
678        let post1_update = incoming.iter().find(|r| r.new_state.id.data_id == "post1");
679        assert!(post1_update.is_some(), "Should find post1 update");
680        let post1_update = post1_update.unwrap();
681        assert!(
682            post1_update.old_state.is_some(),
683            "Update should have old state"
684        );
685        assert_eq!(
686            post1_update.old_state.as_ref().unwrap().revision,
687            100,
688            "Old state should be original revision"
689        );
690
691        // Test 20: Rebase pending outgoing records
692        storage.rebase_pending_outgoing_records(150).await.unwrap();
693
694        // Test 21: Check that pending outgoing change revision was updated
695        let pending = storage.get_pending_outgoing_changes(10).await.unwrap();
696        assert!(
697            pending[0].change.revision > revision2,
698            "Revision should be rebased"
699        );
700
701        // Test 22: Test limit on pending outgoing changes
702        // Add multiple changes
703        for i in 0..5 {
704            let mut fields = HashMap::new();
705            fields.insert("value".to_string(), format!("\"{i}\""));
706
707            let change = UnversionedRecordChange {
708                id: RecordId::new("test".to_string(), format!("test{i}")),
709                schema_version: "1.0.0".to_string(),
710                updated_fields: fields,
711            };
712            storage.add_outgoing_change(change).await.unwrap();
713        }
714
715        let pending_limited = storage.get_pending_outgoing_changes(3).await.unwrap();
716        assert_eq!(
717            pending_limited.len(),
718            3,
719            "Should respect limit on pending changes"
720        );
721
722        // Test 23: Test limit on incoming records
723        let incoming_limited = storage.get_incoming_records(1).await.unwrap();
724        assert_eq!(
725            incoming_limited.len(),
726            1,
727            "Should respect limit on incoming records"
728        );
729
730        // Test 24: Test ordering - pending outgoing should be ordered by revision ASC
731        let all_pending = storage.get_pending_outgoing_changes(100).await.unwrap();
732        for i in 1..all_pending.len() {
733            assert!(
734                all_pending[i].change.revision >= all_pending[i.saturating_sub(1)].change.revision,
735                "Pending changes should be ordered by revision ascending"
736            );
737        }
738
739        // Test 25: Test ordering - incoming should be ordered by revision ASC
740        let all_incoming = storage.get_incoming_records(100).await.unwrap();
741        for i in 1..all_incoming.len() {
742            assert!(
743                all_incoming[i].new_state.revision
744                    >= all_incoming[i.saturating_sub(1)].new_state.revision,
745                "Incoming records should be ordered by revision ascending"
746            );
747        }
748
749        // Test 26: Test empty insert_incoming_records
750        storage.insert_incoming_records(vec![]).await.unwrap();
751
752        // Test 27: Test different record types
753        let mut settings_fields = HashMap::new();
754        settings_fields.insert("theme".to_string(), "\"dark\"".to_string());
755
756        let settings_change = UnversionedRecordChange {
757            id: RecordId::new("settings".to_string(), "global".to_string()),
758            schema_version: "2.0.0".to_string(),
759            updated_fields: settings_fields,
760        };
761
762        let settings_revision = storage.add_outgoing_change(settings_change).await.unwrap();
763
764        let pending = storage.get_pending_outgoing_changes(100).await.unwrap();
765        let settings_pending = pending.iter().find(|p| p.change.id.r#type == "settings");
766        assert!(settings_pending.is_some(), "Should find settings change");
767        assert_eq!(
768            settings_pending.unwrap().change.schema_version,
769            "2.0.0",
770            "Should preserve schema version"
771        );
772
773        // Test 28: Complete multiple types
774        let mut complete_settings_data = HashMap::new();
775        complete_settings_data.insert("theme".to_string(), "\"dark\"".to_string());
776
777        let completed_settings = Record {
778            id: RecordId::new("settings".to_string(), "global".to_string()),
779            revision: settings_revision,
780            schema_version: "2.0.0".to_string(),
781            data: complete_settings_data,
782        };
783
784        storage
785            .complete_outgoing_sync(completed_settings)
786            .await
787            .unwrap();
788
789        let last_revision = storage.get_last_revision().await.unwrap();
790        assert!(
791            last_revision >= settings_revision,
792            "Last revision should be at least settings revision"
793        );
794    }
795
796    #[allow(clippy::too_many_lines)]
797    pub async fn test_sqlite_storage(storage: Box<dyn Storage>) {
798        use crate::SetLnurlMetadataItem;
799        use crate::models::{LnurlPayInfo, TokenMetadata};
800
801        // Test 1: Spark invoice payment
802        let spark_payment = Payment {
803            id: "spark_pmt123".to_string(),
804            payment_type: PaymentType::Send,
805            status: PaymentStatus::Completed,
806            amount: u128::from(u64::MAX).checked_add(100_000).unwrap(),
807            fees: 1_000,
808            timestamp: 5_000,
809            method: PaymentMethod::Spark,
810            details: Some(PaymentDetails::Spark {
811                invoice_details: Some(crate::SparkInvoicePaymentDetails {
812                    description: Some("description".to_string()),
813                    invoice: "invoice_string".to_string(),
814                }),
815                htlc_details: None,
816                conversion_info: None,
817            }),
818        };
819
820        // Test 2: Spark HTLC payment
821        let spark_htlc_payment = Payment {
822            id: "spark_htlc_pmt123".to_string(),
823            payment_type: PaymentType::Receive,
824            status: PaymentStatus::Completed,
825            amount: 20_000,
826            fees: 2_000,
827            timestamp: 10_000,
828            method: PaymentMethod::Spark,
829            details: Some(PaymentDetails::Spark {
830                invoice_details: None,
831                htlc_details: Some(SparkHtlcDetails {
832                    payment_hash: "payment_hash123".to_string(),
833                    preimage: Some("preimage123".to_string()),
834                    expiry_time: 15_000,
835                    status: SparkHtlcStatus::PreimageShared,
836                }),
837                conversion_info: None,
838            }),
839        };
840
841        // Test 3: Token payment
842        let token_metadata = TokenMetadata {
843            identifier: "token123".to_string(),
844            issuer_public_key:
845                "02abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab".to_string(),
846            name: "Test Token".to_string(),
847            ticker: "TTK".to_string(),
848            decimals: 8,
849            max_supply: 21_000_000,
850            is_freezable: false,
851        };
852        let token_payment = Payment {
853            id: "token_pmt456".to_string(),
854            payment_type: PaymentType::Receive,
855            status: PaymentStatus::Pending,
856            amount: 50_000,
857            fees: 500,
858            timestamp: Utc::now().timestamp().try_into().unwrap(),
859            method: PaymentMethod::Token,
860            details: Some(PaymentDetails::Token {
861                metadata: token_metadata.clone(),
862                tx_hash: "tx_hash".to_string(),
863                invoice_details: Some(crate::SparkInvoicePaymentDetails {
864                    description: Some("description_2".to_string()),
865                    invoice: "invoice_string_2".to_string(),
866                }),
867                conversion_info: None,
868            }),
869        };
870
871        // Test 4: Lightning payment with full details
872        let pay_metadata = PaymentMetadata {
873            lnurl_pay_info: Some(LnurlPayInfo {
874                ln_address: Some("test@example.com".to_string()),
875                comment: Some("Test comment".to_string()),
876                domain: Some("example.com".to_string()),
877                metadata: Some("[[\"text/plain\", \"Test metadata\"]]".to_string()),
878                processed_success_action: None,
879                raw_success_action: None,
880            }),
881            ..Default::default()
882        };
883
884        let lightning_lnurl_pay_payment = Payment {
885            id: "lightning_pmt789".to_string(),
886            payment_type: PaymentType::Send,
887            status: PaymentStatus::Completed,
888            amount: 25_000,
889            fees: 250,
890            timestamp: Utc::now().timestamp().try_into().unwrap(),
891            method: PaymentMethod::Lightning,
892            details: Some(PaymentDetails::Lightning {
893                description: Some("Test lightning payment".to_string()),
894                preimage: Some("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab".to_string()),
895                invoice: "lnbc250n1pjqxyz9pp5abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567890abcdefghijklmnopqrstuvwxyz".to_string(),
896                payment_hash: "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321".to_string(),
897                destination_pubkey: "03123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01".to_string(),
898                lnurl_pay_info: pay_metadata.lnurl_pay_info.clone(),
899                lnurl_withdraw_info: pay_metadata.lnurl_withdraw_info.clone(),
900                lnurl_receive_metadata: None,
901            }),
902        };
903
904        // Test 5: Lightning payment with full details
905        let withdraw_metadata = PaymentMetadata {
906            lnurl_withdraw_info: Some(LnurlWithdrawInfo {
907                withdraw_url: "http://example.com/withdraw".to_string(),
908            }),
909            ..Default::default()
910        };
911        let lightning_lnurl_withdraw_payment = Payment {
912            id: "lightning_pmtabc".to_string(),
913            payment_type: PaymentType::Receive,
914            status: PaymentStatus::Completed,
915            amount: 75_000,
916            fees: 750,
917            timestamp: Utc::now().timestamp().try_into().unwrap(),
918            method: PaymentMethod::Lightning,
919            details: Some(PaymentDetails::Lightning {
920                description: Some("Test lightning payment".to_string()),
921                preimage: Some("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab".to_string()),
922                invoice: "lnbc250n1pjqxyz9pp5abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567890abcdefghijklmnopqrstuvwxyz".to_string(),
923                payment_hash: "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321".to_string(),
924                destination_pubkey: "03123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01".to_string(),
925                lnurl_pay_info: withdraw_metadata.lnurl_pay_info.clone(),
926                lnurl_withdraw_info: withdraw_metadata.lnurl_withdraw_info.clone(),
927                lnurl_receive_metadata: None,
928            }),
929        };
930
931        // Test 6: Lightning payment with minimal details
932        let lightning_minimal_payment = Payment {
933            id: "lightning_minimal_pmt012".to_string(),
934            payment_type: PaymentType::Receive,
935            status: PaymentStatus::Failed,
936            amount: 10_000,
937            fees: 100,
938            timestamp: Utc::now().timestamp().try_into().unwrap(),
939            method: PaymentMethod::Lightning,
940            details: Some(PaymentDetails::Lightning {
941                description: None,
942                preimage: None,
943                invoice: "lnbc100n1pjqxyz9pp5def456ghi789jkl012mno345pqr678stu901vwx234yz567890abcdefghijklmnopqrstuvwxyz".to_string(),
944                payment_hash: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string(),
945                destination_pubkey: "02987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba09".to_string(),
946                lnurl_pay_info: None,
947                lnurl_withdraw_info: None,
948                lnurl_receive_metadata: None,
949            }),
950        };
951
952        // Test 7: Lightning payment with LNURL receive metadata
953        let lnurl_receive_payment_hash =
954            "receivehash1234567890abcdef1234567890abcdef1234567890abcdef1234".to_string();
955        let lnurl_receive_metadata = crate::LnurlReceiveMetadata {
956            sender_comment: Some("Test sender comment".to_string()),
957            nostr_zap_request: Some(r#"{"kind":9734,"content":"test zap"}"#.to_string()),
958            nostr_zap_receipt: Some(r#"{"kind":9735,"content":"test receipt"}"#.to_string()),
959        };
960        let lightning_lnurl_receive_payment = Payment {
961            id: "lightning_lnurl_receive_pmt".to_string(),
962            payment_type: PaymentType::Receive,
963            status: PaymentStatus::Completed,
964            amount: 100_000,
965            fees: 1000,
966            timestamp: Utc::now().timestamp().try_into().unwrap(),
967            method: PaymentMethod::Lightning,
968            details: Some(PaymentDetails::Lightning {
969                description: Some("LNURL receive test".to_string()),
970                preimage: Some("receivepreimage1234567890abcdef1234567890abcdef1234567890abcdef12".to_string()),
971                invoice: "lnbc1000n1pjqxyz9pp5receive123def456ghi789jkl012mno345pqr678stu901vwx234yz567890abcdefghijklmnopqrstuvwxyz".to_string(),
972                payment_hash: lnurl_receive_payment_hash.clone(),
973                destination_pubkey: "03receivepubkey123456789abcdef0123456789abcdef0123456789abcdef01234".to_string(),
974                lnurl_pay_info: None,
975                lnurl_withdraw_info: None,
976                lnurl_receive_metadata: Some(lnurl_receive_metadata.clone()),
977            }),
978        };
979
980        // Test 8: Withdraw payment
981        let withdraw_payment = Payment {
982            id: "withdraw_pmt345".to_string(),
983            payment_type: PaymentType::Send,
984            status: PaymentStatus::Completed,
985            amount: 200_000,
986            fees: 2000,
987            timestamp: Utc::now().timestamp().try_into().unwrap(),
988            method: PaymentMethod::Withdraw,
989            details: Some(PaymentDetails::Withdraw {
990                tx_id: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
991                    .to_string(),
992            }),
993        };
994
995        // Test 9: Deposit payment
996        let deposit_payment = Payment {
997            id: "deposit_pmt678".to_string(),
998            payment_type: PaymentType::Receive,
999            status: PaymentStatus::Completed,
1000            amount: 150_000,
1001            fees: 1500,
1002            timestamp: Utc::now().timestamp().try_into().unwrap(),
1003            method: PaymentMethod::Deposit,
1004            details: Some(PaymentDetails::Deposit {
1005                tx_id: "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321fe"
1006                    .to_string(),
1007            }),
1008        };
1009
1010        // Test 10: Payment with no details
1011        let no_details_payment = Payment {
1012            id: "no_details_pmt901".to_string(),
1013            payment_type: PaymentType::Send,
1014            status: PaymentStatus::Pending,
1015            amount: 75_000,
1016            fees: 750,
1017            timestamp: Utc::now().timestamp().try_into().unwrap(),
1018            method: PaymentMethod::Unknown,
1019            details: None,
1020        };
1021
1022        // Test 11: Successful conversion payment
1023        let successful_sent_conversion_payment_metadata = PaymentMetadata {
1024            parent_payment_id: Some("after_conversion_pmt124".to_string()),
1025            conversion_info: Some(crate::ConversionInfo {
1026                pool_id: "pool_abc".to_string(),
1027                conversion_id: "conversion_sent_pmt123".to_string(),
1028                status: crate::ConversionStatus::Completed,
1029                fee: Some(21),
1030                purpose: None,
1031            }),
1032            ..Default::default()
1033        };
1034        let successful_sent_conversion_payment = Payment {
1035            id: "conversion_sent_pmt123".to_string(),
1036            payment_type: PaymentType::Send,
1037            status: PaymentStatus::Completed,
1038            amount: 10_000,
1039            fees: 0,
1040            timestamp: Utc::now().timestamp().try_into().unwrap(),
1041            method: PaymentMethod::Spark,
1042            details: Some(PaymentDetails::Spark {
1043                invoice_details: None,
1044                htlc_details: None,
1045                conversion_info: successful_sent_conversion_payment_metadata
1046                    .conversion_info
1047                    .clone(),
1048            }),
1049        };
1050        let successful_received_conversion_payment = Payment {
1051            id: "conversion_received_pmt123".to_string(),
1052            payment_type: PaymentType::Receive,
1053            status: PaymentStatus::Completed,
1054            amount: 10_000_000,
1055            fees: 0,
1056            timestamp: Utc::now().timestamp().try_into().unwrap(),
1057            method: PaymentMethod::Token,
1058            details: Some(PaymentDetails::Token {
1059                metadata: token_metadata.clone(),
1060                tx_hash: "conversion_received_pmt123_tx_hash".to_string(),
1061                invoice_details: None,
1062                conversion_info: None,
1063            }),
1064        };
1065        let after_conversion_payment = Payment {
1066            id: "after_conversion_pmt124".to_string(),
1067            payment_type: PaymentType::Send,
1068            status: PaymentStatus::Completed,
1069            amount: 10_000_000,
1070            fees: 0,
1071            timestamp: Utc::now().timestamp().try_into().unwrap(),
1072            method: PaymentMethod::Token,
1073            details: Some(PaymentDetails::Token {
1074                metadata: token_metadata.clone(),
1075                tx_hash: "after_conversion_pmt124_tx_hash".to_string(),
1076                invoice_details: None,
1077                conversion_info: None,
1078            }),
1079        };
1080
1081        // Test 12: Failed conversion payment with refund info
1082        let failed_with_refund_conversion_payment_metadata = PaymentMetadata {
1083            conversion_info: Some(crate::ConversionInfo {
1084                pool_id: "pool_xyz".to_string(),
1085                conversion_id: "conversion_pmt789".to_string(),
1086                status: crate::ConversionStatus::Refunded,
1087                fee: None,
1088                purpose: None,
1089            }),
1090            ..Default::default()
1091        };
1092        let failed_with_refund_conversion_payment = Payment {
1093            id: "conversion_pmt789".to_string(),
1094            payment_type: PaymentType::Send,
1095            status: PaymentStatus::Completed,
1096            amount: 10_000,
1097            fees: 0,
1098            timestamp: Utc::now().timestamp().try_into().unwrap(),
1099            method: PaymentMethod::Spark,
1100            details: Some(PaymentDetails::Spark {
1101                invoice_details: None,
1102                htlc_details: None,
1103                conversion_info: failed_with_refund_conversion_payment_metadata
1104                    .conversion_info
1105                    .clone(),
1106            }),
1107        };
1108
1109        // Test 13: Failed conversion payment with no refund info
1110        let failed_no_refund_conversion_payment_metadata = PaymentMetadata {
1111            conversion_info: Some(crate::ConversionInfo {
1112                pool_id: "pool_xyz".to_string(),
1113                conversion_id: "conversion_pmt000".to_string(),
1114                status: crate::ConversionStatus::RefundNeeded,
1115                fee: None,
1116                purpose: None,
1117            }),
1118            ..Default::default()
1119        };
1120        let failed_no_refund_conversion_payment = Payment {
1121            id: "conversion_pmt000".to_string(),
1122            payment_type: PaymentType::Send,
1123            status: PaymentStatus::Completed,
1124            amount: 20_000,
1125            fees: 0,
1126            timestamp: Utc::now().timestamp().try_into().unwrap(),
1127            method: PaymentMethod::Spark,
1128            details: Some(PaymentDetails::Spark {
1129                invoice_details: None,
1130                htlc_details: None,
1131                conversion_info: failed_no_refund_conversion_payment_metadata
1132                    .conversion_info
1133                    .clone(),
1134            }),
1135        };
1136
1137        let test_payments = vec![
1138            spark_payment.clone(),
1139            spark_htlc_payment.clone(),
1140            token_payment.clone(),
1141            lightning_lnurl_pay_payment.clone(),
1142            lightning_lnurl_withdraw_payment.clone(),
1143            lightning_minimal_payment.clone(),
1144            lightning_lnurl_receive_payment.clone(),
1145            withdraw_payment.clone(),
1146            deposit_payment.clone(),
1147            no_details_payment.clone(),
1148            successful_sent_conversion_payment.clone(),
1149            successful_received_conversion_payment.clone(),
1150            after_conversion_payment.clone(),
1151            failed_with_refund_conversion_payment.clone(),
1152            failed_no_refund_conversion_payment.clone(),
1153        ];
1154
1155        // Insert all payments
1156        for payment in &test_payments {
1157            storage.insert_payment(payment.clone()).await.unwrap();
1158        }
1159        storage
1160            .set_payment_metadata(lightning_lnurl_pay_payment.id.clone(), pay_metadata)
1161            .await
1162            .unwrap();
1163        storage
1164            .set_payment_metadata(
1165                lightning_lnurl_withdraw_payment.id.clone(),
1166                withdraw_metadata,
1167            )
1168            .await
1169            .unwrap();
1170        storage
1171            .set_lnurl_metadata(vec![SetLnurlMetadataItem {
1172                nostr_zap_receipt: lnurl_receive_metadata.nostr_zap_receipt.clone(),
1173                nostr_zap_request: lnurl_receive_metadata.nostr_zap_request.clone(),
1174                payment_hash: lnurl_receive_payment_hash.clone(),
1175                sender_comment: lnurl_receive_metadata.sender_comment.clone(),
1176            }])
1177            .await
1178            .unwrap();
1179        storage
1180            .set_payment_metadata(
1181                successful_sent_conversion_payment.id.clone(),
1182                successful_sent_conversion_payment_metadata,
1183            )
1184            .await
1185            .unwrap();
1186        storage
1187            .set_payment_metadata(
1188                failed_with_refund_conversion_payment.id.clone(),
1189                failed_with_refund_conversion_payment_metadata,
1190            )
1191            .await
1192            .unwrap();
1193        storage
1194            .set_payment_metadata(
1195                failed_no_refund_conversion_payment.id.clone(),
1196                failed_no_refund_conversion_payment_metadata,
1197            )
1198            .await
1199            .unwrap();
1200        // List all payments
1201        let payments = storage
1202            .list_payments(ListPaymentsRequest {
1203                offset: Some(0),
1204                limit: Some(16),
1205                ..Default::default()
1206            })
1207            .await
1208            .unwrap();
1209        assert_eq!(payments.len(), 15);
1210
1211        // Test each payment type individually
1212        for (i, expected_payment) in test_payments.iter().enumerate() {
1213            let retrieved_payment = storage
1214                .get_payment_by_id(expected_payment.id.clone())
1215                .await
1216                .unwrap();
1217
1218            // Basic fields
1219            assert_eq!(retrieved_payment.id, expected_payment.id);
1220            assert_eq!(
1221                retrieved_payment.payment_type,
1222                expected_payment.payment_type
1223            );
1224            assert_eq!(retrieved_payment.status, expected_payment.status);
1225            assert_eq!(retrieved_payment.amount, expected_payment.amount);
1226            assert_eq!(retrieved_payment.fees, expected_payment.fees);
1227            assert_eq!(retrieved_payment.method, expected_payment.method);
1228
1229            // Test payment details persistence
1230            match (&retrieved_payment.details, &expected_payment.details) {
1231                (None, None) => {}
1232                (
1233                    Some(PaymentDetails::Spark {
1234                        invoice_details: r_invoice,
1235                        htlc_details: r_htlc,
1236                        conversion_info: r_conversion_info,
1237                    }),
1238                    Some(PaymentDetails::Spark {
1239                        invoice_details: e_invoice,
1240                        htlc_details: e_htlc,
1241                        conversion_info: e_conversion_info,
1242                    }),
1243                ) => {
1244                    assert_eq!(r_invoice, e_invoice);
1245                    assert_eq!(r_htlc, e_htlc);
1246                    assert_eq!(r_conversion_info, e_conversion_info);
1247                }
1248                (
1249                    Some(PaymentDetails::Token {
1250                        metadata: r_metadata,
1251                        tx_hash: r_tx_hash,
1252                        invoice_details: r_invoice,
1253                        conversion_info: r_conversion_info,
1254                    }),
1255                    Some(PaymentDetails::Token {
1256                        metadata: e_metadata,
1257                        tx_hash: e_tx_hash,
1258                        invoice_details: e_invoice,
1259                        conversion_info: e_conversion_info,
1260                    }),
1261                ) => {
1262                    assert_eq!(r_metadata.identifier, e_metadata.identifier);
1263                    assert_eq!(r_metadata.issuer_public_key, e_metadata.issuer_public_key);
1264                    assert_eq!(r_metadata.name, e_metadata.name);
1265                    assert_eq!(r_metadata.ticker, e_metadata.ticker);
1266                    assert_eq!(r_metadata.decimals, e_metadata.decimals);
1267                    assert_eq!(r_metadata.max_supply, e_metadata.max_supply);
1268                    assert_eq!(r_metadata.is_freezable, e_metadata.is_freezable);
1269                    assert_eq!(r_tx_hash, e_tx_hash);
1270                    assert_eq!(r_invoice, e_invoice);
1271                    assert_eq!(r_conversion_info, e_conversion_info);
1272                }
1273                (
1274                    Some(PaymentDetails::Lightning {
1275                        description: r_description,
1276                        preimage: r_preimage,
1277                        invoice: r_invoice,
1278                        payment_hash: r_hash,
1279                        destination_pubkey: r_dest_pubkey,
1280                        lnurl_pay_info: r_pay_lnurl,
1281                        lnurl_withdraw_info: r_withdraw_lnurl,
1282                        lnurl_receive_metadata: r_receive_metadata,
1283                    }),
1284                    Some(PaymentDetails::Lightning {
1285                        description: e_description,
1286                        preimage: e_preimage,
1287                        invoice: e_invoice,
1288                        payment_hash: e_hash,
1289                        destination_pubkey: e_dest_pubkey,
1290                        lnurl_pay_info: e_pay_lnurl,
1291                        lnurl_withdraw_info: e_withdraw_lnurl,
1292                        lnurl_receive_metadata: e_receive_metadata,
1293                    }),
1294                ) => {
1295                    assert_eq!(r_description, e_description);
1296                    assert_eq!(r_preimage, e_preimage);
1297                    assert_eq!(r_invoice, e_invoice);
1298                    assert_eq!(r_hash, e_hash);
1299                    assert_eq!(r_dest_pubkey, e_dest_pubkey);
1300
1301                    // Test LNURL pay info if present
1302                    match (r_pay_lnurl, e_pay_lnurl) {
1303                        (Some(r_info), Some(e_info)) => {
1304                            assert_eq!(r_info.ln_address, e_info.ln_address);
1305                            assert_eq!(r_info.comment, e_info.comment);
1306                            assert_eq!(r_info.domain, e_info.domain);
1307                            assert_eq!(r_info.metadata, e_info.metadata);
1308                        }
1309                        (None, None) => {}
1310                        _ => panic!(
1311                            "LNURL pay info mismatch for payment {}",
1312                            expected_payment.id
1313                        ),
1314                    }
1315
1316                    // Test LNURL withdraw info if present
1317                    match (r_withdraw_lnurl, e_withdraw_lnurl) {
1318                        (Some(r_info), Some(e_info)) => {
1319                            assert_eq!(r_info.withdraw_url, e_info.withdraw_url);
1320                        }
1321                        (None, None) => {}
1322                        _ => panic!(
1323                            "LNURL withdraw info mismatch for payment {}",
1324                            expected_payment.id
1325                        ),
1326                    }
1327
1328                    // Test LNURL receive metadata if present
1329                    match (r_receive_metadata, e_receive_metadata) {
1330                        (Some(r_info), Some(e_info)) => {
1331                            assert_eq!(r_info.nostr_zap_request, e_info.nostr_zap_request);
1332                            assert_eq!(r_info.sender_comment, e_info.sender_comment);
1333                        }
1334                        (None, None) => {}
1335                        _ => panic!(
1336                            "LNURL receive metadata mismatch for payment {}",
1337                            expected_payment.id
1338                        ),
1339                    }
1340                }
1341                (
1342                    Some(PaymentDetails::Withdraw { tx_id: r_tx_id }),
1343                    Some(PaymentDetails::Withdraw { tx_id: e_tx_id }),
1344                )
1345                | (
1346                    Some(PaymentDetails::Deposit { tx_id: r_tx_id }),
1347                    Some(PaymentDetails::Deposit { tx_id: e_tx_id }),
1348                ) => {
1349                    assert_eq!(r_tx_id, e_tx_id);
1350                }
1351                _ => panic!(
1352                    "Payment details mismatch for payment {} (index {})",
1353                    expected_payment.id, i
1354                ),
1355            }
1356        }
1357
1358        // Test filtering by payment type
1359        let send_payments = payments
1360            .iter()
1361            .filter(|p| p.payment_type == PaymentType::Send)
1362            .count();
1363        let receive_payments = payments
1364            .iter()
1365            .filter(|p| p.payment_type == PaymentType::Receive)
1366            .count();
1367        assert_eq!(send_payments, 8); // spark, lightning_lnurl_pay, withdraw, no_details, conversion x4
1368        assert_eq!(receive_payments, 7); // spark_htlc, token, lightning_lnurl_withdraw, lightning_minimal, lightning_lnurl_receive, deposit, conversion
1369
1370        // Test filtering by status
1371        let completed_payments = payments
1372            .iter()
1373            .filter(|p| p.status == PaymentStatus::Completed)
1374            .count();
1375        let pending_payments = payments
1376            .iter()
1377            .filter(|p| p.status == PaymentStatus::Pending)
1378            .count();
1379        let failed_payments = payments
1380            .iter()
1381            .filter(|p| p.status == PaymentStatus::Failed)
1382            .count();
1383        assert_eq!(completed_payments, 12); // spark, spark_htlc, lightning_lnurl_pay, lightning_lnurl_withdraw, lightning_lnurl_receive, withdraw, deposit, conversion x5
1384        assert_eq!(pending_payments, 2); // token, no_details
1385        assert_eq!(failed_payments, 1); // lightning_minimal
1386
1387        // Test filtering by method
1388        let lightning_count = payments
1389            .iter()
1390            .filter(|p| p.method == PaymentMethod::Lightning)
1391            .count();
1392        assert_eq!(lightning_count, 4); // lightning_lnurl_pay, lightning_lnurl_withdraw, lightning_minimal, lightning_lnurl_receive
1393
1394        // Test 9: Lightning payment with lnurl receive metadata (zap request and sender comment)
1395        let lightning_zap_payment = Payment {
1396            id: "lightning_zap_pmt".to_string(),
1397            payment_type: PaymentType::Receive,
1398            status: PaymentStatus::Completed,
1399            amount: 100_000,
1400            fees: 1000,
1401            timestamp: Utc::now().timestamp().try_into().unwrap(),
1402            method: PaymentMethod::Lightning,
1403            details: Some(PaymentDetails::Lightning {
1404                description: Some("Zap payment".to_string()),
1405                preimage: Some("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01".to_string()),
1406                invoice: "lnbc1000n1pjqxyz9pp5zap123def456ghi789jkl012mno345pqr678stu901vwx234yz567890abcdefghijklmnopqrstuvwxyz".to_string(),
1407                payment_hash: "zaphash1234567890abcdef1234567890abcdef1234567890abcdef12345678".to_string(),
1408                destination_pubkey: "03zappubkey123456789abcdef0123456789abcdef0123456789abcdef0123456701".to_string(),
1409                lnurl_pay_info: None,
1410                lnurl_withdraw_info: None,
1411                lnurl_receive_metadata: None,
1412            }),
1413        };
1414
1415        storage
1416            .insert_payment(lightning_zap_payment.clone())
1417            .await
1418            .unwrap();
1419
1420        // Add lnurl receive metadata for the zap payment
1421        storage
1422            .set_lnurl_metadata(vec![SetLnurlMetadataItem {
1423                payment_hash: "zaphash1234567890abcdef1234567890abcdef1234567890abcdef12345678"
1424                    .to_string(),
1425                sender_comment: Some("Great content!".to_string()),
1426                nostr_zap_request: Some(
1427                    r#"{"kind":9734,"content":"zap request","tags":[]}"#.to_string(),
1428                ),
1429                nostr_zap_receipt: Some(
1430                    r#"{"kind":9735,"content":"zap receipt","tags":[]}"#.to_string(),
1431                ),
1432            }])
1433            .await
1434            .unwrap();
1435
1436        // Retrieve the payment and verify lnurl receive metadata is present
1437        let retrieved_zap_payment = storage
1438            .get_payment_by_id(lightning_zap_payment.id.clone())
1439            .await
1440            .unwrap();
1441
1442        match retrieved_zap_payment.details {
1443            Some(PaymentDetails::Lightning {
1444                lnurl_receive_metadata: Some(metadata),
1445                ..
1446            }) => {
1447                assert_eq!(
1448                    metadata.sender_comment,
1449                    Some("Great content!".to_string()),
1450                    "Sender comment should match"
1451                );
1452                assert_eq!(
1453                    metadata.nostr_zap_request,
1454                    Some(r#"{"kind":9734,"content":"zap request","tags":[]}"#.to_string()),
1455                    "Nostr zap request should match"
1456                );
1457                assert_eq!(
1458                    metadata.nostr_zap_receipt,
1459                    Some(r#"{"kind":9735,"content":"zap receipt","tags":[]}"#.to_string()),
1460                    "Nostr zap receipt should match"
1461                );
1462            }
1463            _ => panic!("Expected Lightning payment with lnurl receive metadata"),
1464        }
1465
1466        // Test 10: Add multiple lnurl receive metadata items at once
1467        let lightning_zap_payment2 = Payment {
1468            id: "lightning_zap_pmt2".to_string(),
1469            payment_type: PaymentType::Receive,
1470            status: PaymentStatus::Completed,
1471            amount: 50_000,
1472            fees: 500,
1473            timestamp: Utc::now().timestamp().try_into().unwrap(),
1474            method: PaymentMethod::Lightning,
1475            details: Some(PaymentDetails::Lightning {
1476                description: Some("Another zap".to_string()),
1477                preimage: None,
1478                invoice: "lnbc500n1pjqxyz9pp5zap2".to_string(),
1479                payment_hash: "zaphash2".to_string(),
1480                destination_pubkey: "03zappubkey2".to_string(),
1481                lnurl_pay_info: None,
1482                lnurl_withdraw_info: None,
1483                lnurl_receive_metadata: None,
1484            }),
1485        };
1486
1487        let lightning_zap_payment3 = Payment {
1488            id: "lightning_zap_pmt3".to_string(),
1489            payment_type: PaymentType::Receive,
1490            status: PaymentStatus::Completed,
1491            amount: 25_000,
1492            fees: 250,
1493            timestamp: Utc::now().timestamp().try_into().unwrap(),
1494            method: PaymentMethod::Lightning,
1495            details: Some(PaymentDetails::Lightning {
1496                description: Some("Third zap".to_string()),
1497                preimage: None,
1498                invoice: "lnbc250n1pjqxyz9pp5zap3".to_string(),
1499                payment_hash: "zaphash3".to_string(),
1500                destination_pubkey: "03zappubkey3".to_string(),
1501                lnurl_pay_info: None,
1502                lnurl_withdraw_info: None,
1503                lnurl_receive_metadata: None,
1504            }),
1505        };
1506
1507        storage
1508            .insert_payment(lightning_zap_payment2.clone())
1509            .await
1510            .unwrap();
1511        storage
1512            .insert_payment(lightning_zap_payment3.clone())
1513            .await
1514            .unwrap();
1515
1516        // Add multiple metadata items at once
1517        storage
1518            .set_lnurl_metadata(vec![
1519                SetLnurlMetadataItem {
1520                    payment_hash: "zaphash2".to_string(),
1521                    sender_comment: Some("Nice work!".to_string()),
1522                    nostr_zap_request: None,
1523                    nostr_zap_receipt: None,
1524                },
1525                SetLnurlMetadataItem {
1526                    payment_hash: "zaphash3".to_string(),
1527                    sender_comment: None,
1528                    nostr_zap_request: Some(r#"{"kind":9734,"content":"zap3"}"#.to_string()),
1529                    nostr_zap_receipt: None,
1530                },
1531            ])
1532            .await
1533            .unwrap();
1534
1535        // Verify both payments have their respective metadata
1536        let retrieved_zap2 = storage
1537            .get_payment_by_id(lightning_zap_payment2.id.clone())
1538            .await
1539            .unwrap();
1540
1541        match retrieved_zap2.details {
1542            Some(PaymentDetails::Lightning {
1543                lnurl_receive_metadata: Some(metadata),
1544                ..
1545            }) => {
1546                assert_eq!(
1547                    metadata.sender_comment,
1548                    Some("Nice work!".to_string()),
1549                    "Second payment should have sender comment"
1550                );
1551                assert_eq!(
1552                    metadata.nostr_zap_request, None,
1553                    "Second payment should not have zap request"
1554                );
1555            }
1556            _ => panic!("Expected Lightning payment with lnurl receive metadata"),
1557        }
1558
1559        let retrieved_zap3 = storage
1560            .get_payment_by_id(lightning_zap_payment3.id.clone())
1561            .await
1562            .unwrap();
1563
1564        match retrieved_zap3.details {
1565            Some(PaymentDetails::Lightning {
1566                lnurl_receive_metadata: Some(metadata),
1567                ..
1568            }) => {
1569                assert_eq!(
1570                    metadata.sender_comment, None,
1571                    "Third payment should not have sender comment"
1572                );
1573                assert_eq!(
1574                    metadata.nostr_zap_request,
1575                    Some(r#"{"kind":9734,"content":"zap3"}"#.to_string()),
1576                    "Third payment should have zap request"
1577                );
1578            }
1579            _ => panic!("Expected Lightning payment with lnurl receive metadata"),
1580        }
1581
1582        // Test 11: Lightning payment without lnurl receive metadata should return None
1583        let retrieved_minimal = storage
1584            .get_payment_by_id(lightning_minimal_payment.id.clone())
1585            .await
1586            .unwrap();
1587
1588        match retrieved_minimal.details {
1589            Some(PaymentDetails::Lightning {
1590                lnurl_receive_metadata,
1591                ..
1592            }) => {
1593                assert!(
1594                    lnurl_receive_metadata.is_none(),
1595                    "Payment without metadata should have None"
1596                );
1597            }
1598            _ => panic!("Expected Lightning payment"),
1599        }
1600    }
1601
1602    pub async fn test_unclaimed_deposits_crud(storage: Box<dyn Storage>) {
1603        // Initially, list should be empty
1604        let deposits = storage.list_deposits().await.unwrap();
1605        assert_eq!(deposits.len(), 0);
1606
1607        // Add first deposit
1608        storage
1609            .add_deposit("tx123".to_string(), 0, 50000)
1610            .await
1611            .unwrap();
1612        let deposits = storage.list_deposits().await.unwrap();
1613        assert_eq!(deposits.len(), 1);
1614        assert_eq!(deposits[0].txid, "tx123");
1615        assert_eq!(deposits[0].vout, 0);
1616        assert_eq!(deposits[0].amount_sats, 50000);
1617        assert!(deposits[0].claim_error.is_none());
1618
1619        // Add second deposit
1620        storage
1621            .add_deposit("tx456".to_string(), 1, 75000)
1622            .await
1623            .unwrap();
1624        storage
1625            .update_deposit(
1626                "tx456".to_string(),
1627                1,
1628                UpdateDepositPayload::ClaimError {
1629                    error: DepositClaimError::Generic {
1630                        message: "Test error".to_string(),
1631                    },
1632                },
1633            )
1634            .await
1635            .unwrap();
1636        let deposits = storage.list_deposits().await.unwrap();
1637        assert_eq!(deposits.len(), 2);
1638
1639        // Find deposit2 in the list
1640        let deposit2_found = deposits.iter().find(|d| d.txid == "tx456").unwrap();
1641        assert_eq!(deposit2_found.vout, 1);
1642        assert_eq!(deposit2_found.amount_sats, 75000);
1643        assert!(deposit2_found.claim_error.is_some());
1644
1645        // Remove first deposit
1646        storage
1647            .delete_deposit("tx123".to_string(), 0)
1648            .await
1649            .unwrap();
1650        let deposits = storage.list_deposits().await.unwrap();
1651        assert_eq!(deposits.len(), 1);
1652        assert_eq!(deposits[0].txid, "tx456");
1653
1654        // Remove second deposit
1655        storage
1656            .delete_deposit("tx456".to_string(), 1)
1657            .await
1658            .unwrap();
1659        let deposits = storage.list_deposits().await.unwrap();
1660        assert_eq!(deposits.len(), 0);
1661    }
1662
1663    pub async fn test_deposit_refunds(storage: Box<dyn Storage>) {
1664        // Add the initial deposit
1665        storage
1666            .add_deposit("test_tx_123".to_string(), 0, 100_000)
1667            .await
1668            .unwrap();
1669        let deposits = storage.list_deposits().await.unwrap();
1670        assert_eq!(deposits.len(), 1);
1671        assert_eq!(deposits[0].txid, "test_tx_123");
1672        assert_eq!(deposits[0].vout, 0);
1673        assert_eq!(deposits[0].amount_sats, 100_000);
1674        assert!(deposits[0].claim_error.is_none());
1675
1676        // Update the deposit refund information
1677        storage
1678            .update_deposit(
1679                "test_tx_123".to_string(),
1680                0,
1681                UpdateDepositPayload::Refund {
1682                    refund_txid: "refund_tx_id_456".to_string(),
1683                    refund_tx: "0200000001abcd1234...".to_string(),
1684                },
1685            )
1686            .await
1687            .unwrap();
1688
1689        // Verify that the deposit information remains unchanged
1690        let deposits = storage.list_deposits().await.unwrap();
1691        assert_eq!(deposits.len(), 1);
1692        assert_eq!(deposits[0].txid, "test_tx_123");
1693        assert_eq!(deposits[0].vout, 0);
1694        assert_eq!(deposits[0].amount_sats, 100_000);
1695        assert!(deposits[0].claim_error.is_none());
1696        assert_eq!(
1697            deposits[0].refund_tx_id,
1698            Some("refund_tx_id_456".to_string())
1699        );
1700        assert_eq!(
1701            deposits[0].refund_tx,
1702            Some("0200000001abcd1234...".to_string())
1703        );
1704    }
1705
1706    pub async fn test_payment_type_filtering(storage: Box<dyn Storage>) {
1707        // Create test payments with different types
1708        let send_payment = Payment {
1709            id: "send_1".to_string(),
1710            payment_type: PaymentType::Send,
1711            status: PaymentStatus::Completed,
1712            amount: 10_000,
1713            fees: 100,
1714            timestamp: 1000,
1715            method: PaymentMethod::Lightning,
1716            details: Some(PaymentDetails::Lightning {
1717                invoice: "lnbc1".to_string(),
1718                payment_hash: "hash1".to_string(),
1719                destination_pubkey: "pubkey1".to_string(),
1720                description: None,
1721                preimage: None,
1722                lnurl_pay_info: None,
1723                lnurl_withdraw_info: None,
1724                lnurl_receive_metadata: None,
1725            }),
1726        };
1727
1728        let receive_payment = Payment {
1729            id: "receive_1".to_string(),
1730            payment_type: PaymentType::Receive,
1731            status: PaymentStatus::Completed,
1732            amount: 20_000,
1733            fees: 200,
1734            timestamp: 2000,
1735            method: PaymentMethod::Lightning,
1736            details: Some(PaymentDetails::Lightning {
1737                invoice: "lnbc2".to_string(),
1738                payment_hash: "hash2".to_string(),
1739                destination_pubkey: "pubkey2".to_string(),
1740                description: None,
1741                preimage: None,
1742                lnurl_pay_info: None,
1743                lnurl_withdraw_info: None,
1744                lnurl_receive_metadata: None,
1745            }),
1746        };
1747
1748        storage.insert_payment(send_payment).await.unwrap();
1749        storage.insert_payment(receive_payment).await.unwrap();
1750
1751        // Test filter by Send type only
1752        let send_only = storage
1753            .list_payments(ListPaymentsRequest {
1754                type_filter: Some(vec![PaymentType::Send]),
1755                ..Default::default()
1756            })
1757            .await
1758            .unwrap();
1759        assert_eq!(send_only.len(), 1);
1760        assert_eq!(send_only[0].id, "send_1");
1761
1762        // Test filter by Receive type only
1763        let receive_only = storage
1764            .list_payments(ListPaymentsRequest {
1765                type_filter: Some(vec![PaymentType::Receive]),
1766                ..Default::default()
1767            })
1768            .await
1769            .unwrap();
1770        assert_eq!(receive_only.len(), 1);
1771        assert_eq!(receive_only[0].id, "receive_1");
1772
1773        // Test filter by both types
1774        let both_types = storage
1775            .list_payments(ListPaymentsRequest {
1776                type_filter: Some(vec![PaymentType::Send, PaymentType::Receive]),
1777                ..Default::default()
1778            })
1779            .await
1780            .unwrap();
1781        assert_eq!(both_types.len(), 2);
1782
1783        // Test with no filter (should return all)
1784        let all_payments = storage
1785            .list_payments(ListPaymentsRequest::default())
1786            .await
1787            .unwrap();
1788        assert_eq!(all_payments.len(), 2);
1789    }
1790
1791    pub async fn test_payment_status_filtering(storage: Box<dyn Storage>) {
1792        // Create test payments with different statuses
1793        let completed_payment = Payment {
1794            id: "completed_1".to_string(),
1795            payment_type: PaymentType::Send,
1796            status: PaymentStatus::Completed,
1797            amount: 10_000,
1798            fees: 100,
1799            timestamp: 1000,
1800            method: PaymentMethod::Spark,
1801            details: Some(PaymentDetails::Spark {
1802                invoice_details: None,
1803                htlc_details: None,
1804                conversion_info: None,
1805            }),
1806        };
1807
1808        let pending_payment = Payment {
1809            id: "pending_1".to_string(),
1810            payment_type: PaymentType::Send,
1811            status: PaymentStatus::Pending,
1812            amount: 20_000,
1813            fees: 200,
1814            timestamp: 2000,
1815            method: PaymentMethod::Spark,
1816            details: Some(PaymentDetails::Spark {
1817                invoice_details: None,
1818                htlc_details: None,
1819                conversion_info: None,
1820            }),
1821        };
1822
1823        let failed_payment = Payment {
1824            id: "failed_1".to_string(),
1825            payment_type: PaymentType::Send,
1826            status: PaymentStatus::Failed,
1827            amount: 30_000,
1828            fees: 300,
1829            timestamp: 3000,
1830            method: PaymentMethod::Spark,
1831            details: Some(PaymentDetails::Spark {
1832                invoice_details: None,
1833                htlc_details: None,
1834                conversion_info: None,
1835            }),
1836        };
1837
1838        storage.insert_payment(completed_payment).await.unwrap();
1839        storage.insert_payment(pending_payment).await.unwrap();
1840        storage.insert_payment(failed_payment).await.unwrap();
1841
1842        // Test filter by Completed status only
1843        let completed_only = storage
1844            .list_payments(ListPaymentsRequest {
1845                status_filter: Some(vec![PaymentStatus::Completed]),
1846                ..Default::default()
1847            })
1848            .await
1849            .unwrap();
1850        assert_eq!(completed_only.len(), 1);
1851        assert_eq!(completed_only[0].id, "completed_1");
1852
1853        // Test filter by Pending status only
1854        let pending_only = storage
1855            .list_payments(ListPaymentsRequest {
1856                status_filter: Some(vec![PaymentStatus::Pending]),
1857                ..Default::default()
1858            })
1859            .await
1860            .unwrap();
1861        assert_eq!(pending_only.len(), 1);
1862        assert_eq!(pending_only[0].id, "pending_1");
1863
1864        // Test filter by multiple statuses
1865        let completed_or_failed = storage
1866            .list_payments(ListPaymentsRequest {
1867                status_filter: Some(vec![PaymentStatus::Completed, PaymentStatus::Failed]),
1868                ..Default::default()
1869            })
1870            .await
1871            .unwrap();
1872        assert_eq!(completed_or_failed.len(), 2);
1873    }
1874
1875    #[allow(clippy::too_many_lines)]
1876    pub async fn test_asset_filtering(storage: Box<dyn Storage>) {
1877        use crate::models::TokenMetadata;
1878
1879        // Create payments with different asset types
1880        let spark_payment = Payment {
1881            id: "spark_1".to_string(),
1882            payment_type: PaymentType::Send,
1883            status: PaymentStatus::Completed,
1884            amount: 10_000,
1885            fees: 100,
1886            timestamp: 1000,
1887            method: PaymentMethod::Spark,
1888            details: Some(PaymentDetails::Spark {
1889                invoice_details: None,
1890                htlc_details: None,
1891                conversion_info: None,
1892            }),
1893        };
1894
1895        let lightning_payment = Payment {
1896            id: "lightning_1".to_string(),
1897            payment_type: PaymentType::Send,
1898            status: PaymentStatus::Completed,
1899            amount: 20_000,
1900            fees: 200,
1901            timestamp: 2000,
1902            method: PaymentMethod::Lightning,
1903            details: Some(PaymentDetails::Lightning {
1904                invoice: "lnbc1".to_string(),
1905                payment_hash: "hash1".to_string(),
1906                destination_pubkey: "pubkey1".to_string(),
1907                description: None,
1908                preimage: None,
1909                lnurl_pay_info: None,
1910                lnurl_withdraw_info: None,
1911                lnurl_receive_metadata: None,
1912            }),
1913        };
1914
1915        let token_payment = Payment {
1916            id: "token_1".to_string(),
1917            payment_type: PaymentType::Receive,
1918            status: PaymentStatus::Completed,
1919            amount: 30_000,
1920            fees: 300,
1921            timestamp: 3000,
1922            method: PaymentMethod::Token,
1923            details: Some(PaymentDetails::Token {
1924                metadata: TokenMetadata {
1925                    identifier: "token_id_1".to_string(),
1926                    issuer_public_key: "pubkey".to_string(),
1927                    name: "Token 1".to_string(),
1928                    ticker: "TK1".to_string(),
1929                    decimals: 8,
1930                    max_supply: 1_000_000,
1931                    is_freezable: false,
1932                },
1933                tx_hash: "tx_hash_1".to_string(),
1934                invoice_details: None,
1935                conversion_info: None,
1936            }),
1937        };
1938
1939        let withdraw_payment = Payment {
1940            id: "withdraw_1".to_string(),
1941            payment_type: PaymentType::Send,
1942            status: PaymentStatus::Completed,
1943            amount: 40_000,
1944            fees: 400,
1945            timestamp: 4000,
1946            method: PaymentMethod::Withdraw,
1947            details: Some(PaymentDetails::Withdraw {
1948                tx_id: "withdraw_tx_1".to_string(),
1949            }),
1950        };
1951
1952        let deposit_payment = Payment {
1953            id: "deposit_1".to_string(),
1954            payment_type: PaymentType::Receive,
1955            status: PaymentStatus::Completed,
1956            amount: 50_000,
1957            fees: 500,
1958            timestamp: 5000,
1959            method: PaymentMethod::Deposit,
1960            details: Some(PaymentDetails::Deposit {
1961                tx_id: "deposit_tx_1".to_string(),
1962            }),
1963        };
1964
1965        storage.insert_payment(spark_payment).await.unwrap();
1966        storage.insert_payment(lightning_payment).await.unwrap();
1967        storage.insert_payment(token_payment).await.unwrap();
1968        storage.insert_payment(withdraw_payment).await.unwrap();
1969        storage.insert_payment(deposit_payment).await.unwrap();
1970
1971        // Test filter by Bitcoin
1972        let spark_only = storage
1973            .list_payments(ListPaymentsRequest {
1974                asset_filter: Some(crate::AssetFilter::Bitcoin),
1975                ..Default::default()
1976            })
1977            .await
1978            .unwrap();
1979        assert_eq!(spark_only.len(), 4);
1980
1981        // Test filter by Token (no identifier)
1982        let token_only = storage
1983            .list_payments(ListPaymentsRequest {
1984                asset_filter: Some(crate::AssetFilter::Token {
1985                    token_identifier: None,
1986                }),
1987                ..Default::default()
1988            })
1989            .await
1990            .unwrap();
1991        assert_eq!(token_only.len(), 1);
1992        assert_eq!(token_only[0].id, "token_1");
1993
1994        // Test filter by Token with specific identifier
1995        let token_specific = storage
1996            .list_payments(ListPaymentsRequest {
1997                asset_filter: Some(crate::AssetFilter::Token {
1998                    token_identifier: Some("token_id_1".to_string()),
1999                }),
2000                ..Default::default()
2001            })
2002            .await
2003            .unwrap();
2004        assert_eq!(token_specific.len(), 1);
2005        assert_eq!(token_specific[0].id, "token_1");
2006
2007        // Test filter by Token with non-existent identifier
2008        let token_no_match = storage
2009            .list_payments(ListPaymentsRequest {
2010                asset_filter: Some(crate::AssetFilter::Token {
2011                    token_identifier: Some("nonexistent".to_string()),
2012                }),
2013                ..Default::default()
2014            })
2015            .await
2016            .unwrap();
2017        assert_eq!(token_no_match.len(), 0);
2018    }
2019
2020    #[allow(clippy::too_many_lines)]
2021    pub async fn test_spark_htlc_status_filtering(storage: Box<dyn Storage>) {
2022        // Create payments with different HTLC statuses
2023        let htlc_waiting = Payment {
2024            id: "htlc_waiting".to_string(),
2025            payment_type: PaymentType::Receive,
2026            status: PaymentStatus::Pending,
2027            amount: 10_000,
2028            fees: 0,
2029            timestamp: 1000,
2030            method: PaymentMethod::Spark,
2031            details: Some(PaymentDetails::Spark {
2032                invoice_details: None,
2033                htlc_details: Some(SparkHtlcDetails {
2034                    payment_hash: "hash1".to_string(),
2035                    preimage: None,
2036                    expiry_time: 2000,
2037                    status: SparkHtlcStatus::WaitingForPreimage,
2038                }),
2039                conversion_info: None,
2040            }),
2041        };
2042
2043        let htlc_shared = Payment {
2044            id: "htlc_shared".to_string(),
2045            payment_type: PaymentType::Receive,
2046            status: PaymentStatus::Completed,
2047            amount: 20_000,
2048            fees: 0,
2049            timestamp: 2000,
2050            method: PaymentMethod::Spark,
2051            details: Some(PaymentDetails::Spark {
2052                invoice_details: None,
2053                htlc_details: Some(SparkHtlcDetails {
2054                    payment_hash: "hash2".to_string(),
2055                    preimage: Some("preimage123".to_string()),
2056                    expiry_time: 3000,
2057                    status: SparkHtlcStatus::PreimageShared,
2058                }),
2059                conversion_info: None,
2060            }),
2061        };
2062
2063        let htlc_returned = Payment {
2064            id: "htlc_returned".to_string(),
2065            payment_type: PaymentType::Receive,
2066            status: PaymentStatus::Failed,
2067            amount: 30_000,
2068            fees: 0,
2069            timestamp: 3000,
2070            method: PaymentMethod::Spark,
2071            details: Some(PaymentDetails::Spark {
2072                invoice_details: None,
2073                htlc_details: Some(SparkHtlcDetails {
2074                    payment_hash: "hash3".to_string(),
2075                    preimage: None,
2076                    expiry_time: 4000,
2077                    status: SparkHtlcStatus::Returned,
2078                }),
2079                conversion_info: None,
2080            }),
2081        };
2082
2083        // Create a payment that is not HTLC-related
2084        let non_htlc_payment = Payment {
2085            id: "non_htlc".to_string(),
2086            payment_type: PaymentType::Send,
2087            status: PaymentStatus::Completed,
2088            amount: 40_000,
2089            fees: 100,
2090            timestamp: 4000,
2091            method: PaymentMethod::Spark,
2092            details: Some(PaymentDetails::Spark {
2093                invoice_details: Some(crate::SparkInvoicePaymentDetails {
2094                    description: Some("Test invoice".to_string()),
2095                    invoice: "spark_invoice".to_string(),
2096                }),
2097                htlc_details: None,
2098                conversion_info: None,
2099            }),
2100        };
2101
2102        // Insert all payments
2103        storage.insert_payment(htlc_waiting).await.unwrap();
2104        storage.insert_payment(htlc_shared).await.unwrap();
2105        storage.insert_payment(htlc_returned).await.unwrap();
2106        storage.insert_payment(non_htlc_payment).await.unwrap();
2107
2108        // Test filter for WaitingForPreimage
2109        let waiting_filter = storage
2110            .list_payments(ListPaymentsRequest {
2111                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2112                    htlc_status: Some(vec![SparkHtlcStatus::WaitingForPreimage]),
2113                    conversion_refund_needed: None,
2114                }]),
2115                ..Default::default()
2116            })
2117            .await
2118            .unwrap();
2119        assert_eq!(waiting_filter.len(), 1);
2120        assert_eq!(waiting_filter[0].id, "htlc_waiting");
2121
2122        // Test filter for PreimageShared
2123        let shared_filter = storage
2124            .list_payments(ListPaymentsRequest {
2125                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2126                    htlc_status: Some(vec![SparkHtlcStatus::PreimageShared]),
2127                    conversion_refund_needed: None,
2128                }]),
2129                ..Default::default()
2130            })
2131            .await
2132            .unwrap();
2133        assert_eq!(shared_filter.len(), 1);
2134        assert_eq!(shared_filter[0].id, "htlc_shared");
2135
2136        // Test filter for Returned
2137        let returned_filter = storage
2138            .list_payments(ListPaymentsRequest {
2139                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2140                    htlc_status: Some(vec![SparkHtlcStatus::Returned]),
2141                    conversion_refund_needed: None,
2142                }]),
2143                ..Default::default()
2144            })
2145            .await
2146            .unwrap();
2147        assert_eq!(returned_filter.len(), 1);
2148        assert_eq!(returned_filter[0].id, "htlc_returned");
2149
2150        // Test filter for multiple statuses (WaitingForPreimage and PreimageShared)
2151        let multiple_filter = storage
2152            .list_payments(ListPaymentsRequest {
2153                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2154                    htlc_status: Some(vec![
2155                        SparkHtlcStatus::WaitingForPreimage,
2156                        SparkHtlcStatus::PreimageShared,
2157                    ]),
2158                    conversion_refund_needed: None,
2159                }]),
2160                ..Default::default()
2161            })
2162            .await
2163            .unwrap();
2164        assert_eq!(multiple_filter.len(), 2);
2165        assert!(multiple_filter.iter().any(|p| p.id == "htlc_waiting"));
2166        assert!(multiple_filter.iter().any(|p| p.id == "htlc_shared"));
2167
2168        // Test that non-HTLC payment is not included in any HTLC status filter
2169        let all_htlc_filter = storage
2170            .list_payments(ListPaymentsRequest {
2171                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2172                    htlc_status: Some(vec![
2173                        SparkHtlcStatus::WaitingForPreimage,
2174                        SparkHtlcStatus::PreimageShared,
2175                        SparkHtlcStatus::Returned,
2176                    ]),
2177                    conversion_refund_needed: None,
2178                }]),
2179                ..Default::default()
2180            })
2181            .await
2182            .unwrap();
2183        assert_eq!(all_htlc_filter.len(), 3);
2184        assert!(all_htlc_filter.iter().all(|p| p.id != "non_htlc"));
2185    }
2186
2187    #[allow(clippy::too_many_lines)]
2188    pub async fn test_conversion_refund_needed_filtering(storage: Box<dyn Storage>) {
2189        // Create payments with and without conversion info
2190        let payment_with_refund_metadata = PaymentMetadata {
2191            conversion_info: Some(crate::ConversionInfo {
2192                pool_id: "pool1".to_string(),
2193                conversion_id: "with_refund".to_string(),
2194                status: crate::ConversionStatus::Refunded,
2195                fee: None,
2196                purpose: None,
2197            }),
2198            ..Default::default()
2199        };
2200        let payment_with_refund = Payment {
2201            id: "with_refund".to_string(),
2202            payment_type: PaymentType::Send,
2203            status: PaymentStatus::Completed,
2204            amount: 10_000_000,
2205            fees: 0,
2206            timestamp: 1000,
2207            method: PaymentMethod::Token,
2208            details: Some(PaymentDetails::Token {
2209                metadata: crate::TokenMetadata {
2210                    identifier: "token1".to_string(),
2211                    issuer_public_key: "pubkey1".to_string(),
2212                    name: "Test Token".to_string(),
2213                    ticker: "TTK".to_string(),
2214                    decimals: 8,
2215                    max_supply: 1_000_000_000,
2216                    is_freezable: false,
2217                },
2218                tx_hash: "txhash1".to_string(),
2219                invoice_details: None,
2220                conversion_info: None,
2221            }),
2222        };
2223
2224        let successful_conversion_metadata = PaymentMetadata {
2225            conversion_info: Some(crate::ConversionInfo {
2226                pool_id: "pool1".to_string(),
2227                conversion_id: "successful_conversion".to_string(),
2228                status: crate::ConversionStatus::Completed,
2229                fee: Some(100),
2230                purpose: None,
2231            }),
2232            ..Default::default()
2233        };
2234        let successful_conversion = Payment {
2235            id: "successful_conversion".to_string(),
2236            payment_type: PaymentType::Send,
2237            status: PaymentStatus::Completed,
2238            amount: 20_000,
2239            fees: 0,
2240            timestamp: 2000,
2241            method: PaymentMethod::Spark,
2242            details: Some(PaymentDetails::Spark {
2243                invoice_details: None,
2244                htlc_details: None,
2245                conversion_info: None,
2246            }),
2247        };
2248
2249        let payment_without_refund_metadata = PaymentMetadata {
2250            conversion_info: Some(crate::ConversionInfo {
2251                pool_id: "pool1".to_string(),
2252                conversion_id: "without_refund".to_string(),
2253                status: crate::ConversionStatus::RefundNeeded,
2254                fee: None,
2255                purpose: None,
2256            }),
2257            ..Default::default()
2258        };
2259        let payment_without_refund = Payment {
2260            id: "without_refund".to_string(),
2261            payment_type: PaymentType::Send,
2262            status: PaymentStatus::Completed,
2263            amount: 10_000,
2264            fees: 0,
2265            timestamp: 3000,
2266            method: PaymentMethod::Spark,
2267            details: Some(PaymentDetails::Spark {
2268                invoice_details: None,
2269                htlc_details: None,
2270                conversion_info: None,
2271            }),
2272        };
2273
2274        storage.insert_payment(payment_with_refund).await.unwrap();
2275        storage.insert_payment(successful_conversion).await.unwrap();
2276        storage
2277            .insert_payment(payment_without_refund)
2278            .await
2279            .unwrap();
2280        storage
2281            .set_payment_metadata("with_refund".to_string(), payment_with_refund_metadata)
2282            .await
2283            .unwrap();
2284        storage
2285            .set_payment_metadata(
2286                "successful_conversion".to_string(),
2287                successful_conversion_metadata,
2288            )
2289            .await
2290            .unwrap();
2291        storage
2292            .set_payment_metadata(
2293                "without_refund".to_string(),
2294                payment_without_refund_metadata,
2295            )
2296            .await
2297            .unwrap();
2298
2299        let payments = storage
2300            .list_payments(ListPaymentsRequest::default())
2301            .await
2302            .unwrap();
2303        assert_eq!(payments.len(), 3);
2304
2305        // Test filter for payments missing transfer refund info
2306        let missing_refund_filter = storage
2307            .list_payments(ListPaymentsRequest {
2308                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2309                    htlc_status: None,
2310                    conversion_refund_needed: Some(true),
2311                }]),
2312                ..Default::default()
2313            })
2314            .await
2315            .unwrap();
2316        assert_eq!(missing_refund_filter.len(), 1);
2317        assert_eq!(missing_refund_filter[0].id, "without_refund");
2318
2319        // Test filter for payments with transfer refund info present
2320        let present_refund_filter = storage
2321            .list_payments(ListPaymentsRequest {
2322                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Token {
2323                    conversion_refund_needed: Some(false),
2324                    tx_hash: None,
2325                }]),
2326                ..Default::default()
2327            })
2328            .await
2329            .unwrap();
2330        assert_eq!(present_refund_filter.len(), 1);
2331        assert_eq!(present_refund_filter[0].id, "with_refund");
2332
2333        // Test multiple payment detail filters
2334        let multiple_filters = storage
2335            .list_payments(ListPaymentsRequest {
2336                payment_details_filter: Some(vec![
2337                    crate::PaymentDetailsFilter::Spark {
2338                        htlc_status: None,
2339                        conversion_refund_needed: Some(true),
2340                    },
2341                    crate::PaymentDetailsFilter::Token {
2342                        conversion_refund_needed: Some(false),
2343                        tx_hash: None,
2344                    },
2345                ]),
2346                ..Default::default()
2347            })
2348            .await
2349            .unwrap();
2350        assert_eq!(multiple_filters.len(), 2);
2351
2352        // Test filter for token payments missing transfer refund info
2353        let token_no_refund_filter = storage
2354            .list_payments(ListPaymentsRequest {
2355                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Token {
2356                    conversion_refund_needed: Some(true),
2357                    tx_hash: None,
2358                }]),
2359                ..Default::default()
2360            })
2361            .await
2362            .unwrap();
2363        assert_eq!(token_no_refund_filter.len(), 0);
2364
2365        // Test filter for spark payments with transfer refund info present
2366        let spark_with_refund_filter = storage
2367            .list_payments(ListPaymentsRequest {
2368                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2369                    htlc_status: None,
2370                    conversion_refund_needed: Some(false),
2371                }]),
2372                ..Default::default()
2373            })
2374            .await
2375            .unwrap();
2376        assert_eq!(spark_with_refund_filter.len(), 1);
2377
2378        // Test filter for all payments regardless of transfer refund info
2379        let all_payments_filter = storage
2380            .list_payments(ListPaymentsRequest {
2381                payment_details_filter: Some(vec![crate::PaymentDetailsFilter::Spark {
2382                    htlc_status: None,
2383                    conversion_refund_needed: None,
2384                }]),
2385                ..Default::default()
2386            })
2387            .await
2388            .unwrap();
2389        assert_eq!(all_payments_filter.len(), 3);
2390    }
2391
2392    pub async fn test_timestamp_filtering(storage: Box<dyn Storage>) {
2393        // Create payments at different timestamps
2394        let payment1 = Payment {
2395            id: "ts_1000".to_string(),
2396            payment_type: PaymentType::Send,
2397            status: PaymentStatus::Completed,
2398            amount: 10_000,
2399            fees: 100,
2400            timestamp: 1000,
2401            method: PaymentMethod::Spark,
2402            details: Some(PaymentDetails::Spark {
2403                invoice_details: None,
2404                htlc_details: None,
2405                conversion_info: None,
2406            }),
2407        };
2408
2409        let payment2 = Payment {
2410            id: "ts_2000".to_string(),
2411            payment_type: PaymentType::Send,
2412            status: PaymentStatus::Completed,
2413            amount: 20_000,
2414            fees: 200,
2415            timestamp: 2000,
2416            method: PaymentMethod::Spark,
2417            details: Some(PaymentDetails::Spark {
2418                invoice_details: None,
2419                htlc_details: None,
2420                conversion_info: None,
2421            }),
2422        };
2423
2424        let payment3 = Payment {
2425            id: "ts_3000".to_string(),
2426            payment_type: PaymentType::Send,
2427            status: PaymentStatus::Completed,
2428            amount: 30_000,
2429            fees: 300,
2430            timestamp: 3000,
2431            method: PaymentMethod::Spark,
2432            details: Some(PaymentDetails::Spark {
2433                invoice_details: None,
2434                htlc_details: None,
2435                conversion_info: None,
2436            }),
2437        };
2438
2439        storage.insert_payment(payment1).await.unwrap();
2440        storage.insert_payment(payment2).await.unwrap();
2441        storage.insert_payment(payment3).await.unwrap();
2442
2443        // Test filter by from_timestamp
2444        let from_2000 = storage
2445            .list_payments(ListPaymentsRequest {
2446                from_timestamp: Some(2000),
2447                ..Default::default()
2448            })
2449            .await
2450            .unwrap();
2451        assert_eq!(from_2000.len(), 2);
2452        assert!(from_2000.iter().any(|p| p.id == "ts_2000"));
2453        assert!(from_2000.iter().any(|p| p.id == "ts_3000"));
2454
2455        // Test filter by to_timestamp
2456        let to_2000 = storage
2457            .list_payments(ListPaymentsRequest {
2458                to_timestamp: Some(2000),
2459                ..Default::default()
2460            })
2461            .await
2462            .unwrap();
2463        assert_eq!(to_2000.len(), 1);
2464        assert!(to_2000.iter().any(|p| p.id == "ts_1000"));
2465
2466        // Test filter by both from_timestamp and to_timestamp
2467        let range = storage
2468            .list_payments(ListPaymentsRequest {
2469                from_timestamp: Some(1500),
2470                to_timestamp: Some(2500),
2471                ..Default::default()
2472            })
2473            .await
2474            .unwrap();
2475        assert_eq!(range.len(), 1);
2476        assert_eq!(range[0].id, "ts_2000");
2477    }
2478
2479    pub async fn test_combined_filters(storage: Box<dyn Storage>) {
2480        // Create diverse test payments
2481        let payment1 = Payment {
2482            id: "combined_1".to_string(),
2483            payment_type: PaymentType::Send,
2484            status: PaymentStatus::Completed,
2485            amount: 10_000,
2486            fees: 100,
2487            timestamp: 1000,
2488            method: PaymentMethod::Spark,
2489            details: Some(PaymentDetails::Spark {
2490                invoice_details: None,
2491                htlc_details: None,
2492                conversion_info: None,
2493            }),
2494        };
2495
2496        let payment2 = Payment {
2497            id: "combined_2".to_string(),
2498            payment_type: PaymentType::Send,
2499            status: PaymentStatus::Pending,
2500            amount: 20_000,
2501            fees: 200,
2502            timestamp: 2000,
2503            method: PaymentMethod::Lightning,
2504            details: Some(PaymentDetails::Lightning {
2505                invoice: "lnbc1".to_string(),
2506                payment_hash: "hash1".to_string(),
2507                destination_pubkey: "pubkey1".to_string(),
2508                description: None,
2509                preimage: None,
2510                lnurl_pay_info: None,
2511                lnurl_withdraw_info: None,
2512                lnurl_receive_metadata: None,
2513            }),
2514        };
2515
2516        let payment3 = Payment {
2517            id: "combined_3".to_string(),
2518            payment_type: PaymentType::Receive,
2519            status: PaymentStatus::Completed,
2520            amount: 30_000,
2521            fees: 300,
2522            timestamp: 3000,
2523            method: PaymentMethod::Lightning,
2524            details: Some(PaymentDetails::Lightning {
2525                invoice: "lnbc2".to_string(),
2526                payment_hash: "hash2".to_string(),
2527                destination_pubkey: "pubkey2".to_string(),
2528                description: None,
2529                preimage: None,
2530                lnurl_pay_info: None,
2531                lnurl_withdraw_info: None,
2532                lnurl_receive_metadata: None,
2533            }),
2534        };
2535
2536        storage.insert_payment(payment1).await.unwrap();
2537        storage.insert_payment(payment2).await.unwrap();
2538        storage.insert_payment(payment3).await.unwrap();
2539
2540        // Test: Send + Completed
2541        let send_completed = storage
2542            .list_payments(ListPaymentsRequest {
2543                type_filter: Some(vec![PaymentType::Send]),
2544                status_filter: Some(vec![PaymentStatus::Completed]),
2545                ..Default::default()
2546            })
2547            .await
2548            .unwrap();
2549        assert_eq!(send_completed.len(), 1);
2550        assert_eq!(send_completed[0].id, "combined_1");
2551
2552        // Test: Bitcoin + timestamp range
2553        let bitcoin_recent = storage
2554            .list_payments(ListPaymentsRequest {
2555                asset_filter: Some(crate::AssetFilter::Bitcoin),
2556                from_timestamp: Some(2500),
2557                ..Default::default()
2558            })
2559            .await
2560            .unwrap();
2561        assert_eq!(bitcoin_recent.len(), 1);
2562        assert_eq!(bitcoin_recent[0].id, "combined_3");
2563
2564        // Test: Type + Status + Asset
2565        let send_pending_bitcoin = storage
2566            .list_payments(ListPaymentsRequest {
2567                type_filter: Some(vec![PaymentType::Send]),
2568                status_filter: Some(vec![PaymentStatus::Pending]),
2569                asset_filter: Some(crate::AssetFilter::Bitcoin),
2570                ..Default::default()
2571            })
2572            .await
2573            .unwrap();
2574        assert_eq!(send_pending_bitcoin.len(), 1);
2575        assert_eq!(send_pending_bitcoin[0].id, "combined_2");
2576    }
2577
2578    pub async fn test_sort_order(storage: Box<dyn Storage>) {
2579        // Create payments at different timestamps
2580        let payment1 = Payment {
2581            id: "sort_1".to_string(),
2582            payment_type: PaymentType::Send,
2583            status: PaymentStatus::Completed,
2584            amount: 10_000,
2585            fees: 100,
2586            timestamp: 1000,
2587            method: PaymentMethod::Spark,
2588            details: Some(PaymentDetails::Spark {
2589                invoice_details: None,
2590                htlc_details: None,
2591                conversion_info: None,
2592            }),
2593        };
2594
2595        let payment2 = Payment {
2596            id: "sort_2".to_string(),
2597            payment_type: PaymentType::Send,
2598            status: PaymentStatus::Completed,
2599            amount: 20_000,
2600            fees: 200,
2601            timestamp: 2000,
2602            method: PaymentMethod::Spark,
2603            details: Some(PaymentDetails::Spark {
2604                invoice_details: None,
2605                htlc_details: None,
2606                conversion_info: None,
2607            }),
2608        };
2609
2610        let payment3 = Payment {
2611            id: "sort_3".to_string(),
2612            payment_type: PaymentType::Send,
2613            status: PaymentStatus::Completed,
2614            amount: 30_000,
2615            fees: 300,
2616            timestamp: 3000,
2617            method: PaymentMethod::Spark,
2618            details: Some(PaymentDetails::Spark {
2619                invoice_details: None,
2620                htlc_details: None,
2621                conversion_info: None,
2622            }),
2623        };
2624
2625        storage.insert_payment(payment1).await.unwrap();
2626        storage.insert_payment(payment2).await.unwrap();
2627        storage.insert_payment(payment3).await.unwrap();
2628
2629        // Test default sort (descending by timestamp)
2630        let desc_payments = storage
2631            .list_payments(ListPaymentsRequest::default())
2632            .await
2633            .unwrap();
2634        assert_eq!(desc_payments.len(), 3);
2635        assert_eq!(desc_payments[0].id, "sort_3"); // Most recent first
2636        assert_eq!(desc_payments[1].id, "sort_2");
2637        assert_eq!(desc_payments[2].id, "sort_1");
2638
2639        // Test ascending sort
2640        let asc_payments = storage
2641            .list_payments(ListPaymentsRequest {
2642                sort_ascending: Some(true),
2643                ..Default::default()
2644            })
2645            .await
2646            .unwrap();
2647        assert_eq!(asc_payments.len(), 3);
2648        assert_eq!(asc_payments[0].id, "sort_1"); // Oldest first
2649        assert_eq!(asc_payments[1].id, "sort_2");
2650        assert_eq!(asc_payments[2].id, "sort_3");
2651
2652        // Test explicit descending sort
2653        let desc_explicit = storage
2654            .list_payments(ListPaymentsRequest {
2655                sort_ascending: Some(false),
2656                ..Default::default()
2657            })
2658            .await
2659            .unwrap();
2660        assert_eq!(desc_explicit.len(), 3);
2661        assert_eq!(desc_explicit[0].id, "sort_3");
2662        assert_eq!(desc_explicit[1].id, "sort_2");
2663        assert_eq!(desc_explicit[2].id, "sort_1");
2664    }
2665
2666    pub async fn test_payment_metadata(storage: Box<dyn Storage>) {
2667        let cache = ObjectCacheRepository::new(storage.into());
2668
2669        // Prepare test data
2670        let payment_request1 = "pr1".to_string();
2671        let metadata1 = PaymentMetadata {
2672            lnurl_description: Some("desc1".to_string()),
2673            lnurl_withdraw_info: Some(LnurlWithdrawInfo {
2674                withdraw_url: "https://callback.url".to_string(),
2675            }),
2676            ..Default::default()
2677        };
2678
2679        let payment_request2 = "pr2".to_string();
2680        let metadata2 = PaymentMetadata {
2681            lnurl_description: Some("desc2".to_string()),
2682            lnurl_withdraw_info: Some(LnurlWithdrawInfo {
2683                withdraw_url: "https://callback2.url".to_string(),
2684            }),
2685            ..Default::default()
2686        };
2687
2688        // set_payment_request_metadata
2689        cache
2690            .save_payment_metadata(&payment_request1, &metadata1)
2691            .await
2692            .unwrap();
2693        cache
2694            .save_payment_metadata(&payment_request2, &metadata2)
2695            .await
2696            .unwrap();
2697
2698        // get_payment_request_metadata
2699        let fetched1 = cache
2700            .fetch_payment_metadata(&payment_request1)
2701            .await
2702            .unwrap();
2703        assert!(fetched1.is_some());
2704        let fetched1 = fetched1.unwrap();
2705        assert_eq!(fetched1.lnurl_description.unwrap(), "desc1");
2706        assert_eq!(
2707            fetched1.lnurl_withdraw_info.unwrap().withdraw_url,
2708            "https://callback.url"
2709        );
2710
2711        let fetched2 = cache
2712            .fetch_payment_metadata(&payment_request2)
2713            .await
2714            .unwrap();
2715        assert!(fetched2.is_some());
2716
2717        // delete_payment_request_metadata
2718        cache
2719            .delete_payment_metadata(&payment_request1)
2720            .await
2721            .unwrap();
2722        let deleted = cache
2723            .fetch_payment_metadata(&payment_request1)
2724            .await
2725            .unwrap();
2726        assert!(deleted.is_none());
2727    }
2728
2729    pub async fn test_payment_details_update_persistence(storage: Box<dyn Storage>) {
2730        // Create a payment with incomplete details
2731        let mut payment = Payment {
2732            id: "payment_1".to_string(),
2733            payment_type: PaymentType::Send,
2734            status: PaymentStatus::Pending,
2735            amount: 15_000,
2736            fees: 150,
2737            timestamp: 1_234_567_890,
2738            method: PaymentMethod::Lightning,
2739            details: Some(PaymentDetails::Spark {
2740                invoice_details: None,
2741                htlc_details: Some(SparkHtlcDetails {
2742                    payment_hash: "hash_123".to_string(),
2743                    preimage: None,
2744                    expiry_time: 1_234_567_990,
2745                    status: SparkHtlcStatus::WaitingForPreimage,
2746                }),
2747                conversion_info: None,
2748            }),
2749        };
2750
2751        // Insert the payment into storage
2752        storage.insert_payment(payment.clone()).await.unwrap();
2753
2754        // Simulate payment completion by updating status
2755        payment.status = PaymentStatus::Completed;
2756        storage.insert_payment(payment.clone()).await.unwrap();
2757
2758        // Check the payment details
2759        let updated_payment = storage
2760            .get_payment_by_id("payment_1".to_string())
2761            .await
2762            .unwrap();
2763        assert_eq!(updated_payment.status, PaymentStatus::Completed);
2764        let Some(PaymentDetails::Spark { htlc_details, .. }) = &updated_payment.details else {
2765            panic!("Payment details are not of Spark type");
2766        };
2767        assert_eq!(
2768            htlc_details.as_ref().unwrap().status,
2769            SparkHtlcStatus::WaitingForPreimage
2770        );
2771
2772        // Now, update the payment details
2773        payment.details = Some(PaymentDetails::Spark {
2774            invoice_details: None,
2775            htlc_details: Some(SparkHtlcDetails {
2776                payment_hash: "hash_123".to_string(),
2777                preimage: Some("preimage_123".to_string()),
2778                expiry_time: 1_234_567_990,
2779                status: SparkHtlcStatus::PreimageShared,
2780            }),
2781            conversion_info: None,
2782        });
2783        storage.insert_payment(payment.clone()).await.unwrap();
2784
2785        // Check the updated payment details
2786        let updated_payment = storage
2787            .get_payment_by_id("payment_1".to_string())
2788            .await
2789            .unwrap();
2790        let Some(PaymentDetails::Spark { htlc_details, .. }) = &updated_payment.details else {
2791            panic!("Payment details are not of Spark type");
2792        };
2793        assert_eq!(
2794            htlc_details.as_ref().unwrap().status,
2795            SparkHtlcStatus::PreimageShared
2796        );
2797        assert_eq!(
2798            htlc_details.as_ref().unwrap().preimage.as_ref().unwrap(),
2799            "preimage_123"
2800        );
2801    }
2802}