@@ -21,7 +21,7 @@ use crate::io::{
2121 PAYMENT_METADATA_PERSISTENCE_PRIMARY_NAMESPACE ,
2222 PAYMENT_METADATA_PERSISTENCE_SECONDARY_NAMESPACE ,
2323} ;
24- use crate :: logger:: { log_error , LdkLogger , Logger } ;
24+ use crate :: logger:: Logger ;
2525use crate :: payment:: store:: LSPFeeLimits ;
2626use crate :: types:: DynStore ;
2727use crate :: Error ;
@@ -266,3 +266,250 @@ impl PaymentMetadataStore {
266266 Ok ( ( ) )
267267 }
268268}
269+
270+ #[ cfg( test) ]
271+ mod tests {
272+ use lightning:: util:: ser:: { Readable , Writeable } ;
273+ use lightning:: util:: test_utils:: TestLogger ;
274+
275+ use super :: * ;
276+ use crate :: io:: test_utils:: InMemoryStore ;
277+ use crate :: types:: DynStoreWrapper ;
278+
279+ fn make_store ( ) -> ( Arc < DynStore > , Arc < Logger > ) {
280+ let kv_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
281+ let logger = Arc :: new ( Logger :: new_log_facade ( ) ) ;
282+ ( kv_store, logger)
283+ }
284+
285+ fn make_metadata_id ( val : u8 ) -> MetadataId {
286+ MetadataId { id : [ val; 32 ] }
287+ }
288+
289+ fn make_payment_id ( val : u8 ) -> PaymentId {
290+ PaymentId ( [ val; 32 ] )
291+ }
292+
293+ #[ test]
294+ fn serialization_roundtrip_lsp_fee_limits ( ) {
295+ let limits = LSPFeeLimits {
296+ max_total_opening_fee_msat : Some ( 1000 ) ,
297+ max_proportional_opening_fee_ppm_msat : Some ( 500 ) ,
298+ } ;
299+ let kind = PaymentMetadataKind :: LSPFeeLimits { limits } ;
300+ let entry = PaymentMetadataEntry {
301+ id : make_metadata_id ( 1 ) ,
302+ kind,
303+ payment_ids : vec ! [ make_payment_id( 10 ) , make_payment_id( 11 ) ] ,
304+ } ;
305+
306+ let encoded = entry. encode ( ) ;
307+ let decoded = PaymentMetadataEntry :: read ( & mut & * encoded) . unwrap ( ) ;
308+
309+ assert_eq ! ( entry. id, decoded. id) ;
310+ assert_eq ! ( entry. payment_ids, decoded. payment_ids) ;
311+ match decoded. kind {
312+ PaymentMetadataKind :: LSPFeeLimits { limits : decoded_limits } => {
313+ assert_eq ! ( limits, decoded_limits) ;
314+ } ,
315+ _ => panic ! ( "Expected LSPFeeLimits variant" ) ,
316+ }
317+ }
318+
319+ #[ test]
320+ fn insert_get_remove_lifecycle ( ) {
321+ let ( kv_store, logger) = make_store ( ) ;
322+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , kv_store, logger) ;
323+
324+ let mid = make_metadata_id ( 1 ) ;
325+ let pid = make_payment_id ( 10 ) ;
326+ let limits = LSPFeeLimits {
327+ max_total_opening_fee_msat : Some ( 2000 ) ,
328+ max_proportional_opening_fee_ppm_msat : None ,
329+ } ;
330+ let entry = PaymentMetadataEntry {
331+ id : mid,
332+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
333+ payment_ids : vec ! [ pid] ,
334+ } ;
335+
336+ // Insert
337+ let returned_id = store. insert ( entry) . unwrap ( ) ;
338+ assert_eq ! ( returned_id, mid) ;
339+
340+ // Get
341+ let retrieved = store. get ( & mid) . unwrap ( ) ;
342+ assert_eq ! ( retrieved. id, mid) ;
343+ assert_eq ! ( retrieved. payment_ids, vec![ pid] ) ;
344+
345+ // Get by payment ID
346+ let entries = store. get_for_payment_id ( & pid) ;
347+ assert_eq ! ( entries. len( ) , 1 ) ;
348+ assert_eq ! ( entries[ 0 ] . id, mid) ;
349+
350+ // Remove
351+ store. remove ( & mid) . unwrap ( ) ;
352+ assert ! ( store. get( & mid) . is_none( ) ) ;
353+ assert ! ( store. get_for_payment_id( & pid) . is_empty( ) ) ;
354+ }
355+
356+ #[ test]
357+ fn reverse_index_multiple_payment_ids ( ) {
358+ let ( kv_store, logger) = make_store ( ) ;
359+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , kv_store, logger) ;
360+
361+ let mid = make_metadata_id ( 1 ) ;
362+ let pid1 = make_payment_id ( 10 ) ;
363+ let pid2 = make_payment_id ( 11 ) ;
364+ let limits = LSPFeeLimits {
365+ max_total_opening_fee_msat : Some ( 3000 ) ,
366+ max_proportional_opening_fee_ppm_msat : None ,
367+ } ;
368+ let entry = PaymentMetadataEntry {
369+ id : mid,
370+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
371+ payment_ids : vec ! [ pid1, pid2] ,
372+ } ;
373+
374+ store. insert ( entry) . unwrap ( ) ;
375+
376+ // Both payment IDs should find the entry
377+ assert_eq ! ( store. get_for_payment_id( & pid1) . len( ) , 1 ) ;
378+ assert_eq ! ( store. get_for_payment_id( & pid2) . len( ) , 1 ) ;
379+
380+ // Non-existent payment ID
381+ let pid3 = make_payment_id ( 99 ) ;
382+ assert ! ( store. get_for_payment_id( & pid3) . is_empty( ) ) ;
383+ }
384+
385+ #[ test]
386+ fn add_payment_id_updates_reverse_index ( ) {
387+ let ( kv_store, logger) = make_store ( ) ;
388+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , kv_store, logger) ;
389+
390+ let mid = make_metadata_id ( 1 ) ;
391+ let pid1 = make_payment_id ( 10 ) ;
392+ let limits = LSPFeeLimits {
393+ max_total_opening_fee_msat : Some ( 4000 ) ,
394+ max_proportional_opening_fee_ppm_msat : None ,
395+ } ;
396+ let entry = PaymentMetadataEntry {
397+ id : mid,
398+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
399+ payment_ids : vec ! [ pid1] ,
400+ } ;
401+
402+ store. insert ( entry) . unwrap ( ) ;
403+
404+ let pid2 = make_payment_id ( 11 ) ;
405+ store. add_payment_id ( mid, pid2) . unwrap ( ) ;
406+
407+ // Both should now resolve
408+ assert_eq ! ( store. get_for_payment_id( & pid1) . len( ) , 1 ) ;
409+ assert_eq ! ( store. get_for_payment_id( & pid2) . len( ) , 1 ) ;
410+
411+ // The entry itself should have both payment IDs
412+ let retrieved = store. get ( & mid) . unwrap ( ) ;
413+ assert_eq ! ( retrieved. payment_ids. len( ) , 2 ) ;
414+ assert ! ( retrieved. payment_ids. contains( & pid1) ) ;
415+ assert ! ( retrieved. payment_ids. contains( & pid2) ) ;
416+ }
417+
418+ #[ test]
419+ fn get_lsp_fee_limits_for_payment_id ( ) {
420+ let ( kv_store, logger) = make_store ( ) ;
421+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , kv_store, logger) ;
422+
423+ let mid = make_metadata_id ( 1 ) ;
424+ let pid = make_payment_id ( 10 ) ;
425+ let limits = LSPFeeLimits {
426+ max_total_opening_fee_msat : Some ( 5000 ) ,
427+ max_proportional_opening_fee_ppm_msat : Some ( 100 ) ,
428+ } ;
429+ let entry = PaymentMetadataEntry {
430+ id : mid,
431+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
432+ payment_ids : vec ! [ pid] ,
433+ } ;
434+
435+ store. insert ( entry) . unwrap ( ) ;
436+
437+ let retrieved_limits = store. get_lsp_fee_limits_for_payment_id ( & pid) . unwrap ( ) ;
438+ assert_eq ! ( retrieved_limits, limits) ;
439+
440+ // Non-existent payment ID
441+ let pid2 = make_payment_id ( 99 ) ;
442+ assert ! ( store. get_lsp_fee_limits_for_payment_id( & pid2) . is_none( ) ) ;
443+ }
444+
445+ #[ test]
446+ fn remove_payment_id_cleans_reverse_index ( ) {
447+ let ( kv_store, logger) = make_store ( ) ;
448+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , kv_store, logger) ;
449+
450+ let mid = make_metadata_id ( 1 ) ;
451+ let pid1 = make_payment_id ( 10 ) ;
452+ let pid2 = make_payment_id ( 11 ) ;
453+ let limits = LSPFeeLimits {
454+ max_total_opening_fee_msat : Some ( 6000 ) ,
455+ max_proportional_opening_fee_ppm_msat : None ,
456+ } ;
457+ let entry = PaymentMetadataEntry {
458+ id : mid,
459+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
460+ payment_ids : vec ! [ pid1, pid2] ,
461+ } ;
462+
463+ store. insert ( entry) . unwrap ( ) ;
464+
465+ // Remove pid1
466+ store. remove_payment_id ( & pid1) . unwrap ( ) ;
467+
468+ // pid1 should no longer resolve
469+ assert ! ( store. get_for_payment_id( & pid1) . is_empty( ) ) ;
470+
471+ // pid2 should still resolve
472+ assert_eq ! ( store. get_for_payment_id( & pid2) . len( ) , 1 ) ;
473+
474+ // The entry should still exist but with only pid2
475+ let retrieved = store. get ( & mid) . unwrap ( ) ;
476+ assert_eq ! ( retrieved. payment_ids, vec![ pid2] ) ;
477+ }
478+
479+ #[ test]
480+ fn persistence_roundtrip ( ) {
481+ let kv_store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
482+
483+ let mid = make_metadata_id ( 1 ) ;
484+ let pid = make_payment_id ( 10 ) ;
485+ let limits = LSPFeeLimits {
486+ max_total_opening_fee_msat : Some ( 7000 ) ,
487+ max_proportional_opening_fee_ppm_msat : None ,
488+ } ;
489+ let entry = PaymentMetadataEntry {
490+ id : mid,
491+ kind : PaymentMetadataKind :: LSPFeeLimits { limits } ,
492+ payment_ids : vec ! [ pid] ,
493+ } ;
494+
495+ // Insert via first store instance
496+ {
497+ let logger = Arc :: new ( Logger :: new_log_facade ( ) ) ;
498+ let store = PaymentMetadataStore :: new ( Vec :: new ( ) , Arc :: clone ( & kv_store) , logger) ;
499+ store. insert ( entry. clone ( ) ) . unwrap ( ) ;
500+ }
501+
502+ // Reconstruct from same KVStore — simulate reading persisted entries
503+ {
504+ let logger = Arc :: new ( Logger :: new_log_facade ( ) ) ;
505+ let store = PaymentMetadataStore :: new ( vec ! [ entry] , Arc :: clone ( & kv_store) , logger) ;
506+ let retrieved = store. get ( & mid) . unwrap ( ) ;
507+ assert_eq ! ( retrieved. id, mid) ;
508+ assert_eq ! ( retrieved. payment_ids, vec![ pid] ) ;
509+
510+ // Reverse index should work
511+ let entries = store. get_for_payment_id ( & pid) ;
512+ assert_eq ! ( entries. len( ) , 1 ) ;
513+ }
514+ }
515+ }
0 commit comments