mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
- Replace KIND_REPOSITORY_ANNOUNCEMENT with Kind::GitRepoAnnouncement - Replace KIND_REPOSITORY_STATE with Kind::RepoState - Replace KIND_PR with Kind::GitPullRequest - Replace KIND_PR_UPDATE with Kind::GitPullRequestUpdate - Replace KIND_USER_GRASP_LIST with Kind::GitUserGraspList - Replace KIND_PATCH with Kind::GitPatch - Replace KIND_ISSUE with Kind::GitIssue - Replace KIND_COMMENT with Kind::Comment - Replace all Kind::Custom(30617|30618|1617|1618|1619|1621|1111|10317) patterns - Remove all hardcoded KIND_* constants from events.rs - Update all match statements to use Kind enum directly - Update all filter builders to use Kind variants - Update all test helpers and assertions Benefits: - Type safety: compiler prevents wrong kind numbers - Readability: Kind::GitRepoAnnouncement is self-documenting - Maintainability: single source of truth (rust-nostr) - IDE support: full autocompletion and refactoring - Standards: aligns with rust-nostr best practices Files modified: 21 Constants removed: 9 Patterns replaced: 100+ Tests passing: 222/222
434 lines
14 KiB
Rust
434 lines
14 KiB
Rust
//! 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.
|
|
//!
|
|
//! # Tests
|
|
//! - Test 2: Direct Layer 3 discovery from Layer 2
|
|
//! - Test 3: Recursive multi-hop Layer 3 discovery
|
|
|
|
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. Create a repository announcement that lists BOTH relays
|
|
let announcement = create_repo_announcement(
|
|
&keys,
|
|
&[&relay_a.domain(), &relay_b.domain()],
|
|
"test-repo-discovery",
|
|
);
|
|
let announcement_id = announcement.id;
|
|
|
|
println!(
|
|
"Created announcement {} (kind {})",
|
|
announcement_id,
|
|
announcement.kind.as_u16()
|
|
);
|
|
for tag in announcement.tags.iter() {
|
|
println!(" Tag: {:?}", tag.as_slice());
|
|
}
|
|
|
|
// 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(),
|
|
"test-repo-discovery"
|
|
);
|
|
|
|
// 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());
|
|
for tag in patch.tags.iter() {
|
|
println!(" Tag: {:?}", tag.as_slice());
|
|
}
|
|
|
|
// 7. Send announcement and patch to relay_a ONLY
|
|
let client_a = TestClient::new(relay_a.url(), keys.clone())
|
|
.await
|
|
.expect("Failed to connect to relay_a");
|
|
|
|
client_a
|
|
.send_event(&announcement)
|
|
.await
|
|
.expect("Failed to send announcement to relay_a");
|
|
println!("Announcement sent 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. Send announcement to relay_b directly (triggers discovery of relay_a)
|
|
let client_b = TestClient::new(relay_b.url(), keys.clone())
|
|
.await
|
|
.expect("Failed to connect to relay_b");
|
|
|
|
client_b
|
|
.send_event(&announcement)
|
|
.await
|
|
.expect("Failed to send announcement to relay_b");
|
|
println!("Announcement sent to relay_b (should trigger discovery of relay_a)");
|
|
|
|
client_b.disconnect().await;
|
|
|
|
// 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. Create the event chain on relay_a:
|
|
|
|
// Layer 1: Repository announcement
|
|
let announcement = create_repo_announcement(
|
|
&keys,
|
|
&[&relay_a.domain(), &relay_b.domain()],
|
|
"test-repo-chain",
|
|
);
|
|
let announcement_id = announcement.id;
|
|
println!("Created announcement {} (Layer 1)", announcement_id);
|
|
|
|
// Build repo coordinate for Layer 2 reference
|
|
let repo_coord = repo_coord(&keys, "test-repo-chain");
|
|
|
|
// 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 all events 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(&announcement)
|
|
.await
|
|
.expect("Failed to send announcement");
|
|
client_a
|
|
.send_event(&issue)
|
|
.await
|
|
.expect("Failed to send issue");
|
|
|
|
println!("Events sent to relay_a");
|
|
client_a.disconnect().await;
|
|
|
|
// 6. Send only the announcement to relay_b (triggers discovery)
|
|
let client_b = TestClient::new(relay_b.url(), keys.clone())
|
|
.await
|
|
.expect("Failed to connect to relay_b");
|
|
|
|
client_b
|
|
.send_event(&announcement)
|
|
.await
|
|
.expect("Failed to send announcement to relay_b");
|
|
println!("Announcement sent to relay_b (should trigger discovery)");
|
|
|
|
client_b.disconnect().await;
|
|
|
|
// 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
|
|
);
|
|
}
|
|
|
|
/// Test 3: 3-relay recursive discovery - relay discovers third relay through bootstrap
|
|
///
|
|
/// Scenario:
|
|
/// ```text
|
|
/// relay_a (SUT) relay_b (bootstrap) relay_c (discovered)
|
|
/// │ │ │
|
|
/// │ │ has announcement_x │ has announcement_y
|
|
/// │ │ listing A+B+C │ listing A+C
|
|
/// │ │ │
|
|
/// ├────connect──────────► │
|
|
/// │◄───sync announcement_x───────────────────────
|
|
/// │ │
|
|
/// │ discovers relay_c from announcement_x │
|
|
/// │ │
|
|
/// ├─────────────connect─────────────────────────►
|
|
/// │◄────────────sync announcement_y─────────────┘
|
|
/// ```
|
|
///
|
|
/// This tests that relay_a:
|
|
/// 1. Connects to relay_b (configured as bootstrap)
|
|
/// 2. Receives announcement_x which lists relay_c
|
|
/// 3. Discovers and connects to relay_c
|
|
/// 4. Syncs announcement_y from relay_c
|
|
///
|
|
#[tokio::test]
|
|
async fn test_recursive_relay_discovery_via_announcements_with_historic_sync() {
|
|
// 1. Start all three relays
|
|
|
|
// relay_b - will be the bootstrap relay, has announcement_x
|
|
let relay_b = TestRelay::start().await;
|
|
println!(
|
|
"relay_b (bootstrap) started at {} (domain: {})",
|
|
relay_b.url(),
|
|
relay_b.domain()
|
|
);
|
|
|
|
// relay_c - will be discovered via announcement_x, has announcement_y
|
|
let relay_c = TestRelay::start().await;
|
|
println!(
|
|
"relay_c (to be discovered) started at {} (domain: {})",
|
|
relay_c.url(),
|
|
relay_c.domain()
|
|
);
|
|
|
|
// relay_a - SUT, starts with relay_b as bootstrap
|
|
let relay_a = TestRelay::start_with_sync(Some(relay_b.url().to_string())).await;
|
|
println!(
|
|
"relay_a (SUT) started at {} (domain: {})",
|
|
relay_a.url(),
|
|
relay_a.domain()
|
|
);
|
|
|
|
// 2. Create test keys (one for each announcement)
|
|
let keys_x = Keys::generate();
|
|
let keys_y = Keys::generate();
|
|
|
|
// 3. Create announcement_x on relay_b (lists all three relays: A+B+C)
|
|
let announcement_x = create_repo_announcement(
|
|
&keys_x,
|
|
&[&relay_a.domain(), &relay_b.domain(), &relay_c.domain()],
|
|
"repo-x-all-relays",
|
|
);
|
|
let announcement_x_id = announcement_x.id;
|
|
println!("Created announcement_x {} listing A+B+C", announcement_x_id);
|
|
for tag in announcement_x.tags.iter() {
|
|
println!(" Tag: {:?}", tag.as_slice());
|
|
}
|
|
|
|
// 4. Create announcement_y on relay_c (lists only A+C, NOT B)
|
|
let announcement_y = create_repo_announcement(
|
|
&keys_y,
|
|
&[&relay_a.domain(), &relay_c.domain()],
|
|
"repo-y-ac-only",
|
|
);
|
|
let announcement_y_id = announcement_y.id;
|
|
println!(
|
|
"Created announcement_y {} listing A+C only",
|
|
announcement_y_id
|
|
);
|
|
for tag in announcement_y.tags.iter() {
|
|
println!(" Tag: {:?}", tag.as_slice());
|
|
}
|
|
|
|
// 5. Send announcement_x to relay_b only
|
|
let client_b = TestClient::new(relay_b.url(), keys_x.clone())
|
|
.await
|
|
.expect("Failed to connect to relay_b");
|
|
|
|
client_b
|
|
.send_event(&announcement_x)
|
|
.await
|
|
.expect("Failed to send announcement_x to relay_b");
|
|
println!("announcement_x sent to relay_b");
|
|
|
|
client_b.disconnect().await;
|
|
|
|
// 6. Send announcement_y to relay_c only
|
|
let client_c = TestClient::new(relay_c.url(), keys_y.clone())
|
|
.await
|
|
.expect("Failed to connect to relay_c");
|
|
|
|
client_c
|
|
.send_event(&announcement_y)
|
|
.await
|
|
.expect("Failed to send announcement_y to relay_c");
|
|
println!("announcement_y sent to relay_c");
|
|
|
|
client_c.disconnect().await;
|
|
|
|
// 7. Wait for relay_a to:
|
|
// - Sync from bootstrap relay_b (gets announcement_x)
|
|
// - Discover relay_c from announcement_x's relays tag
|
|
// - Connect to relay_c and sync announcement_y
|
|
println!("Waiting 5s for recursive relay discovery...");
|
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
|
|
|
// 8. Verify announcement_x was synced to relay_a (from bootstrap relay_b)
|
|
let filter_x = Filter::new()
|
|
.kind(Kind::GitRepoAnnouncement)
|
|
.author(keys_x.public_key());
|
|
|
|
let announcement_x_synced =
|
|
wait_for_event_on_relay(relay_a.url(), filter_x, Duration::from_secs(5)).await;
|
|
|
|
println!(
|
|
"announcement_x {} synced to relay_a: {}",
|
|
announcement_x_id, announcement_x_synced
|
|
);
|
|
|
|
// 9. Verify announcement_y was synced to relay_a (from discovered relay_c)
|
|
let filter_y = Filter::new()
|
|
.kind(Kind::GitRepoAnnouncement)
|
|
.author(keys_y.public_key());
|
|
|
|
let announcement_y_synced =
|
|
wait_for_event_on_relay(relay_a.url(), filter_y, Duration::from_secs(5)).await;
|
|
|
|
println!(
|
|
"announcement_y {} synced to relay_a: {}",
|
|
announcement_y_id, announcement_y_synced
|
|
);
|
|
|
|
// 10. Cleanup
|
|
relay_a.stop().await;
|
|
relay_b.stop().await;
|
|
relay_c.stop().await;
|
|
|
|
// 11. Assertions
|
|
assert!(
|
|
announcement_x_synced,
|
|
"announcement_x {} should have synced from bootstrap relay_b to relay_a",
|
|
announcement_x_id
|
|
);
|
|
|
|
assert!(
|
|
announcement_y_synced,
|
|
"announcement_y {} should have synced from discovered relay_c to relay_a (recursive discovery)",
|
|
announcement_y_id
|
|
);
|
|
}
|