Skip to content

Commit 9cce048

Browse files
committed
Add unit tests for PaymentMetadataStore
Add comprehensive tests covering: - Serialization roundtrip for `LSPFeeLimits` variant - Basic insert / get / remove lifecycle - Reverse index correctness with multiple payment IDs - `add_payment_id` adds associations correctly - `get_lsp_fee_limits_for_payment_id` returns correct limits - `remove_payment_id` cleans up the reverse index - Persistence roundtrip (insert, reconstruct from same KVStore) Generated with the help of AI (Claude Code). Co-Authored-By: HAL 9000
1 parent d5e604a commit 9cce048

File tree

1 file changed

+248
-1
lines changed

1 file changed

+248
-1
lines changed

src/payment/metadata_store.rs

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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;
2525
use crate::payment::store::LSPFeeLimits;
2626
use crate::types::DynStore;
2727
use 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

Comments
 (0)