//! Discovery Sync Tests //! //! Tests for relay discovery from announcement events. //! When a relay receives an announcement listing another relay, //! it should discover and connect to that relay to sync events. use std::time::Duration; use nostr_sdk::prelude::*; use crate::common::{sync_helpers::*, TestRelay}; // NOTE: Using rust-nostr Kind variant: // - Kind::GitPatch.as_u16() -> Kind::GitPatch (1617) /// Create an event referencing a repository coordinate via 'a' tag. /// /// Used to create Layer 2 events like patches that reference a repository. fn create_event_referencing_repo(keys: &Keys, repo_coord: &str, kind: u16, content: &str) -> Event { let tags = vec![Tag::custom( TagKind::custom("a"), vec![repo_coord.to_string()], )]; EventBuilder::new(Kind::from_u16(kind), content) .tags(tags) .sign_with_keys(keys) .expect("Failed to sign event") } /// Test 2: Relay discovers another relay via announcement and syncs Layer 2 events /// /// Scenario: /// 1. relay_a has announcement + patch event (Layer 2) /// 2. relay_b (sync enabled, NO bootstrap) receives the announcement directly /// 3. relay_b discovers relay_a from the announcement's relays tag /// 4. relay_b connects to relay_a and syncs the patch event /// /// This tests dynamic relay discovery from direct submissions. #[tokio::test] async fn test_discovers_layer3_via_layer2() { // 1. Start relay_a (source) with the patch event let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); // 2. Start relay_b: sync enabled but NO bootstrap relay - will discover relay_a let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); // 3. Create test keys let keys = Keys::generate(); // 4. Set up repository announcement on relay_a with git data // (purgatory requires git data before announcements are accepted) let repo_id = "test-repo-discovery"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; let announcement_id = announcement.id; println!( "Announcement {} set up on relay_a with git data", announcement_id ); // 5. Build the repo coordinate for the 'a' tag in the patch let repo_coord = format!( "{}:{}:{}", Kind::GitRepoAnnouncement.as_u16(), keys.public_key().to_hex(), repo_id ); // 6. Create a patch event (Layer 2) that references the announcement let patch = create_event_referencing_repo( &keys, &repo_coord, Kind::GitPatch.as_u16(), "Test patch proposal", ); let patch_id = patch.id; println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16()); // 7. Send patch to relay_a let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&patch) .await .expect("Failed to send patch to relay_a"); println!("Patch sent to relay_a"); client_a.disconnect().await; // 8. Set up announcement on relay_b (triggers discovery of relay_a) let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b (should trigger discovery of relay_a)"); // 9. Wait for relay_b to discover relay_a and sync the patch println!("Waiting 3s for relay_b to discover relay_a and sync patch..."); tokio::time::sleep(Duration::from_secs(3)).await; // 10. Verify patch was synced to relay_b let filter = Filter::new().kind(Kind::GitPatch).author(keys.public_key()); let patch_synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; if patch_synced { println!( "Patch {} found on relay_b (synced from discovered relay_a)", patch_id ); } else { println!("Patch {} NOT found on relay_b", patch_id); } // 11. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( patch_synced, "Patch {} should have been synced to relay_b from discovered relay_a", patch_id ); } /// Test 3: Layer 2 discovery with full event chain /// /// Scenario: /// 1. relay_a has: announcement → issue (Layer 2) /// 2. relay_b receives announcement directly /// 3. relay_b discovers relay_a and syncs the issue (Layer 2) /// /// This tests that Layer 2 events (issues/patches) are synced when their /// parent repository is discovered. The chain is: /// Layer 1 (30617): Repository announcement /// Layer 2 (1618): Issue referencing repo /// /// Note: Layer 3 (comments on issues) sync is tracked separately and may /// be implemented in future phases. This test focuses on Layer 2 discovery. #[tokio::test] async fn test_relay_discovery_via_announcements_with_historic_sync() { // 1. Start relay_a (source) with the event chain let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); // 2. Start relay_b: sync enabled but NO bootstrap relay let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); // 3. Create test keys let keys = Keys::generate(); // 4. Set up repository on relay_a with git data and a Layer 2 issue // Layer 1: Set up announcement with git data let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let repo_id = "test-repo-chain"; let (announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; let announcement_id = announcement.id; println!( "Announcement {} set up on relay_a with git data (Layer 1)", announcement_id ); // Build repo coordinate for Layer 2 reference let repo_coord = repo_coord(&keys, repo_id); // Layer 2: Issue referencing the repo let issue = build_layer2_issue_event(&keys, &repo_coord, "Test issue for chain discovery") .expect("Failed to create issue"); let issue_id = issue.id; println!("Created issue {} (Layer 2)", issue_id); // 5. Send issue to relay_a let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue"); println!("Issue sent to relay_a"); client_a.disconnect().await; // 6. Set up announcement on relay_b (triggers discovery of relay_a) let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b (should trigger discovery of relay_a)"); // 7. Wait for sync println!("Waiting 3s for Layer 2 sync..."); tokio::time::sleep(Duration::from_secs(3)).await; // 8. Verify Layer 2 event synced to relay_b let issue_filter = Filter::new().kind(Kind::GitIssue).author(keys.public_key()); let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; println!("Sync result:"); println!(" Issue {} synced: {}", issue_id, issue_synced); // 9. Cleanup relay_b.stop().await; relay_a.stop().await; // 10. Assert Layer 2 event synced assert!( issue_synced, "Issue {} (Layer 2) should have synced to relay_b via discovery", issue_id ); }