test: update sync tests to set up git data for purgatory flow

All sync tests now create a local git repo, send announcement + state
event to the source relay, and push git data to release both from
purgatory before the syncing relay starts bootstrap sync.
This commit is contained in:
DanConwayDev
2026-02-23 12:05:05 +00:00
parent 63865548b0
commit 49b9405dfc
7 changed files with 680 additions and 507 deletions
+301 -35
View File
@@ -507,41 +507,53 @@ fn check_sync_connections_in_metrics(metrics: &str, expected: usize) -> bool {
/// assert!(found, "Expected event {} to sync to relay", event.id);
/// ```
pub async fn wait_for_event_on_relay(relay_url: &str, filter: Filter, timeout: Duration) -> bool {
// Create a temporary client for querying
let temp_keys = Keys::generate();
let client = Client::new(temp_keys);
let deadline = tokio::time::Instant::now() + timeout;
let poll_interval = Duration::from_millis(200);
// Try to connect
if client.add_relay(relay_url).await.is_err() {
return false;
}
loop {
// Create a fresh client for each poll attempt (avoids stale connection state)
let temp_keys = Keys::generate();
let client = Client::new(temp_keys);
client.connect().await;
// Wait for connection (brief timeout)
let mut connected = false;
for _ in 0..10 {
tokio::time::sleep(Duration::from_millis(100)).await;
let relays = client.relays().await;
if relays.values().any(|r| r.is_connected()) {
connected = true;
break;
if client.add_relay(relay_url).await.is_err() {
if tokio::time::Instant::now() >= deadline {
return false;
}
tokio::time::sleep(poll_interval).await;
continue;
}
}
if !connected {
client.disconnect().await;
return false;
}
client.connect().await;
// Fetch events with the provided timeout
let result = client.fetch_events(filter, timeout).await;
// Wait for connection
let mut connected = false;
for _ in 0..10 {
tokio::time::sleep(Duration::from_millis(100)).await;
let relays = client.relays().await;
if relays.values().any(|r| r.is_connected()) {
connected = true;
break;
}
}
client.disconnect().await;
if connected {
// Use a short fetch window — if the event is there, EOSE comes back quickly
let fetch_timeout = Duration::from_millis(500);
let result = client.fetch_events(filter.clone(), fetch_timeout).await;
client.disconnect().await;
match result {
Ok(events) => !events.is_empty(),
Err(_) => false,
match result {
Ok(events) if !events.is_empty() => return true,
_ => {}
}
} else {
client.disconnect().await;
}
if tokio::time::Instant::now() >= deadline {
return false;
}
tokio::time::sleep(poll_interval).await;
}
}
@@ -774,6 +786,11 @@ impl MetricsTestHarness {
self.source_relays[idx].domain()
}
/// Get a reference to a source relay (for advanced test operations)
pub fn source_relay(&self, idx: usize) -> &TestRelay {
&self.source_relays[idx]
}
/// Submit events to a specific source relay
pub async fn submit_events(&self, source_idx: usize, events: &[Event]) -> Result<(), String> {
let relay = &self.source_relays[source_idx];
@@ -1099,6 +1116,259 @@ pub async fn send_to_relay_url(relay_url: &str, event: &Event) -> Result<(), Str
Ok(())
}
/// Push git repository data to a relay to release a purgatory-held announcement.
///
/// Creates a local git repo, sends a state event, and pushes to the relay.
/// Use this when you need to build a custom announcement but still need the
/// relay to accept it (i.e., release it from purgatory).
///
/// # Arguments
/// * `relay` - The relay to push to
/// * `keys` - Keys of the repository owner
/// * `identifier` - Repository identifier
/// * `domains` - All domains in the announcement (for state event URLs)
///
/// # Returns
/// `tempfile::TempDir` - Keep alive for test duration
pub async fn push_git_data_to_relay(
relay: &TestRelay,
keys: &Keys,
identifier: &str,
domains: &[&str],
) -> tempfile::TempDir {
use super::purgatory_helpers::{
create_state_event, create_test_repo_with_commit, push_to_relay, CommitVariant,
};
let npub = keys
.public_key()
.to_bech32()
.expect("Failed to convert public key to npub");
// Create local git repo
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
.expect("Failed to create test git repo");
let clone_urls: Vec<String> = domains
.iter()
.map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
.collect();
let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
// Build and send state event with all domains' clone URLs
let state_event = create_state_event(
keys,
identifier,
&[("main", &commit_hash)],
&[],
&clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
&relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
)
.expect("Failed to create state event");
send_to_relay(relay, &state_event)
.await
.expect("Failed to send state event");
// Git push to relay → releases state event from purgatory, authorizes push
push_to_relay(git_temp_dir.path(), &relay.domain(), &npub, identifier)
.expect("Failed to push git data to relay");
// Brief wait for push processing
tokio::time::sleep(Duration::from_millis(500)).await;
git_temp_dir
}
/// Like `push_git_data_to_relay` but writes a unique marker file so each call
/// produces a distinct commit hash.
///
/// Use this when multiple callers push to the same relay with the same identifier
/// but different keys — identical commit hashes cause git to skip pack transfer,
/// which can leave the announcement in purgatory.
///
/// # Arguments
/// * `relay` - The relay to push to
/// * `keys` - Keys of the repository owner
/// * `identifier` - Repository identifier
/// * `domains` - All domains in the announcement (for state event URLs)
/// * `unique_seed` - A string written into a `.unique` file to differentiate commits
///
/// # Returns
/// `tempfile::TempDir` - Keep alive for test duration
pub async fn push_unique_git_data_to_relay(
relay: &TestRelay,
keys: &Keys,
identifier: &str,
domains: &[&str],
unique_seed: &str,
) -> tempfile::TempDir {
use super::purgatory_helpers::{create_state_event, push_to_relay};
let npub = keys
.public_key()
.to_bech32()
.expect("Failed to convert public key to npub");
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
let path = git_temp_dir.path();
fn git(path: &std::path::Path, args: &[&str]) {
let status = std::process::Command::new("git")
.args(args)
.current_dir(path)
.env("GIT_AUTHOR_NAME", "Test User")
.env("GIT_AUTHOR_EMAIL", "test@example.com")
.env("GIT_COMMITTER_NAME", "Test User")
.env("GIT_COMMITTER_EMAIL", "test@example.com")
.env("GIT_AUTHOR_DATE", "2024-01-01T00:00:00+00:00")
.env("GIT_COMMITTER_DATE", "2024-01-01T00:00:00+00:00")
.output()
.unwrap_or_else(|e| panic!("git {:?} failed to spawn: {}", args, e));
assert!(
status.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&status.stderr)
);
}
git(path, &["init", "--initial-branch=main"]);
git(path, &["config", "user.email", "test@example.com"]);
git(path, &["config", "user.name", "Test User"]);
git(path, &["config", "commit.gpgsign", "false"]);
// Write a unique file so each maintainer gets a distinct commit hash
std::fs::write(path.join("state_test.txt"), "State test content for purgatory sync")
.expect("write state_test.txt");
std::fs::write(path.join(".unique"), unique_seed).expect("write .unique");
git(path, &["add", "."]);
git(path, &["commit", "-m", "State test commit"]);
let commit_hash = {
let out = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(path)
.output()
.expect("git rev-parse");
String::from_utf8_lossy(&out.stdout).trim().to_string()
};
let clone_urls: Vec<String> = domains
.iter()
.map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
.collect();
let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
let state_event = create_state_event(
keys,
identifier,
&[("main", &commit_hash)],
&[],
&clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
&relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
)
.expect("Failed to create state event");
send_to_relay(relay, &state_event)
.await
.expect("Failed to send state event");
push_to_relay(path, &relay.domain(), &npub, identifier)
.expect("Failed to push git data to relay");
tokio::time::sleep(Duration::from_millis(500)).await;
git_temp_dir
}
/// Set up a repository announcement on a relay with git data so it passes purgatory.
///
/// With the announcement purgatory feature, announcements (kind 30617) require git
/// data before they are promoted to the relay's main DB. This helper:
///
/// 1. Creates a local git repo with a commit
/// 2. Builds an announcement and state event (kind 30618) pointing to the relay
/// 3. Sends both to the relay (they go to purgatory)
/// 4. Git pushes to the relay → releases both from purgatory immediately
/// 5. Returns the announcement event and temp dir (keep alive for test duration)
///
/// # Arguments
/// * `relay` - The relay to set up the announcement on
/// * `keys` - Keys to sign the announcement with (repo owner)
/// * `domains` - All domains that should be listed in the announcement (including relay.domain())
/// * `identifier` - Repository identifier (d-tag)
///
/// # Returns
/// `(Event, tempfile::TempDir)` - The announcement event and temp dir.
/// The temp dir MUST be kept alive for the duration of the test.
pub async fn setup_announcement_on_relay(
relay: &TestRelay,
keys: &Keys,
domains: &[&str],
identifier: &str,
) -> (Event, tempfile::TempDir) {
use super::purgatory_helpers::{
create_state_event, create_test_repo_with_commit, push_to_relay, CommitVariant,
};
let npub = keys
.public_key()
.to_bech32()
.expect("Failed to convert public key to npub");
// Create local git repo with a commit
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
.expect("Failed to create test git repo");
// Build clone URLs and relay URLs from domains
let clone_urls: Vec<String> = domains
.iter()
.map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
.collect();
let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
// Build announcement event (lists ALL domains for relay discovery)
let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
.tags(vec![
Tag::identifier(identifier),
Tag::custom(TagKind::custom("clone"), clone_urls.clone()),
Tag::custom(TagKind::custom("relays"), relay_urls.clone()),
])
.sign_with_keys(keys)
.expect("Failed to sign repo announcement");
// Build state event with all domains' clone URLs
let state_event = create_state_event(
keys,
identifier,
&[("main", &commit_hash)],
&[],
&clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
&relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
)
.expect("Failed to create state event");
// Send announcement and state event to relay (both go to purgatory)
send_to_relay(relay, &announcement)
.await
.expect("Failed to send announcement");
send_to_relay(relay, &state_event)
.await
.expect("Failed to send state event");
// Git push to relay → releases both from purgatory
push_to_relay(git_temp_dir.path(), &relay.domain(), &npub, identifier)
.expect("Failed to push git data to relay");
// Brief wait for push processing
tokio::time::sleep(Duration::from_millis(500)).await;
(announcement, git_temp_dir)
}
/// Unified sync test helper that automatically determines sync mode.
///
/// This function sets up a complete sync test environment by determining whether
@@ -1158,9 +1428,8 @@ pub async fn run_sync_test(historic_events: &[Event], live_events: &[Event]) ->
// 3. Create local git repo with a commit
let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
let commit_hash =
create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
.expect("Failed to create test git repo");
let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
.expect("Failed to create test git repo");
// 4. Create keys and build URLs
let keys = Keys::generate();
@@ -1172,10 +1441,7 @@ pub async fn run_sync_test(historic_events: &[Event], live_events: &[Event]) ->
// Clone URLs: source relay HTTP endpoint is where git data lives
// The syncing relay's purgatory will fetch from source's clone URL
let clone_url_source = format!("http://{}/{}/{}.git", source.domain(), npub, "test-repo");
let clone_url_syncing = format!(
"http://{}/{}/{}.git",
syncing_domain, npub, "test-repo"
);
let clone_url_syncing = format!("http://{}/{}/{}.git", syncing_domain, npub, "test-repo");
let clone_urls = vec![clone_url_source.clone(), clone_url_syncing.clone()];
let relay_urls = vec![
+50 -114
View File
@@ -62,29 +62,26 @@ async fn test_discovers_layer3_via_layer2() {
// 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;
// 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!(
"Created announcement {} (kind {})",
announcement_id,
announcement.kind.as_u16()
"Announcement {} set up on relay_a with git data",
announcement_id
);
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"
repo_id
);
// 6. Create a patch event (Layer 2) that references the announcement
@@ -97,21 +94,12 @@ async fn test_discovers_layer3_via_layer2() {
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
// 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(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_a
.send_event(&patch)
.await
@@ -120,18 +108,10 @@ async fn test_discovers_layer3_via_layer2() {
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;
// 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...");
@@ -197,19 +177,20 @@ async fn test_relay_discovery_via_announcements_with_historic_sync() {
// 3. Create test keys
let keys = Keys::generate();
// 4. Create the event chain on relay_a:
// 4. Set up repository on relay_a with git data and a Layer 2 issue
// Layer 1: Repository announcement
let announcement = create_repo_announcement(
&keys,
&[&relay_a.domain(), &relay_b.domain()],
"test-repo-chain",
);
// 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!("Created announcement {} (Layer 1)", 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, "test-repo-chain");
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")
@@ -217,35 +198,23 @@ async fn test_relay_discovery_via_announcements_with_historic_sync() {
let issue_id = issue.id;
println!("Created issue {} (Layer 2)", issue_id);
// 5. Send all events to relay_a
// 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(&announcement)
.await
.expect("Failed to send announcement");
client_a
.send_event(&issue)
.await
.expect("Failed to send issue");
println!("Events sent to relay_a");
println!("Issue 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;
// 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...");
@@ -327,65 +296,32 @@ async fn test_recursive_relay_discovery_via_announcements_with_historic_sync() {
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",
);
// 3. Set up announcement_x on relay_b (lists all three relays: A+B+C) with git data
let domains_x = vec![relay_a.domain(), relay_b.domain(), relay_c.domain()];
let domain_refs_x: Vec<&str> = domains_x.iter().map(|s| s.as_str()).collect();
let (announcement_x, _git_dir_b) =
setup_announcement_on_relay(&relay_b, &keys_x, &domain_refs_x, "repo-x-all-relays").await;
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());
}
println!("announcement_x {} set up on relay_b with git data (listing A+B+C)", announcement_x_id);
// 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",
);
// 4. Set up announcement_y on relay_c (lists only A+C, NOT B) with git data
let domains_y = vec![relay_a.domain(), relay_c.domain()];
let domain_refs_y: Vec<&str> = domains_y.iter().map(|s| s.as_str()).collect();
let (announcement_y, _git_dir_c) =
setup_announcement_on_relay(&relay_c, &keys_y, &domain_refs_y, "repo-y-ac-only").await;
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;
println!("announcement_y {} set up on relay_c with git data (listing A+C only)", announcement_y_id);
// 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;
// With purgatory, each relay needs to: sync announcement → purgatory → sync state event →
// immediate purgatory sync → fetch git data → promote. Allow extra time for this.
println!("Waiting 12s for recursive relay discovery (with purgatory flow)...");
tokio::time::sleep(Duration::from_secs(12)).await;
// 8. Verify announcement_x was synced to relay_a (from bootstrap relay_b)
let filter_x = Filter::new()
+11 -21
View File
@@ -224,34 +224,24 @@ async fn test_history_sync_without_negentropy() {
// Create keys
let keys = Keys::generate();
// Create announcement listing BOTH relay domains
// This event will exist on source BEFORE syncing relay ever connects
let announcement = create_repo_announcement(
// Set up announcement on source with git data
// (purgatory requires git data before announcements are accepted)
let domains = vec![source.domain(), syncing_domain.clone()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let (announcement, _git_dir) = setup_announcement_on_relay(
&source,
&keys,
&[&source.domain(), &syncing_domain],
&domain_refs,
"test-repo-history-no-negentropy",
);
)
.await;
let announcement_id = announcement.id;
println!(
"Created announcement {} (kind {})",
announcement_id,
announcement.kind.as_u16()
"Announcement {} set up on source with git data (event exists BEFORE syncing relay connects)",
announcement_id
);
// Send announcement to source (event now exists BEFORE syncing relay connects)
let client = TestClient::new(source.url(), keys.clone())
.await
.expect("Failed to connect to source");
client
.send_event(&announcement)
.await
.expect("Failed to send announcement to source");
println!("Announcement sent to source (event exists BEFORE syncing relay connects)");
client.disconnect().await;
// Wait to ensure event is stored
tokio::time::sleep(Duration::from_millis(500)).await;
+43 -76
View File
@@ -56,43 +56,24 @@ async fn test_live_sync_layer2_events() {
// 3. Create test keys
let keys = Keys::generate();
// 4. Create a repository announcement that lists BOTH relays
// 4. Create a repository announcement on both relays with git data
// (purgatory requires git data before announcements are accepted)
let repo_id = "test-repo-live-l2";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
println!(
"Created announcement {} (kind {})",
announcement.id,
announcement.kind.as_u16()
);
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
// 5. Send announcement to relay_a
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to 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 with git data (triggers discovery)");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
// 6. Send announcement to relay_b (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 (triggers discovery)");
// 7. Wait for discovery to complete
// 5. Wait for discovery to complete
tokio::time::sleep(Duration::from_secs(1)).await;
// 8. Create and send a Layer 2 issue event (using helper)
// 6. Create and send a Layer 2 issue event (using helper)
let repo_coordinate = repo_coord(&keys, repo_id);
let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync")
.expect("Failed to create issue event");
@@ -104,6 +85,10 @@ async fn test_live_sync_layer2_events() {
}
// Send issue 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(&issue)
.await
@@ -111,7 +96,6 @@ async fn test_live_sync_layer2_events() {
println!("Issue sent to relay_a");
client_a.disconnect().await;
client_b.disconnect().await;
// 9. Wait and verify event syncs to relay_b
let filter = Filter::new()
@@ -166,30 +150,19 @@ async fn test_live_sync_layer3_events() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
// (purgatory requires git data before announcements are accepted)
let repo_id = "test-repo-live-l3";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -200,6 +173,10 @@ async fn test_live_sync_layer3_events() {
.expect("Failed to create issue");
let issue_id = issue.id;
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
.await
@@ -243,7 +220,6 @@ async fn test_live_sync_layer3_events() {
println!("Issue synced to relay_b: {}", issue_synced);
client_a.disconnect().await;
client_b.disconnect().await;
// 7. Wait and verify comment syncs to relay_b
let comment_filter = Filter::new()
@@ -343,29 +319,17 @@ async fn test_live_sync_event_ordering() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
// (purgatory requires git data before announcements are accepted)
let repo_id = "test-repo-ordering";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcements sent to both relays");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
let (_announcement_b, _git_dir_b) =
setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
println!("Announcements set up on both relays with git data");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -375,6 +339,10 @@ async fn test_live_sync_event_ordering() {
let mut issue_ids = Vec::new();
let mut expected_order_timestamps = Vec::new();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
for i in 1..=3 {
let issue = build_layer2_issue_event(
&keys,
@@ -402,7 +370,6 @@ async fn test_live_sync_event_ordering() {
}
client_a.disconnect().await;
client_b.disconnect().await;
// 5. Wait for all events to sync
tokio::time::sleep(Duration::from_secs(3)).await;
+110 -43
View File
@@ -7,7 +7,10 @@ use std::time::Duration;
use nostr_sdk::prelude::*;
use crate::common::{sync_helpers::*, TestRelay};
use crate::common::{
sync_helpers::*,
TestRelay,
};
/// Test that maintainer announcements are re-processed immediately when owner announcement accepted
///
@@ -37,10 +40,12 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
let start = std::time::Instant::now();
// Step 1: Send maintainer announcement to relay_a (will be rejected - doesn't list relay_b)
let client_a = TestClient::new(relay_a.url(), maintainer_keys.clone())
.await
.expect("Failed to connect to relay_a");
// Step 1: Send maintainer announcement to relay_a (will be rejected by relay_b - doesn't list relay_b)
// Use HTTP clone URL pointing to relay_a's git endpoint so it can be released from purgatory
let maintainer_npub = maintainer_keys
.public_key()
.to_bech32()
.expect("Failed to get npub");
let maintainer_announcement =
EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository")
@@ -48,27 +53,50 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
Tag::identifier(identifier),
Tag::custom(
TagKind::custom("clone"),
vec![format!("https://{}/{}.git", relay_a.domain(), identifier)],
vec![format!(
"http://{}/{}/{}.git",
relay_a.domain(),
maintainer_npub,
identifier
)],
),
Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]),
])
.sign_with_keys(&maintainer_keys)
.unwrap();
client_a.send_event(&maintainer_announcement).await.unwrap();
send_to_relay(&relay_a, &maintainer_announcement)
.await
.unwrap();
println!("✓ Maintainer announcement sent to relay_a");
// Step 2: Send owner announcement to relay_b (lists relay_a + maintainer)
let client_b = TestClient::new(relay_b.url(), owner_keys.clone())
.await
.expect("Failed to connect to relay_b");
// Push git data for maintainer's repo to relay_a → releases maintainer announcement from purgatory
let _git_dir_maintainer = push_git_data_to_relay(
&relay_a,
&maintainer_keys,
identifier,
&[&relay_a.domain()],
)
.await;
println!("✓ Maintainer git data pushed to relay_a (announcement released from purgatory)");
// Step 2: Set up owner announcement on relay_b (lists relay_a + maintainer) with git data
let owner_npub = owner_keys
.public_key()
.to_bech32()
.expect("Failed to get npub");
let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
.tags(vec![
Tag::identifier(identifier),
Tag::custom(
TagKind::custom("clone"),
vec![format!("https://{}/{}.git", relay_b.domain(), identifier)],
vec![format!(
"http://{}/{}/{}.git",
relay_b.domain(),
owner_npub,
identifier
)],
),
Tag::custom(
TagKind::custom("relays"),
@@ -82,9 +110,14 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
.sign_with_keys(&owner_keys)
.unwrap();
client_b.send_event(&owner_announcement).await.unwrap();
send_to_relay(&relay_b, &owner_announcement).await.unwrap();
println!("✓ Owner announcement sent to relay_b");
// Push git data for owner's repo to relay_b → releases owner announcement from purgatory
let _git_dir_owner =
push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await;
println!("✓ Owner git data pushed to relay_b (announcement released from purgatory)");
// Step 3: Wait for sync and re-processing (relay_b discovers relay_a, syncs, re-processes)
tokio::time::sleep(Duration::from_secs(3)).await;
@@ -114,15 +147,13 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
// Step 5: Verify it happened quickly (not 24 hours!)
assert!(
elapsed.as_secs() < 10,
"Re-processing should happen in <10 seconds, took {:?}",
elapsed.as_secs() < 15,
"Re-processing should happen in <15 seconds, took {:?}",
elapsed
);
println!("✅ Maintainer announcement re-processed in {:?}", elapsed);
client_a.disconnect().await;
client_b.disconnect().await;
relay_a.stop().await;
relay_b.stop().await;
}
@@ -253,15 +284,18 @@ async fn test_multiple_maintainers_all_reprocessed() {
let identifier = "multi-maintainer-repo";
// Step 1: Send three maintainer announcements to relay_a
let client_a = TestClient::new(relay_a.url(), maintainer1_keys.clone())
.await
.expect("Failed to connect to relay_a");
// Step 1: Send three maintainer announcements to relay_a with git data
// (purgatory requires git data before announcements are accepted)
let mut git_dirs_maintainers = Vec::new();
for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys]
.iter()
.enumerate()
{
let m_npub = maintainer_keys
.public_key()
.to_bech32()
.expect("Failed to get npub");
let announcement = EventBuilder::new(
Kind::GitRepoAnnouncement,
format!("Maintainer {} repository", idx + 1),
@@ -270,28 +304,45 @@ async fn test_multiple_maintainers_all_reprocessed() {
Tag::identifier(identifier),
Tag::custom(
TagKind::custom("clone"),
vec![format!("https://{}/{}.git", relay_a.domain(), identifier)],
vec![format!(
"http://{}/{}/{}.git",
relay_a.domain(),
m_npub,
identifier
)],
),
Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]),
])
.sign_with_keys(maintainer_keys)
.unwrap();
client_a.send_event(&announcement).await.unwrap();
send_to_relay(&relay_a, &announcement).await.unwrap();
// Push git data to release each maintainer's announcement from purgatory
let git_dir =
push_git_data_to_relay(&relay_a, maintainer_keys, identifier, &[&relay_a.domain()])
.await;
git_dirs_maintainers.push(git_dir);
}
println!("✓ Three maintainer announcements sent to relay_a");
println!("✓ Three maintainer announcements sent to relay_a with git data");
// Step 2: Send owner announcement to relay_b (lists relay_a + all three maintainers)
let client_b = TestClient::new(relay_b.url(), owner_keys.clone())
.await
.expect("Failed to connect to relay_b");
let owner_npub = owner_keys
.public_key()
.to_bech32()
.expect("Failed to get npub");
let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
.tags(vec![
Tag::identifier(identifier),
Tag::custom(
TagKind::custom("clone"),
vec![format!("https://{}/{}.git", relay_b.domain(), identifier)],
vec![format!(
"http://{}/{}/{}.git",
relay_b.domain(),
owner_npub,
identifier
)],
),
Tag::custom(
TagKind::custom("relays"),
@@ -309,9 +360,14 @@ async fn test_multiple_maintainers_all_reprocessed() {
.sign_with_keys(&owner_keys)
.unwrap();
client_b.send_event(&owner_announcement).await.unwrap();
send_to_relay(&relay_b, &owner_announcement).await.unwrap();
println!("✓ Owner announcement sent to relay_b");
// Push git data for owner to relay_b → releases owner announcement from purgatory
let _git_dir_owner =
push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await;
println!("✓ Owner git data pushed to relay_b (announcement released from purgatory)");
// Step 3: Wait for sync and re-processing
tokio::time::sleep(Duration::from_secs(3)).await;
@@ -333,8 +389,6 @@ async fn test_multiple_maintainers_all_reprocessed() {
println!("✅ All three maintainer announcements re-processed successfully");
client_a.disconnect().await;
client_b.disconnect().await;
relay_a.stop().await;
relay_b.stop().await;
}
@@ -356,12 +410,8 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
let identifier = "invalid-maintainer-repo";
// Create client using TestClient helper
let client = TestClient::new(relay.url(), owner_keys.clone())
.await
.expect("Failed to connect to relay");
// Step 1: Send maintainer announcement (will be rejected - doesn't list our relay)
// This one uses example.com clone URL - it goes to purgatory on relay, never promoted
let maintainer_announcement =
EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository")
.tags(vec![
@@ -378,17 +428,28 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
.sign_with_keys(&maintainer_keys)
.unwrap();
// Send maintainer announcement - expect it to be rejected
let _ = client.send_event(&maintainer_announcement).await;
// Send maintainer announcement - expect it to be rejected (purgatory / policy)
send_to_relay(&relay, &maintainer_announcement).await.ok();
tokio::time::sleep(Duration::from_millis(200)).await;
// Step 2: Send owner announcement with INVALID maintainer hex
// Step 2: Set up owner announcement with INVALID maintainer hex and git data
// Use HTTP clone URL to relay's git endpoint so it can be released from purgatory
let owner_npub = owner_keys
.public_key()
.to_bech32()
.expect("Failed to get npub");
let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
.tags(vec![
Tag::identifier(identifier),
Tag::custom(
TagKind::custom("clone"),
vec![format!("https://{}/{}.git", relay.domain(), identifier)],
vec![format!(
"http://{}/{}/{}.git",
relay.domain(),
owner_npub,
identifier
)],
),
Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]),
Tag::custom(
@@ -399,7 +460,14 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
.sign_with_keys(&owner_keys)
.unwrap();
client.send_event(&owner_announcement).await.unwrap();
send_to_relay(&relay, &owner_announcement).await.unwrap();
// Push git data to relay → releases owner announcement from purgatory
let _git_dir =
push_git_data_to_relay(&relay, &owner_keys, identifier, &[&relay.domain()]).await;
println!("✓ Owner git data pushed to relay (announcement released from purgatory)");
// Wait for processing
tokio::time::sleep(Duration::from_millis(500)).await;
// Step 3: Verify owner announcement accepted, maintainer not re-processed
@@ -429,6 +497,5 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
println!("✅ Invalid maintainer pubkey handled gracefully without panic");
client.disconnect().await;
relay.stop().await;
}
+86 -53
View File
@@ -16,8 +16,8 @@ use nostr_sdk::prelude::*;
use crate::common::{
sync_helpers::{
create_repo_announcement, fetch_metrics, wait_for_sync_connection, MetricsTestHarness,
ParsedMetrics, TestClient,
create_repo_announcement, fetch_metrics, setup_announcement_on_relay,
wait_for_sync_connection, MetricsTestHarness, ParsedMetrics, TestClient,
},
TestRelay,
};
@@ -224,16 +224,17 @@ async fn test_startup_sync_event_count() {
// 3. Create test keys
let keys = Keys::generate();
// 4. Create an announcement that lists BOTH relays (required for discovery)
let announcement = create_repo_announcement(
&keys,
&[&source_relay.domain(), &syncing_relay.domain()],
"test-repo-metrics",
);
// 4. Set up announcement on SOURCE relay with git data
// (purgatory requires git data before announcements are accepted)
let repo_id = "test-repo-metrics";
let domains = vec![source_relay.domain(), syncing_relay.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let (announcement, _git_dir_source) =
setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
println!(
"Created announcement {} (kind {})",
announcement.id,
announcement.kind.as_u16()
"Announcement {} set up on source relay with git data",
announcement.id
);
// 5. Build the repo coordinate for the 'a' tag in the patches
@@ -241,7 +242,7 @@ async fn test_startup_sync_event_count() {
"{}:{}:{}",
Kind::GitRepoAnnouncement.as_u16(),
keys.public_key().to_hex(),
"test-repo-metrics"
repo_id
);
// 6. Create 3 patch events (Layer 2) that reference the announcement
@@ -257,17 +258,11 @@ async fn test_startup_sync_event_count() {
.collect();
println!("Created {} patches", patches.len());
// 7. Send announcement + patches to SOURCE relay ONLY
// 7. Send patches to SOURCE relay
let source_client = TestClient::new(source_relay.url(), keys.clone())
.await
.expect("Failed to connect to source relay");
source_client
.send_event(&announcement)
.await
.expect("Failed to send announcement to source");
println!("Announcement sent to source relay");
for patch in &patches {
source_client
.send_event(patch)
@@ -277,17 +272,10 @@ async fn test_startup_sync_event_count() {
println!("Patches sent to source relay");
source_client.disconnect().await;
// 8. Send announcement to SYNCING relay (triggers discovery of source relay)
let syncing_client = TestClient::new(syncing_relay.url(), keys.clone())
.await
.expect("Failed to connect to syncing relay");
syncing_client
.send_event(&announcement)
.await
.expect("Failed to send announcement to syncing relay");
println!("Announcement sent to syncing relay (triggers discovery of source)");
syncing_client.disconnect().await;
// 8. Set up announcement on SYNCING relay (triggers discovery of source relay)
let (_announcement_syncing, _git_dir_syncing) =
setup_announcement_on_relay(&syncing_relay, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on syncing relay (triggers discovery of source)");
// 9. Wait for discovery + sync to complete
println!("Waiting 5s for discovery and sync...");
@@ -404,18 +392,35 @@ async fn test_connection_failure_increments_counter() {
/// Test that live sync events are counted in metrics.
///
/// This test validates that events received via live subscription
/// (after sync connection is established) are counted separately
/// from startup/bootstrap events.
/// (after sync connection is established) are counted in metrics.
/// Uses Layer 2 patch events (not announcements) to avoid purgatory,
/// since Layer 2 events are accepted directly to the DB.
#[tokio::test]
async fn test_live_sync_event_count() {
let mut harness = MetricsTestHarness::with_sources(1).await;
// Pre-allocate syncing relay port to include in announcements
let sync_port = TestRelay::find_free_port();
let sync_domain = format!("127.0.0.1:{}", sync_port);
// Start source relay
let source_relay = TestRelay::start().await;
println!("Source relay started at {}", source_relay.url());
// Set up announcement on source relay BEFORE starting syncing relay
// This allows discovery when syncing relay connects
let keys = Keys::generate();
let repo_id = "live-metrics-repo";
let domains = vec![source_relay.domain(), sync_domain.clone()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let (_announcement, _git_dir) =
setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on source relay with git data");
// Start syncing relay with pre-allocated port
harness.start_syncing_relay_on_port(0, sync_port).await;
let syncing_relay =
TestRelay::start_on_port_with_options(sync_port, Some(source_relay.url().to_string()), false)
.await;
println!("Syncing relay started at {}", syncing_relay.url());
// Wait for sync connection to be fully established with EOSE received
// This ensures we're in "live" mode before submitting test events
@@ -424,33 +429,61 @@ async fn test_live_sync_event_count() {
.await
.expect("Sync connection should be established");
// Additional small delay to ensure EOSE has been processed
tokio::time::sleep(Duration::from_millis(500)).await;
// Additional delay to ensure purgatory promotion completes on syncing relay
tokio::time::sleep(Duration::from_secs(4)).await;
// Now add events - these should be "live" not "startup"
// Include BOTH domains so events are accepted by both relays
let keys = Keys::generate();
let events: Vec<_> = (0..2)
.map(|i| {
create_repo_announcement(
&keys,
&[&harness.source_domain(0), &sync_domain],
&format!("live-{}", i),
)
})
.collect();
harness.submit_events(0, &events).await.unwrap();
// Now add Layer 2 patch events (not announcements) - these are accepted immediately
// (Layer 2 events are accepted directly to DB, no purgatory)
let repo_coord_str = format!(
"{}:{}:{}",
Kind::GitRepoAnnouncement.as_u16(),
keys.public_key().to_hex(),
repo_id
);
let patch1 = create_event_referencing_repo(
&keys,
&repo_coord_str,
Kind::GitPatch.as_u16(),
"Live test patch 1",
);
let patch2 = create_event_referencing_repo(
&keys,
&repo_coord_str,
Kind::GitPatch.as_u16(),
"Live test patch 2",
);
// Send patches to source AFTER sync connection established (live mode)
let client = TestClient::new(source_relay.url(), keys.clone())
.await
.expect("Failed to connect to source");
client.send_event(&patch1).await.expect("Failed to send patch 1");
client.send_event(&patch2).await.expect("Failed to send patch 2");
client.disconnect().await;
println!("Two patches sent to source relay (live mode)");
// Wait for live events to be processed and metrics updated
tokio::time::sleep(Duration::from_secs(4)).await;
let metrics = harness.get_metrics().await.unwrap();
// Fetch metrics from syncing relay
let raw_metrics = fetch_metrics(&sync_url)
.await
.expect("Failed to fetch metrics");
let metrics = ParsedMetrics::parse(&raw_metrics);
let synced_count = metrics.events_synced_total();
println!("Events synced total: {:?}", synced_count);
assert_eq!(synced_count, Some(2), "Should have 2 synced events");
// Cleanup
syncing_relay.stop().await;
source_relay.stop().await;
harness.stop_all().await;
assert!(
synced_count.is_some() && synced_count.unwrap() >= 2,
"Should have synced at least 2 events, got {:?}",
synced_count
);
}
/// Test that relay connected status is tracked in metrics.
+79 -165
View File
@@ -55,30 +55,19 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
// (purgatory requires git data before announcements are accepted)
let repo_id = "test-repo-tag-8a";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -95,9 +84,10 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
issue_id,
issue.kind.as_u16()
);
for tag in issue.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
@@ -106,7 +96,6 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
println!("Issue sent to relay_a");
client_a.disconnect().await;
client_b.disconnect().await;
// 5. Wait and verify event syncs to relay_b
let filter = Filter::new()
@@ -154,30 +143,18 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
let repo_id = "test-repo-tag-8b";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -197,9 +174,10 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
issue_id,
issue.kind.as_u16()
);
for tag in issue.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
@@ -208,7 +186,6 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
println!("Issue sent to relay_a");
client_a.disconnect().await;
client_b.disconnect().await;
// 5. Wait and verify event syncs to relay_b
let filter = Filter::new()
@@ -255,30 +232,18 @@ async fn test_layer2_sync_with_q_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
let repo_id = "test-repo-tag-8c";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -294,9 +259,10 @@ async fn test_layer2_sync_with_q_tag() {
issue_id,
issue.kind.as_u16()
);
for tag in issue.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
@@ -305,7 +271,6 @@ async fn test_layer2_sync_with_q_tag() {
println!("Issue sent to relay_a");
client_a.disconnect().await;
client_b.disconnect().await;
// 5. Wait and verify event syncs to relay_b
let filter = Filter::new()
@@ -362,30 +327,18 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
let repo_id = "test-repo-tag-9a";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -396,6 +349,10 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
.expect("Failed to create issue");
let issue_id = issue.id;
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
.await
@@ -410,11 +367,6 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
assert!(issue_synced, "Layer 2 issue should sync first");
// Wait for Layer 3 subscriptions to be established
// After issue syncs, relay_b's SelfSubscriber needs time to:
// 1. Receive the synced issue via notify_event broadcast
// 2. Batch timer to tick (up to 200ms in tests)
// 3. Process batch and create Layer 3 filters
// 4. Subscribe to relay_a with Layer 3 filters
tokio::time::sleep(Duration::from_millis(500)).await;
// 6. Create and send Layer 3 reply with lowercase 'e' tag (kind 1)
@@ -427,9 +379,6 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
reply_id,
reply.kind.as_u16()
);
for tag in reply.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
client_a
.send_event(&reply)
@@ -438,7 +387,6 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
println!("Layer 3 reply {} sent to relay_a", reply_id);
client_a.disconnect().await;
client_b.disconnect().await;
// 7. Wait and verify reply syncs to relay_b
let reply_filter = Filter::new()
@@ -486,30 +434,18 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
let repo_id = "test-repo-tag-9b";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -520,6 +456,10 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
.expect("Failed to create issue");
let issue_id = issue.id;
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
.await
@@ -534,11 +474,6 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
assert!(issue_synced, "Layer 2 issue should sync first");
// Wait for Layer 3 subscriptions to be established
// After issue syncs, relay_b's SelfSubscriber needs time to:
// 1. Receive the synced issue via notify_event broadcast
// 2. Batch timer to tick (up to 200ms in tests)
// 3. Process batch and create Layer 3 filters
// 4. Subscribe to relay_a with Layer 3 filters
tokio::time::sleep(Duration::from_millis(500)).await;
// 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111)
@@ -552,9 +487,6 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
comment_id,
comment.kind.as_u16()
);
for tag in comment.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
client_a
.send_event(&comment)
@@ -563,7 +495,6 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
println!("Layer 3 comment {} sent to relay_a", comment_id);
client_a.disconnect().await;
client_b.disconnect().await;
// 7. Wait and verify comment syncs to relay_b
let comment_filter = Filter::new()
@@ -614,30 +545,18 @@ async fn test_layer3_sync_with_q_tag() {
let keys = Keys::generate();
// 2. Create and send repository announcement to both relays
// 2. Create and send repository announcement to both relays with git data
let repo_id = "test-repo-tag-9c";
let announcement =
create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id);
let domains = vec![relay_a.domain(), relay_b.domain()];
let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
let (_announcement, _git_dir_a) =
setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
println!("Announcement set up on relay_a with git data");
let client_b = TestClient::new(relay_b.url(), keys.clone())
.await
.expect("Failed to connect to relay_b");
client_a
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_a");
println!("Announcement sent to relay_a");
client_b
.send_event(&announcement)
.await
.expect("Failed to send announcement to relay_b");
println!("Announcement sent to relay_b (triggers discovery)");
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 with git data (triggers discovery)");
// 3. Wait for discovery
tokio::time::sleep(Duration::from_secs(1)).await;
@@ -648,6 +567,10 @@ async fn test_layer3_sync_with_q_tag() {
.expect("Failed to create issue");
let issue_id = issue.id;
let client_a = TestClient::new(relay_a.url(), keys.clone())
.await
.expect("Failed to connect to relay_a");
client_a
.send_event(&issue)
.await
@@ -662,11 +585,6 @@ async fn test_layer3_sync_with_q_tag() {
assert!(issue_synced, "Layer 2 issue should sync first");
// Wait for Layer 3 subscriptions to be established
// After issue syncs, relay_b's SelfSubscriber needs time to:
// 1. Receive the synced issue via notify_event broadcast
// 2. Batch timer to tick (up to 200ms in tests)
// 3. Process batch and create Layer 3 filters
// 4. Subscribe to relay_a with Layer 3 filters
tokio::time::sleep(Duration::from_millis(500)).await;
// 6. Create and send Layer 3 quote with 'q' tag (kind 1)
@@ -679,9 +597,6 @@ async fn test_layer3_sync_with_q_tag() {
quote_id,
quote.kind.as_u16()
);
for tag in quote.tags.iter() {
println!(" Tag: {:?}", tag.as_slice());
}
client_a
.send_event(&quote)
@@ -690,7 +605,6 @@ async fn test_layer3_sync_with_q_tag() {
println!("Layer 3 quote {} sent to relay_a", quote_id);
client_a.disconnect().await;
client_b.disconnect().await;
// 7. Wait and verify quote syncs to relay_b
let quote_filter = Filter::new()