Skip to content

Commit f75e730

Browse files
committed
Add gossip test suite
Refactor gossip tests into nostr-gossip-test-suite crate Pull-Request: #1129 Signed-off-by: Yuki Kishimoto <[email protected]>
1 parent 5f0c748 commit f75e730

File tree

8 files changed

+260
-220
lines changed

8 files changed

+260
-220
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- nostr-database
3838
- nostr-gossip
3939
- nostr-gossip-memory
40+
- nostr-gossip-test-suite
4041
- nostr-lmdb
4142
- nostr-indexeddb --target wasm32-unknown-unknown
4243
- nostr-ndb

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
# Gossip
1212
"gossip/nostr-gossip",
1313
"gossip/nostr-gossip-memory",
14+
"gossip/nostr-gossip-test-suite",
1415

1516
# Remote File Storage implementations
1617
"rfs/nostr-blossom",
@@ -45,6 +46,7 @@ nostr-connect = { version = "0.44", path = "./signer/nostr-connect", default-fea
4546
nostr-database = { version = "0.44", path = "./database/nostr-database", default-features = false }
4647
nostr-gossip = { version = "0.44", path = "./gossip/nostr-gossip", default-features = false }
4748
nostr-gossip-memory = { version = "0.44", path = "./gossip/nostr-gossip-memory", default-features = false }
49+
nostr-gossip-test-suite = { path = "./gossip/nostr-gossip-test-suite" }
4850
nostr-lmdb = { version = "0.44", path = "./database/nostr-lmdb", default-features = false }
4951
nostr-ndb = { version = "0.44", path = "./database/nostr-ndb", default-features = false }
5052
nostr-relay-builder = { version = "0.44", path = "./crates/nostr-relay-builder", default-features = false }

contrib/scripts/check-crates.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ buildargs=(
3636
"-p nostr-database"
3737
"-p nostr-gossip"
3838
"-p nostr-gossip-memory"
39+
"-p nostr-gossip-test-suite"
3940
"-p nostr-lmdb"
4041
"-p nostr-indexeddb --target wasm32-unknown-unknown"
4142
"-p nostr-ndb"

gossip/nostr-gossip-memory/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ nostr-gossip.workspace = true
1919
tokio = { workspace = true, features = ["sync"] }
2020

2121
[dev-dependencies]
22-
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
22+
nostr-gossip-test-suite.workspace = true

gossip/nostr-gossip-memory/src/store.rs

Lines changed: 4 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -378,228 +378,13 @@ impl NostrGossip for NostrGossipMemory {
378378

379379
#[cfg(test)]
380380
mod tests {
381-
use nostr::{EventBuilder, JsonUtil, Keys, Tag};
381+
use nostr_gossip_test_suite::gossip_unit_tests;
382382

383383
use super::*;
384384

385-
#[tokio::test]
386-
async fn test_process_event() {
387-
let store = NostrGossipMemory::unbounded();
388-
389-
let json = r#"{"id":"b7b1fb52ad8461a03e949820ae29a9ea07e35bcd79c95c4b59b0254944f62805","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[],"content":"Text note","sig":"ed73a8a4e7c26cd797a7b875c634d9ecb6958c57733305fed23b978109d0411d21b3e182cb67c8ad750884e30ca383b509382ae6187b36e76ee76e6a142c4284"}"#;
390-
let event = Event::from_json(json).unwrap();
391-
392-
// First process
393-
store.process(&event, None).await.unwrap();
394-
395-
// Re-process the same event
396-
store.process(&event, None).await.unwrap();
397-
}
398-
399-
#[tokio::test]
400-
async fn test_process_nip65_relay_list() {
401-
let store = NostrGossipMemory::unbounded();
402-
403-
// NIP-65 relay list event with read and write relays
404-
let json = r#"{"id":"0a49bed4a1eb0973a68a0d43b7ca62781ffd4e052b91bbadef09e5cf756f6e68","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1759351841,"kind":10002,"tags":[["alt","Relay list to discover the user's content"],["r","wss://relay.damus.io/"],["r","wss://nostr.wine/"],["r","wss://nostr.oxtr.dev/"],["r","wss://relay.nostr.wirednet.jp/"]],"content":"","sig":"f5bc6c18b0013214588d018c9086358fb76a529aa10867d4d02a75feb239412ae1c94ac7c7917f6e6e2303d72f00dc4e9b03b168ef98f3c3c0dec9a457ce0304"}"#;
405-
let event = Event::from_json(json).unwrap();
406-
407-
store.process(&event, None).await.unwrap();
408-
409-
let public_key = event.pubkey;
410-
411-
// Test Read selection
412-
let read_relays = store
413-
._get_best_relays(&public_key, BestRelaySelection::Read { limit: 2 })
414-
.await;
415-
416-
assert_eq!(read_relays.len(), 2); // relay.damus.io and nos.lol
417-
418-
// Test Write selection
419-
let write_relays = store
420-
._get_best_relays(&public_key, BestRelaySelection::Write { limit: 2 })
421-
.await;
422-
423-
assert_eq!(write_relays.len(), 2); // relay.damus.io and relay.nostr.band
385+
async fn setup() -> NostrGossipMemory {
386+
NostrGossipMemory::unbounded()
424387
}
425388

426-
#[tokio::test]
427-
async fn test_process_nip17_inbox_relays() {
428-
let store = NostrGossipMemory::unbounded();
429-
430-
// NIP-17 inbox relays event
431-
let json = r#"{"id":"8d9b40907f80bd7d5014bdc6a2541227b92f4ae20cbff59792b4746a713da81e","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1756718818,"kind":10050,"tags":[["relay","wss://auth.nostr1.com/"],["relay","wss://nostr.oxtr.dev/"],["relay","wss://nip17.com"]],"content":"","sig":"05611df32f5c4e55bb8d74ab2840378b7707ad162f785a78f8bdaecee5b872667e4e43bcbbf3c6c638335c637f001155b48b7a7040ce2695660467be62f142d5"}"#;
432-
let event = Event::from_json(json).unwrap();
433-
434-
store.process(&event, None).await.unwrap();
435-
436-
let public_key = event.pubkey;
437-
438-
// Test PrivateMessage selection
439-
let pm_relays = store
440-
._get_best_relays(&public_key, BestRelaySelection::PrivateMessage { limit: 4 })
441-
.await;
442-
443-
assert_eq!(pm_relays.len(), 3); // inbox.nostr.wine and relay.primal.net
444-
}
445-
446-
#[tokio::test]
447-
async fn test_process_hints_from_p_tags() {
448-
let store = NostrGossipMemory::unbounded();
449-
450-
let public_key =
451-
PublicKey::parse("npub1drvpzev3syqt0kjrls50050uzf25gehpz9vgdw08hvex7e0vgfeq0eseet")
452-
.unwrap();
453-
let relay_url = RelayUrl::parse("wss://hint.relay.io").unwrap();
454-
455-
let keys = Keys::generate();
456-
let event = EventBuilder::text_note("test")
457-
.tag(Tag::from_standardized_without_cell(
458-
TagStandard::PublicKey {
459-
public_key,
460-
relay_url: Some(relay_url.clone()),
461-
alias: None,
462-
uppercase: false,
463-
},
464-
))
465-
.sign_with_keys(&keys)
466-
.unwrap();
467-
468-
store.process(&event, None).await.unwrap();
469-
470-
let hint_relays = store
471-
._get_best_relays(&public_key, BestRelaySelection::Hints { limit: 5 })
472-
.await;
473-
474-
assert_eq!(hint_relays.len(), 1);
475-
assert!(hint_relays.iter().any(|r| r == &relay_url));
476-
}
477-
478-
#[tokio::test]
479-
async fn test_received_events_tracking() {
480-
let store = NostrGossipMemory::unbounded();
481-
482-
let keys = Keys::generate();
483-
let relay_url = RelayUrl::parse("wss://test.relay.io").unwrap();
484-
485-
// Process multiple events from the same relay
486-
for i in 0..5 {
487-
let event = EventBuilder::text_note(format!("Test {i}"))
488-
.sign_with_keys(&keys)
489-
.unwrap();
490-
491-
store.process(&event, Some(&relay_url)).await.unwrap();
492-
}
493-
494-
// Test MostReceived selection
495-
let most_received = store
496-
._get_best_relays(
497-
&keys.public_key,
498-
BestRelaySelection::MostReceived { limit: 10 },
499-
)
500-
.await;
501-
502-
assert_eq!(most_received.len(), 1);
503-
assert!(most_received.iter().any(|r| r == &relay_url));
504-
}
505-
506-
#[tokio::test]
507-
async fn test_best_relays_all_selection() {
508-
let store = NostrGossipMemory::unbounded();
509-
510-
let public_key =
511-
PublicKey::from_hex("68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272")
512-
.unwrap();
513-
514-
// Add NIP-65 relays
515-
let nip65_json = r#"{"id":"0000000000000000000000000000000000000000000000000000000000000000","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1704644581,"kind":10002,"tags":[["r","wss://read.relay.io","read"],["r","wss://write.relay.io","write"]],"content":"","sig":"f5bc6c18b0013214588d018c9086358fb76a529aa10867d4d02a75feb239412ae1c94ac7c7917f6e6e2303d72f00dc4e9b03b168ef98f3c3c0dec9a457ce0304"}"#;
516-
let nip65_event = Event::from_json(nip65_json).unwrap();
517-
store.process(&nip65_event, None).await.unwrap();
518-
519-
// Add event with hints
520-
let hint_json = r#"{"id":"0000000000000000000000000000000000000000000000000000000000000001","pubkey":"bb4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","wss://hint.relay.io"]],"content":"Hint","sig":"f5bc6c18b0013214588d018c9086358fb76a529aa10867d4d02a75feb239412ae1c94ac7c7917f6e6e2303d72f00dc4e9b03b168ef98f3c3c0dec9a457ce0304"}"#;
521-
let hint_event = Event::from_json(hint_json).unwrap();
522-
store.process(&hint_event, None).await.unwrap();
523-
524-
// Add received events
525-
let relay_url = RelayUrl::parse("wss://received.relay.io").unwrap();
526-
let received_json = r#"{"id":"0000000000000000000000000000000000000000000000000000000000000002","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1704644581,"kind":1,"tags":[],"content":"Received","sig":"f5bc6c18b0013214588d018c9086358fb76a529aa10867d4d02a75feb239412ae1c94ac7c7917f6e6e2303d72f00dc4e9b03b168ef98f3c3c0dec9a457ce0304"}"#;
527-
let received_event = Event::from_json(received_json).unwrap();
528-
store
529-
.process(&received_event, Some(&relay_url))
530-
.await
531-
.unwrap();
532-
533-
// Test All selection
534-
let all_relays = store
535-
._get_best_relays(
536-
&public_key,
537-
BestRelaySelection::All {
538-
read: 5,
539-
write: 5,
540-
hints: 5,
541-
most_received: 5,
542-
},
543-
)
544-
.await;
545-
546-
// Should have relays from all categories (duplicates removed by HashSet)
547-
assert!(all_relays.len() >= 3);
548-
assert!(all_relays
549-
.iter()
550-
.any(|r| r.as_str() == "wss://read.relay.io"));
551-
assert!(all_relays
552-
.iter()
553-
.any(|r| r.as_str() == "wss://write.relay.io"));
554-
assert!(all_relays
555-
.iter()
556-
.any(|r| r.as_str() == "wss://hint.relay.io"));
557-
assert!(all_relays
558-
.iter()
559-
.any(|r| r.as_str() == "wss://received.relay.io"));
560-
}
561-
562-
#[tokio::test]
563-
async fn test_status_tracking() {
564-
let store = NostrGossipMemory::unbounded();
565-
566-
let public_key =
567-
PublicKey::from_hex("68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272")
568-
.unwrap();
569-
570-
// Initially should be outdated
571-
let status = store.get_status(&public_key, GossipListKind::Nip65).await;
572-
assert!(matches!(status, GossipPublicKeyStatus::Outdated { .. }));
573-
574-
// Process a NIP-65 event
575-
let json = r#"{"id":"0a49bed4a1eb0973a68a0d43b7ca62781ffd4e052b91bbadef09e5cf756f6e68","pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","created_at":1759351841,"kind":10002,"tags":[["alt","Relay list to discover the user's content"],["r","wss://relay.damus.io/"],["r","wss://nostr.wine/"],["r","wss://nostr.oxtr.dev/"],["r","wss://relay.nostr.wirednet.jp/"]],"content":"","sig":"f5bc6c18b0013214588d018c9086358fb76a529aa10867d4d02a75feb239412ae1c94ac7c7917f6e6e2303d72f00dc4e9b03b168ef98f3c3c0dec9a457ce0304"}"#;
576-
let event = Event::from_json(json).unwrap();
577-
store.process(&event, None).await.unwrap();
578-
579-
// Update fetch attempt
580-
store
581-
._update_fetch_attempt(&public_key, GossipListKind::Nip65)
582-
.await;
583-
584-
// Should now be updated
585-
let status = store.get_status(&public_key, GossipListKind::Nip65).await;
586-
assert!(matches!(status, GossipPublicKeyStatus::Updated));
587-
}
588-
589-
#[tokio::test]
590-
async fn test_empty_results() {
591-
let store = NostrGossipMemory::unbounded();
592-
593-
// Random public key with no data
594-
let public_key =
595-
PublicKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001")
596-
.unwrap();
597-
598-
// Should return empty set
599-
let relays = store
600-
._get_best_relays(&public_key, BestRelaySelection::Read { limit: 10 })
601-
.await;
602-
603-
assert_eq!(relays.len(), 0);
604-
}
389+
gossip_unit_tests!(NostrGossipMemory, setup);
605390
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "nostr-gossip-test-suite"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
nostr = { workspace = true, features = ["std"] }
9+
nostr-gossip.workspace = true
10+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

0 commit comments

Comments
 (0)