upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/common/sync_helpers.rs336
-rw-r--r--tests/sync/discovery.rs164
-rw-r--r--tests/sync/historic_sync.rs32
-rw-r--r--tests/sync/live_sync.rs119
-rw-r--r--tests/sync/maintainer_reprocessing.rs153
-rw-r--r--tests/sync/metrics.rs139
-rw-r--r--tests/sync/tag_variations.rs244
7 files changed, 680 insertions, 507 deletions
diff --git a/tests/common/sync_helpers.rs b/tests/common/sync_helpers.rs
index daa684b..af51e78 100644
--- a/tests/common/sync_helpers.rs
+++ b/tests/common/sync_helpers.rs
@@ -507,41 +507,53 @@ fn check_sync_connections_in_metrics(metrics: &str, expected: usize) -> bool {
507/// assert!(found, "Expected event {} to sync to relay", event.id); 507/// assert!(found, "Expected event {} to sync to relay", event.id);
508/// ``` 508/// ```
509pub async fn wait_for_event_on_relay(relay_url: &str, filter: Filter, timeout: Duration) -> bool { 509pub async fn wait_for_event_on_relay(relay_url: &str, filter: Filter, timeout: Duration) -> bool {
510 // Create a temporary client for querying 510 let deadline = tokio::time::Instant::now() + timeout;
511 let temp_keys = Keys::generate(); 511 let poll_interval = Duration::from_millis(200);
512 let client = Client::new(temp_keys);
513
514 // Try to connect
515 if client.add_relay(relay_url).await.is_err() {
516 return false;
517 }
518 512
519 client.connect().await; 513 loop {
514 // Create a fresh client for each poll attempt (avoids stale connection state)
515 let temp_keys = Keys::generate();
516 let client = Client::new(temp_keys);
520 517
521 // Wait for connection (brief timeout) 518 if client.add_relay(relay_url).await.is_err() {
522 let mut connected = false; 519 if tokio::time::Instant::now() >= deadline {
523 for _ in 0..10 { 520 return false;
524 tokio::time::sleep(Duration::from_millis(100)).await; 521 }
525 let relays = client.relays().await; 522 tokio::time::sleep(poll_interval).await;
526 if relays.values().any(|r| r.is_connected()) { 523 continue;
527 connected = true;
528 break;
529 } 524 }
530 }
531 525
532 if !connected { 526 client.connect().await;
533 client.disconnect().await;
534 return false;
535 }
536 527
537 // Fetch events with the provided timeout 528 // Wait for connection
538 let result = client.fetch_events(filter, timeout).await; 529 let mut connected = false;
530 for _ in 0..10 {
531 tokio::time::sleep(Duration::from_millis(100)).await;
532 let relays = client.relays().await;
533 if relays.values().any(|r| r.is_connected()) {
534 connected = true;
535 break;
536 }
537 }
539 538
540 client.disconnect().await; 539 if connected {
540 // Use a short fetch window — if the event is there, EOSE comes back quickly
541 let fetch_timeout = Duration::from_millis(500);
542 let result = client.fetch_events(filter.clone(), fetch_timeout).await;
543 client.disconnect().await;
541 544
542 match result { 545 match result {
543 Ok(events) => !events.is_empty(), 546 Ok(events) if !events.is_empty() => return true,
544 Err(_) => false, 547 _ => {}
548 }
549 } else {
550 client.disconnect().await;
551 }
552
553 if tokio::time::Instant::now() >= deadline {
554 return false;
555 }
556 tokio::time::sleep(poll_interval).await;
545 } 557 }
546} 558}
547 559
@@ -774,6 +786,11 @@ impl MetricsTestHarness {
774 self.source_relays[idx].domain() 786 self.source_relays[idx].domain()
775 } 787 }
776 788
789 /// Get a reference to a source relay (for advanced test operations)
790 pub fn source_relay(&self, idx: usize) -> &TestRelay {
791 &self.source_relays[idx]
792 }
793
777 /// Submit events to a specific source relay 794 /// Submit events to a specific source relay
778 pub async fn submit_events(&self, source_idx: usize, events: &[Event]) -> Result<(), String> { 795 pub async fn submit_events(&self, source_idx: usize, events: &[Event]) -> Result<(), String> {
779 let relay = &self.source_relays[source_idx]; 796 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
1099 Ok(()) 1116 Ok(())
1100} 1117}
1101 1118
1119/// Push git repository data to a relay to release a purgatory-held announcement.
1120///
1121/// Creates a local git repo, sends a state event, and pushes to the relay.
1122/// Use this when you need to build a custom announcement but still need the
1123/// relay to accept it (i.e., release it from purgatory).
1124///
1125/// # Arguments
1126/// * `relay` - The relay to push to
1127/// * `keys` - Keys of the repository owner
1128/// * `identifier` - Repository identifier
1129/// * `domains` - All domains in the announcement (for state event URLs)
1130///
1131/// # Returns
1132/// `tempfile::TempDir` - Keep alive for test duration
1133pub async fn push_git_data_to_relay(
1134 relay: &TestRelay,
1135 keys: &Keys,
1136 identifier: &str,
1137 domains: &[&str],
1138) -> tempfile::TempDir {
1139 use super::purgatory_helpers::{
1140 create_state_event, create_test_repo_with_commit, push_to_relay, CommitVariant,
1141 };
1142
1143 let npub = keys
1144 .public_key()
1145 .to_bech32()
1146 .expect("Failed to convert public key to npub");
1147
1148 // Create local git repo
1149 let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
1150 let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
1151 .expect("Failed to create test git repo");
1152
1153 let clone_urls: Vec<String> = domains
1154 .iter()
1155 .map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
1156 .collect();
1157 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
1158
1159 // Build and send state event with all domains' clone URLs
1160 let state_event = create_state_event(
1161 keys,
1162 identifier,
1163 &[("main", &commit_hash)],
1164 &[],
1165 &clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1166 &relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1167 )
1168 .expect("Failed to create state event");
1169
1170 send_to_relay(relay, &state_event)
1171 .await
1172 .expect("Failed to send state event");
1173
1174 // Git push to relay → releases state event from purgatory, authorizes push
1175 push_to_relay(git_temp_dir.path(), &relay.domain(), &npub, identifier)
1176 .expect("Failed to push git data to relay");
1177
1178 // Brief wait for push processing
1179 tokio::time::sleep(Duration::from_millis(500)).await;
1180
1181 git_temp_dir
1182}
1183
1184/// Like `push_git_data_to_relay` but writes a unique marker file so each call
1185/// produces a distinct commit hash.
1186///
1187/// Use this when multiple callers push to the same relay with the same identifier
1188/// but different keys — identical commit hashes cause git to skip pack transfer,
1189/// which can leave the announcement in purgatory.
1190///
1191/// # Arguments
1192/// * `relay` - The relay to push to
1193/// * `keys` - Keys of the repository owner
1194/// * `identifier` - Repository identifier
1195/// * `domains` - All domains in the announcement (for state event URLs)
1196/// * `unique_seed` - A string written into a `.unique` file to differentiate commits
1197///
1198/// # Returns
1199/// `tempfile::TempDir` - Keep alive for test duration
1200pub async fn push_unique_git_data_to_relay(
1201 relay: &TestRelay,
1202 keys: &Keys,
1203 identifier: &str,
1204 domains: &[&str],
1205 unique_seed: &str,
1206) -> tempfile::TempDir {
1207 use super::purgatory_helpers::{create_state_event, push_to_relay};
1208
1209 let npub = keys
1210 .public_key()
1211 .to_bech32()
1212 .expect("Failed to convert public key to npub");
1213
1214 let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
1215 let path = git_temp_dir.path();
1216
1217 fn git(path: &std::path::Path, args: &[&str]) {
1218 let status = std::process::Command::new("git")
1219 .args(args)
1220 .current_dir(path)
1221 .env("GIT_AUTHOR_NAME", "Test User")
1222 .env("GIT_AUTHOR_EMAIL", "test@example.com")
1223 .env("GIT_COMMITTER_NAME", "Test User")
1224 .env("GIT_COMMITTER_EMAIL", "test@example.com")
1225 .env("GIT_AUTHOR_DATE", "2024-01-01T00:00:00+00:00")
1226 .env("GIT_COMMITTER_DATE", "2024-01-01T00:00:00+00:00")
1227 .output()
1228 .unwrap_or_else(|e| panic!("git {:?} failed to spawn: {}", args, e));
1229 assert!(
1230 status.status.success(),
1231 "git {:?} failed: {}",
1232 args,
1233 String::from_utf8_lossy(&status.stderr)
1234 );
1235 }
1236
1237 git(path, &["init", "--initial-branch=main"]);
1238 git(path, &["config", "user.email", "test@example.com"]);
1239 git(path, &["config", "user.name", "Test User"]);
1240 git(path, &["config", "commit.gpgsign", "false"]);
1241
1242 // Write a unique file so each maintainer gets a distinct commit hash
1243 std::fs::write(path.join("state_test.txt"), "State test content for purgatory sync")
1244 .expect("write state_test.txt");
1245 std::fs::write(path.join(".unique"), unique_seed).expect("write .unique");
1246 git(path, &["add", "."]);
1247 git(path, &["commit", "-m", "State test commit"]);
1248
1249 let commit_hash = {
1250 let out = std::process::Command::new("git")
1251 .args(["rev-parse", "HEAD"])
1252 .current_dir(path)
1253 .output()
1254 .expect("git rev-parse");
1255 String::from_utf8_lossy(&out.stdout).trim().to_string()
1256 };
1257
1258 let clone_urls: Vec<String> = domains
1259 .iter()
1260 .map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
1261 .collect();
1262 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
1263
1264 let state_event = create_state_event(
1265 keys,
1266 identifier,
1267 &[("main", &commit_hash)],
1268 &[],
1269 &clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1270 &relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1271 )
1272 .expect("Failed to create state event");
1273
1274 send_to_relay(relay, &state_event)
1275 .await
1276 .expect("Failed to send state event");
1277
1278 push_to_relay(path, &relay.domain(), &npub, identifier)
1279 .expect("Failed to push git data to relay");
1280
1281 tokio::time::sleep(Duration::from_millis(500)).await;
1282
1283 git_temp_dir
1284}
1285
1286/// Set up a repository announcement on a relay with git data so it passes purgatory.
1287///
1288/// With the announcement purgatory feature, announcements (kind 30617) require git
1289/// data before they are promoted to the relay's main DB. This helper:
1290///
1291/// 1. Creates a local git repo with a commit
1292/// 2. Builds an announcement and state event (kind 30618) pointing to the relay
1293/// 3. Sends both to the relay (they go to purgatory)
1294/// 4. Git pushes to the relay → releases both from purgatory immediately
1295/// 5. Returns the announcement event and temp dir (keep alive for test duration)
1296///
1297/// # Arguments
1298/// * `relay` - The relay to set up the announcement on
1299/// * `keys` - Keys to sign the announcement with (repo owner)
1300/// * `domains` - All domains that should be listed in the announcement (including relay.domain())
1301/// * `identifier` - Repository identifier (d-tag)
1302///
1303/// # Returns
1304/// `(Event, tempfile::TempDir)` - The announcement event and temp dir.
1305/// The temp dir MUST be kept alive for the duration of the test.
1306pub async fn setup_announcement_on_relay(
1307 relay: &TestRelay,
1308 keys: &Keys,
1309 domains: &[&str],
1310 identifier: &str,
1311) -> (Event, tempfile::TempDir) {
1312 use super::purgatory_helpers::{
1313 create_state_event, create_test_repo_with_commit, push_to_relay, CommitVariant,
1314 };
1315
1316 let npub = keys
1317 .public_key()
1318 .to_bech32()
1319 .expect("Failed to convert public key to npub");
1320
1321 // Create local git repo with a commit
1322 let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
1323 let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
1324 .expect("Failed to create test git repo");
1325
1326 // Build clone URLs and relay URLs from domains
1327 let clone_urls: Vec<String> = domains
1328 .iter()
1329 .map(|d| format!("http://{}/{}/{}.git", d, npub, identifier))
1330 .collect();
1331 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
1332
1333 // Build announcement event (lists ALL domains for relay discovery)
1334 let announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Repository state")
1335 .tags(vec![
1336 Tag::identifier(identifier),
1337 Tag::custom(TagKind::custom("clone"), clone_urls.clone()),
1338 Tag::custom(TagKind::custom("relays"), relay_urls.clone()),
1339 ])
1340 .sign_with_keys(keys)
1341 .expect("Failed to sign repo announcement");
1342
1343 // Build state event with all domains' clone URLs
1344 let state_event = create_state_event(
1345 keys,
1346 identifier,
1347 &[("main", &commit_hash)],
1348 &[],
1349 &clone_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1350 &relay_urls.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
1351 )
1352 .expect("Failed to create state event");
1353
1354 // Send announcement and state event to relay (both go to purgatory)
1355 send_to_relay(relay, &announcement)
1356 .await
1357 .expect("Failed to send announcement");
1358 send_to_relay(relay, &state_event)
1359 .await
1360 .expect("Failed to send state event");
1361
1362 // Git push to relay → releases both from purgatory
1363 push_to_relay(git_temp_dir.path(), &relay.domain(), &npub, identifier)
1364 .expect("Failed to push git data to relay");
1365
1366 // Brief wait for push processing
1367 tokio::time::sleep(Duration::from_millis(500)).await;
1368
1369 (announcement, git_temp_dir)
1370}
1371
1102/// Unified sync test helper that automatically determines sync mode. 1372/// Unified sync test helper that automatically determines sync mode.
1103/// 1373///
1104/// This function sets up a complete sync test environment by determining whether 1374/// 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]) ->
1158 1428
1159 // 3. Create local git repo with a commit 1429 // 3. Create local git repo with a commit
1160 let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo"); 1430 let git_temp_dir = tempfile::tempdir().expect("Failed to create temp dir for git repo");
1161 let commit_hash = 1431 let commit_hash = create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest)
1162 create_test_repo_with_commit(git_temp_dir.path(), CommitVariant::StateTest) 1432 .expect("Failed to create test git repo");
1163 .expect("Failed to create test git repo");
1164 1433
1165 // 4. Create keys and build URLs 1434 // 4. Create keys and build URLs
1166 let keys = Keys::generate(); 1435 let keys = Keys::generate();
@@ -1172,10 +1441,7 @@ pub async fn run_sync_test(historic_events: &[Event], live_events: &[Event]) ->
1172 // Clone URLs: source relay HTTP endpoint is where git data lives 1441 // Clone URLs: source relay HTTP endpoint is where git data lives
1173 // The syncing relay's purgatory will fetch from source's clone URL 1442 // The syncing relay's purgatory will fetch from source's clone URL
1174 let clone_url_source = format!("http://{}/{}/{}.git", source.domain(), npub, "test-repo"); 1443 let clone_url_source = format!("http://{}/{}/{}.git", source.domain(), npub, "test-repo");
1175 let clone_url_syncing = format!( 1444 let clone_url_syncing = format!("http://{}/{}/{}.git", syncing_domain, npub, "test-repo");
1176 "http://{}/{}/{}.git",
1177 syncing_domain, npub, "test-repo"
1178 );
1179 1445
1180 let clone_urls = vec![clone_url_source.clone(), clone_url_syncing.clone()]; 1446 let clone_urls = vec![clone_url_source.clone(), clone_url_syncing.clone()];
1181 let relay_urls = vec![ 1447 let relay_urls = vec![
diff --git a/tests/sync/discovery.rs b/tests/sync/discovery.rs
index 8ed80b5..5fcda69 100644
--- a/tests/sync/discovery.rs
+++ b/tests/sync/discovery.rs
@@ -62,29 +62,26 @@ async fn test_discovers_layer3_via_layer2() {
62 // 3. Create test keys 62 // 3. Create test keys
63 let keys = Keys::generate(); 63 let keys = Keys::generate();
64 64
65 // 4. Create a repository announcement that lists BOTH relays 65 // 4. Set up repository announcement on relay_a with git data
66 let announcement = create_repo_announcement( 66 // (purgatory requires git data before announcements are accepted)
67 &keys, 67 let repo_id = "test-repo-discovery";
68 &[&relay_a.domain(), &relay_b.domain()], 68 let domains = vec![relay_a.domain(), relay_b.domain()];
69 "test-repo-discovery", 69 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
70 ); 70
71 let (announcement, _git_dir_a) =
72 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
71 let announcement_id = announcement.id; 73 let announcement_id = announcement.id;
72
73 println!( 74 println!(
74 "Created announcement {} (kind {})", 75 "Announcement {} set up on relay_a with git data",
75 announcement_id, 76 announcement_id
76 announcement.kind.as_u16()
77 ); 77 );
78 for tag in announcement.tags.iter() {
79 println!(" Tag: {:?}", tag.as_slice());
80 }
81 78
82 // 5. Build the repo coordinate for the 'a' tag in the patch 79 // 5. Build the repo coordinate for the 'a' tag in the patch
83 let repo_coord = format!( 80 let repo_coord = format!(
84 "{}:{}:{}", 81 "{}:{}:{}",
85 Kind::GitRepoAnnouncement.as_u16(), 82 Kind::GitRepoAnnouncement.as_u16(),
86 keys.public_key().to_hex(), 83 keys.public_key().to_hex(),
87 "test-repo-discovery" 84 repo_id
88 ); 85 );
89 86
90 // 6. Create a patch event (Layer 2) that references the announcement 87 // 6. Create a patch event (Layer 2) that references the announcement
@@ -97,22 +94,13 @@ async fn test_discovers_layer3_via_layer2() {
97 let patch_id = patch.id; 94 let patch_id = patch.id;
98 95
99 println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16()); 96 println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16());
100 for tag in patch.tags.iter() {
101 println!(" Tag: {:?}", tag.as_slice());
102 }
103 97
104 // 7. Send announcement and patch to relay_a ONLY 98 // 7. Send patch to relay_a
105 let client_a = TestClient::new(relay_a.url(), keys.clone()) 99 let client_a = TestClient::new(relay_a.url(), keys.clone())
106 .await 100 .await
107 .expect("Failed to connect to relay_a"); 101 .expect("Failed to connect to relay_a");
108 102
109 client_a 103 client_a
110 .send_event(&announcement)
111 .await
112 .expect("Failed to send announcement to relay_a");
113 println!("Announcement sent to relay_a");
114
115 client_a
116 .send_event(&patch) 104 .send_event(&patch)
117 .await 105 .await
118 .expect("Failed to send patch to relay_a"); 106 .expect("Failed to send patch to relay_a");
@@ -120,18 +108,10 @@ async fn test_discovers_layer3_via_layer2() {
120 108
121 client_a.disconnect().await; 109 client_a.disconnect().await;
122 110
123 // 8. Send announcement to relay_b directly (triggers discovery of relay_a) 111 // 8. Set up announcement on relay_b (triggers discovery of relay_a)
124 let client_b = TestClient::new(relay_b.url(), keys.clone()) 112 let (_announcement_b, _git_dir_b) =
125 .await 113 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
126 .expect("Failed to connect to relay_b"); 114 println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
127
128 client_b
129 .send_event(&announcement)
130 .await
131 .expect("Failed to send announcement to relay_b");
132 println!("Announcement sent to relay_b (should trigger discovery of relay_a)");
133
134 client_b.disconnect().await;
135 115
136 // 9. Wait for relay_b to discover relay_a and sync the patch 116 // 9. Wait for relay_b to discover relay_a and sync the patch
137 println!("Waiting 3s for relay_b to discover relay_a and sync patch..."); 117 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() {
197 // 3. Create test keys 177 // 3. Create test keys
198 let keys = Keys::generate(); 178 let keys = Keys::generate();
199 179
200 // 4. Create the event chain on relay_a: 180 // 4. Set up repository on relay_a with git data and a Layer 2 issue
201 181
202 // Layer 1: Repository announcement 182 // Layer 1: Set up announcement with git data
203 let announcement = create_repo_announcement( 183 let domains = vec![relay_a.domain(), relay_b.domain()];
204 &keys, 184 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
205 &[&relay_a.domain(), &relay_b.domain()], 185 let repo_id = "test-repo-chain";
206 "test-repo-chain", 186
207 ); 187 let (announcement, _git_dir_a) =
188 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
208 let announcement_id = announcement.id; 189 let announcement_id = announcement.id;
209 println!("Created announcement {} (Layer 1)", announcement_id); 190 println!("Announcement {} set up on relay_a with git data (Layer 1)", announcement_id);
210 191
211 // Build repo coordinate for Layer 2 reference 192 // Build repo coordinate for Layer 2 reference
212 let repo_coord = repo_coord(&keys, "test-repo-chain"); 193 let repo_coord = repo_coord(&keys, repo_id);
213 194
214 // Layer 2: Issue referencing the repo 195 // Layer 2: Issue referencing the repo
215 let issue = build_layer2_issue_event(&keys, &repo_coord, "Test issue for chain discovery") 196 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() {
217 let issue_id = issue.id; 198 let issue_id = issue.id;
218 println!("Created issue {} (Layer 2)", issue_id); 199 println!("Created issue {} (Layer 2)", issue_id);
219 200
220 // 5. Send all events to relay_a 201 // 5. Send issue to relay_a
221 let client_a = TestClient::new(relay_a.url(), keys.clone()) 202 let client_a = TestClient::new(relay_a.url(), keys.clone())
222 .await 203 .await
223 .expect("Failed to connect to relay_a"); 204 .expect("Failed to connect to relay_a");
224 205
225 client_a 206 client_a
226 .send_event(&announcement)
227 .await
228 .expect("Failed to send announcement");
229 client_a
230 .send_event(&issue) 207 .send_event(&issue)
231 .await 208 .await
232 .expect("Failed to send issue"); 209 .expect("Failed to send issue");
233 210
234 println!("Events sent to relay_a"); 211 println!("Issue sent to relay_a");
235 client_a.disconnect().await; 212 client_a.disconnect().await;
236 213
237 // 6. Send only the announcement to relay_b (triggers discovery) 214 // 6. Set up announcement on relay_b (triggers discovery of relay_a)
238 let client_b = TestClient::new(relay_b.url(), keys.clone()) 215 let (_announcement_b, _git_dir_b) =
239 .await 216 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
240 .expect("Failed to connect to relay_b"); 217 println!("Announcement set up on relay_b (should trigger discovery of relay_a)");
241
242 client_b
243 .send_event(&announcement)
244 .await
245 .expect("Failed to send announcement to relay_b");
246 println!("Announcement sent to relay_b (should trigger discovery)");
247
248 client_b.disconnect().await;
249 218
250 // 7. Wait for sync 219 // 7. Wait for sync
251 println!("Waiting 3s for Layer 2 sync..."); 220 println!("Waiting 3s for Layer 2 sync...");
@@ -327,65 +296,32 @@ async fn test_recursive_relay_discovery_via_announcements_with_historic_sync() {
327 let keys_x = Keys::generate(); 296 let keys_x = Keys::generate();
328 let keys_y = Keys::generate(); 297 let keys_y = Keys::generate();
329 298
330 // 3. Create announcement_x on relay_b (lists all three relays: A+B+C) 299 // 3. Set up announcement_x on relay_b (lists all three relays: A+B+C) with git data
331 let announcement_x = create_repo_announcement( 300 let domains_x = vec![relay_a.domain(), relay_b.domain(), relay_c.domain()];
332 &keys_x, 301 let domain_refs_x: Vec<&str> = domains_x.iter().map(|s| s.as_str()).collect();
333 &[&relay_a.domain(), &relay_b.domain(), &relay_c.domain()],
334 "repo-x-all-relays",
335 );
336 let announcement_x_id = announcement_x.id;
337 println!("Created announcement_x {} listing A+B+C", announcement_x_id);
338 for tag in announcement_x.tags.iter() {
339 println!(" Tag: {:?}", tag.as_slice());
340 }
341
342 // 4. Create announcement_y on relay_c (lists only A+C, NOT B)
343 let announcement_y = create_repo_announcement(
344 &keys_y,
345 &[&relay_a.domain(), &relay_c.domain()],
346 "repo-y-ac-only",
347 );
348 let announcement_y_id = announcement_y.id;
349 println!(
350 "Created announcement_y {} listing A+C only",
351 announcement_y_id
352 );
353 for tag in announcement_y.tags.iter() {
354 println!(" Tag: {:?}", tag.as_slice());
355 }
356
357 // 5. Send announcement_x to relay_b only
358 let client_b = TestClient::new(relay_b.url(), keys_x.clone())
359 .await
360 .expect("Failed to connect to relay_b");
361
362 client_b
363 .send_event(&announcement_x)
364 .await
365 .expect("Failed to send announcement_x to relay_b");
366 println!("announcement_x sent to relay_b");
367
368 client_b.disconnect().await;
369 302
370 // 6. Send announcement_y to relay_c only 303 let (announcement_x, _git_dir_b) =
371 let client_c = TestClient::new(relay_c.url(), keys_y.clone()) 304 setup_announcement_on_relay(&relay_b, &keys_x, &domain_refs_x, "repo-x-all-relays").await;
372 .await 305 let announcement_x_id = announcement_x.id;
373 .expect("Failed to connect to relay_c"); 306 println!("announcement_x {} set up on relay_b with git data (listing A+B+C)", announcement_x_id);
374 307
375 client_c 308 // 4. Set up announcement_y on relay_c (lists only A+C, NOT B) with git data
376 .send_event(&announcement_y) 309 let domains_y = vec![relay_a.domain(), relay_c.domain()];
377 .await 310 let domain_refs_y: Vec<&str> = domains_y.iter().map(|s| s.as_str()).collect();
378 .expect("Failed to send announcement_y to relay_c");
379 println!("announcement_y sent to relay_c");
380 311
381 client_c.disconnect().await; 312 let (announcement_y, _git_dir_c) =
313 setup_announcement_on_relay(&relay_c, &keys_y, &domain_refs_y, "repo-y-ac-only").await;
314 let announcement_y_id = announcement_y.id;
315 println!("announcement_y {} set up on relay_c with git data (listing A+C only)", announcement_y_id);
382 316
383 // 7. Wait for relay_a to: 317 // 7. Wait for relay_a to:
384 // - Sync from bootstrap relay_b (gets announcement_x) 318 // - Sync from bootstrap relay_b (gets announcement_x)
385 // - Discover relay_c from announcement_x's relays tag 319 // - Discover relay_c from announcement_x's relays tag
386 // - Connect to relay_c and sync announcement_y 320 // - Connect to relay_c and sync announcement_y
387 println!("Waiting 5s for recursive relay discovery..."); 321 // With purgatory, each relay needs to: sync announcement → purgatory → sync state event →
388 tokio::time::sleep(Duration::from_secs(5)).await; 322 // immediate purgatory sync → fetch git data → promote. Allow extra time for this.
323 println!("Waiting 12s for recursive relay discovery (with purgatory flow)...");
324 tokio::time::sleep(Duration::from_secs(12)).await;
389 325
390 // 8. Verify announcement_x was synced to relay_a (from bootstrap relay_b) 326 // 8. Verify announcement_x was synced to relay_a (from bootstrap relay_b)
391 let filter_x = Filter::new() 327 let filter_x = Filter::new()
diff --git a/tests/sync/historic_sync.rs b/tests/sync/historic_sync.rs
index aec2819..723b776 100644
--- a/tests/sync/historic_sync.rs
+++ b/tests/sync/historic_sync.rs
@@ -224,34 +224,24 @@ async fn test_history_sync_without_negentropy() {
224 // Create keys 224 // Create keys
225 let keys = Keys::generate(); 225 let keys = Keys::generate();
226 226
227 // Create announcement listing BOTH relay domains 227 // Set up announcement on source with git data
228 // This event will exist on source BEFORE syncing relay ever connects 228 // (purgatory requires git data before announcements are accepted)
229 let announcement = create_repo_announcement( 229 let domains = vec![source.domain(), syncing_domain.clone()];
230 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
231 let (announcement, _git_dir) = setup_announcement_on_relay(
232 &source,
230 &keys, 233 &keys,
231 &[&source.domain(), &syncing_domain], 234 &domain_refs,
232 "test-repo-history-no-negentropy", 235 "test-repo-history-no-negentropy",
233 ); 236 )
237 .await;
234 let announcement_id = announcement.id; 238 let announcement_id = announcement.id;
235 239
236 println!( 240 println!(
237 "Created announcement {} (kind {})", 241 "Announcement {} set up on source with git data (event exists BEFORE syncing relay connects)",
238 announcement_id, 242 announcement_id
239 announcement.kind.as_u16()
240 ); 243 );
241 244
242 // Send announcement to source (event now exists BEFORE syncing relay connects)
243 let client = TestClient::new(source.url(), keys.clone())
244 .await
245 .expect("Failed to connect to source");
246
247 client
248 .send_event(&announcement)
249 .await
250 .expect("Failed to send announcement to source");
251 println!("Announcement sent to source (event exists BEFORE syncing relay connects)");
252
253 client.disconnect().await;
254
255 // Wait to ensure event is stored 245 // Wait to ensure event is stored
256 tokio::time::sleep(Duration::from_millis(500)).await; 246 tokio::time::sleep(Duration::from_millis(500)).await;
257 247
diff --git a/tests/sync/live_sync.rs b/tests/sync/live_sync.rs
index 8ee3119..4289004 100644
--- a/tests/sync/live_sync.rs
+++ b/tests/sync/live_sync.rs
@@ -56,43 +56,24 @@ async fn test_live_sync_layer2_events() {
56 // 3. Create test keys 56 // 3. Create test keys
57 let keys = Keys::generate(); 57 let keys = Keys::generate();
58 58
59 // 4. Create a repository announcement that lists BOTH relays 59 // 4. Create a repository announcement on both relays with git data
60 // (purgatory requires git data before announcements are accepted)
60 let repo_id = "test-repo-live-l2"; 61 let repo_id = "test-repo-live-l2";
61 let announcement = 62 let domains = vec![relay_a.domain(), relay_b.domain()];
62 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 63 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
63 64
64 println!( 65 let (_announcement, _git_dir_a) =
65 "Created announcement {} (kind {})", 66 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
66 announcement.id, 67 println!("Announcement set up on relay_a with git data");
67 announcement.kind.as_u16()
68 );
69
70 // 5. Send announcement to relay_a
71 let client_a = TestClient::new(relay_a.url(), keys.clone())
72 .await
73 .expect("Failed to connect to relay_a");
74
75 client_a
76 .send_event(&announcement)
77 .await
78 .expect("Failed to send announcement to relay_a");
79 println!("Announcement sent to relay_a");
80
81 // 6. Send announcement to relay_b (triggers discovery of relay_a)
82 let client_b = TestClient::new(relay_b.url(), keys.clone())
83 .await
84 .expect("Failed to connect to relay_b");
85 68
86 client_b 69 let (_announcement_b, _git_dir_b) =
87 .send_event(&announcement) 70 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
88 .await 71 println!("Announcement set up on relay_b with git data (triggers discovery)");
89 .expect("Failed to send announcement to relay_b");
90 println!("Announcement sent to relay_b (triggers discovery)");
91 72
92 // 7. Wait for discovery to complete 73 // 5. Wait for discovery to complete
93 tokio::time::sleep(Duration::from_secs(1)).await; 74 tokio::time::sleep(Duration::from_secs(1)).await;
94 75
95 // 8. Create and send a Layer 2 issue event (using helper) 76 // 6. Create and send a Layer 2 issue event (using helper)
96 let repo_coordinate = repo_coord(&keys, repo_id); 77 let repo_coordinate = repo_coord(&keys, repo_id);
97 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync") 78 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync")
98 .expect("Failed to create issue event"); 79 .expect("Failed to create issue event");
@@ -104,6 +85,10 @@ async fn test_live_sync_layer2_events() {
104 } 85 }
105 86
106 // Send issue to relay_a only 87 // Send issue to relay_a only
88 let client_a = TestClient::new(relay_a.url(), keys.clone())
89 .await
90 .expect("Failed to connect to relay_a");
91
107 client_a 92 client_a
108 .send_event(&issue) 93 .send_event(&issue)
109 .await 94 .await
@@ -111,7 +96,6 @@ async fn test_live_sync_layer2_events() {
111 println!("Issue sent to relay_a"); 96 println!("Issue sent to relay_a");
112 97
113 client_a.disconnect().await; 98 client_a.disconnect().await;
114 client_b.disconnect().await;
115 99
116 // 9. Wait and verify event syncs to relay_b 100 // 9. Wait and verify event syncs to relay_b
117 let filter = Filter::new() 101 let filter = Filter::new()
@@ -166,30 +150,19 @@ async fn test_live_sync_layer3_events() {
166 150
167 let keys = Keys::generate(); 151 let keys = Keys::generate();
168 152
169 // 2. Create and send repository announcement to both relays 153 // 2. Create and send repository announcement to both relays with git data
154 // (purgatory requires git data before announcements are accepted)
170 let repo_id = "test-repo-live-l3"; 155 let repo_id = "test-repo-live-l3";
171 let announcement = 156 let domains = vec![relay_a.domain(), relay_b.domain()];
172 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 157 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
173 158
174 let client_a = TestClient::new(relay_a.url(), keys.clone()) 159 let (_announcement, _git_dir_a) =
175 .await 160 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
176 .expect("Failed to connect to relay_a"); 161 println!("Announcement set up on relay_a with git data");
177 162
178 let client_b = TestClient::new(relay_b.url(), keys.clone()) 163 let (_announcement_b, _git_dir_b) =
179 .await 164 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
180 .expect("Failed to connect to relay_b"); 165 println!("Announcement set up on relay_b with git data (triggers discovery)");
181
182 client_a
183 .send_event(&announcement)
184 .await
185 .expect("Failed to send announcement to relay_a");
186 println!("Announcement sent to relay_a");
187
188 client_b
189 .send_event(&announcement)
190 .await
191 .expect("Failed to send announcement to relay_b");
192 println!("Announcement sent to relay_b (triggers discovery)");
193 166
194 // 3. Wait for discovery 167 // 3. Wait for discovery
195 tokio::time::sleep(Duration::from_secs(1)).await; 168 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -200,6 +173,10 @@ async fn test_live_sync_layer3_events() {
200 .expect("Failed to create issue"); 173 .expect("Failed to create issue");
201 let issue_id = issue.id; 174 let issue_id = issue.id;
202 175
176 let client_a = TestClient::new(relay_a.url(), keys.clone())
177 .await
178 .expect("Failed to connect to relay_a");
179
203 client_a 180 client_a
204 .send_event(&issue) 181 .send_event(&issue)
205 .await 182 .await
@@ -243,7 +220,6 @@ async fn test_live_sync_layer3_events() {
243 println!("Issue synced to relay_b: {}", issue_synced); 220 println!("Issue synced to relay_b: {}", issue_synced);
244 221
245 client_a.disconnect().await; 222 client_a.disconnect().await;
246 client_b.disconnect().await;
247 223
248 // 7. Wait and verify comment syncs to relay_b 224 // 7. Wait and verify comment syncs to relay_b
249 let comment_filter = Filter::new() 225 let comment_filter = Filter::new()
@@ -343,29 +319,17 @@ async fn test_live_sync_event_ordering() {
343 319
344 let keys = Keys::generate(); 320 let keys = Keys::generate();
345 321
346 // 2. Create and send repository announcement to both relays 322 // 2. Create and send repository announcement to both relays with git data
323 // (purgatory requires git data before announcements are accepted)
347 let repo_id = "test-repo-ordering"; 324 let repo_id = "test-repo-ordering";
348 let announcement = 325 let domains = vec![relay_a.domain(), relay_b.domain()];
349 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 326 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
350 327
351 let client_a = TestClient::new(relay_a.url(), keys.clone()) 328 let (_announcement, _git_dir_a) =
352 .await 329 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
353 .expect("Failed to connect to relay_a"); 330 let (_announcement_b, _git_dir_b) =
354 331 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
355 let client_b = TestClient::new(relay_b.url(), keys.clone()) 332 println!("Announcements set up on both relays with git data");
356 .await
357 .expect("Failed to connect to relay_b");
358
359 client_a
360 .send_event(&announcement)
361 .await
362 .expect("Failed to send announcement to relay_a");
363
364 client_b
365 .send_event(&announcement)
366 .await
367 .expect("Failed to send announcement to relay_b");
368 println!("Announcements sent to both relays");
369 333
370 // 3. Wait for discovery 334 // 3. Wait for discovery
371 tokio::time::sleep(Duration::from_secs(1)).await; 335 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -375,6 +339,10 @@ async fn test_live_sync_event_ordering() {
375 let mut issue_ids = Vec::new(); 339 let mut issue_ids = Vec::new();
376 let mut expected_order_timestamps = Vec::new(); 340 let mut expected_order_timestamps = Vec::new();
377 341
342 let client_a = TestClient::new(relay_a.url(), keys.clone())
343 .await
344 .expect("Failed to connect to relay_a");
345
378 for i in 1..=3 { 346 for i in 1..=3 {
379 let issue = build_layer2_issue_event( 347 let issue = build_layer2_issue_event(
380 &keys, 348 &keys,
@@ -402,7 +370,6 @@ async fn test_live_sync_event_ordering() {
402 } 370 }
403 371
404 client_a.disconnect().await; 372 client_a.disconnect().await;
405 client_b.disconnect().await;
406 373
407 // 5. Wait for all events to sync 374 // 5. Wait for all events to sync
408 tokio::time::sleep(Duration::from_secs(3)).await; 375 tokio::time::sleep(Duration::from_secs(3)).await;
diff --git a/tests/sync/maintainer_reprocessing.rs b/tests/sync/maintainer_reprocessing.rs
index df1bf78..266a437 100644
--- a/tests/sync/maintainer_reprocessing.rs
+++ b/tests/sync/maintainer_reprocessing.rs
@@ -7,7 +7,10 @@ use std::time::Duration;
7 7
8use nostr_sdk::prelude::*; 8use nostr_sdk::prelude::*;
9 9
10use crate::common::{sync_helpers::*, TestRelay}; 10use crate::common::{
11 sync_helpers::*,
12 TestRelay,
13};
11 14
12/// Test that maintainer announcements are re-processed immediately when owner announcement accepted 15/// Test that maintainer announcements are re-processed immediately when owner announcement accepted
13/// 16///
@@ -37,10 +40,12 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
37 40
38 let start = std::time::Instant::now(); 41 let start = std::time::Instant::now();
39 42
40 // Step 1: Send maintainer announcement to relay_a (will be rejected - doesn't list relay_b) 43 // Step 1: Send maintainer announcement to relay_a (will be rejected by relay_b - doesn't list relay_b)
41 let client_a = TestClient::new(relay_a.url(), maintainer_keys.clone()) 44 // Use HTTP clone URL pointing to relay_a's git endpoint so it can be released from purgatory
42 .await 45 let maintainer_npub = maintainer_keys
43 .expect("Failed to connect to relay_a"); 46 .public_key()
47 .to_bech32()
48 .expect("Failed to get npub");
44 49
45 let maintainer_announcement = 50 let maintainer_announcement =
46 EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") 51 EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository")
@@ -48,27 +53,50 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
48 Tag::identifier(identifier), 53 Tag::identifier(identifier),
49 Tag::custom( 54 Tag::custom(
50 TagKind::custom("clone"), 55 TagKind::custom("clone"),
51 vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], 56 vec![format!(
57 "http://{}/{}/{}.git",
58 relay_a.domain(),
59 maintainer_npub,
60 identifier
61 )],
52 ), 62 ),
53 Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), 63 Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]),
54 ]) 64 ])
55 .sign_with_keys(&maintainer_keys) 65 .sign_with_keys(&maintainer_keys)
56 .unwrap(); 66 .unwrap();
57 67
58 client_a.send_event(&maintainer_announcement).await.unwrap(); 68 send_to_relay(&relay_a, &maintainer_announcement)
69 .await
70 .unwrap();
59 println!("✓ Maintainer announcement sent to relay_a"); 71 println!("✓ Maintainer announcement sent to relay_a");
60 72
61 // Step 2: Send owner announcement to relay_b (lists relay_a + maintainer) 73 // Push git data for maintainer's repo to relay_a → releases maintainer announcement from purgatory
62 let client_b = TestClient::new(relay_b.url(), owner_keys.clone()) 74 let _git_dir_maintainer = push_git_data_to_relay(
63 .await 75 &relay_a,
64 .expect("Failed to connect to relay_b"); 76 &maintainer_keys,
77 identifier,
78 &[&relay_a.domain()],
79 )
80 .await;
81 println!("✓ Maintainer git data pushed to relay_a (announcement released from purgatory)");
82
83 // Step 2: Set up owner announcement on relay_b (lists relay_a + maintainer) with git data
84 let owner_npub = owner_keys
85 .public_key()
86 .to_bech32()
87 .expect("Failed to get npub");
65 88
66 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") 89 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
67 .tags(vec![ 90 .tags(vec![
68 Tag::identifier(identifier), 91 Tag::identifier(identifier),
69 Tag::custom( 92 Tag::custom(
70 TagKind::custom("clone"), 93 TagKind::custom("clone"),
71 vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], 94 vec![format!(
95 "http://{}/{}/{}.git",
96 relay_b.domain(),
97 owner_npub,
98 identifier
99 )],
72 ), 100 ),
73 Tag::custom( 101 Tag::custom(
74 TagKind::custom("relays"), 102 TagKind::custom("relays"),
@@ -82,9 +110,14 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
82 .sign_with_keys(&owner_keys) 110 .sign_with_keys(&owner_keys)
83 .unwrap(); 111 .unwrap();
84 112
85 client_b.send_event(&owner_announcement).await.unwrap(); 113 send_to_relay(&relay_b, &owner_announcement).await.unwrap();
86 println!("✓ Owner announcement sent to relay_b"); 114 println!("✓ Owner announcement sent to relay_b");
87 115
116 // Push git data for owner's repo to relay_b → releases owner announcement from purgatory
117 let _git_dir_owner =
118 push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await;
119 println!("✓ Owner git data pushed to relay_b (announcement released from purgatory)");
120
88 // Step 3: Wait for sync and re-processing (relay_b discovers relay_a, syncs, re-processes) 121 // Step 3: Wait for sync and re-processing (relay_b discovers relay_a, syncs, re-processes)
89 tokio::time::sleep(Duration::from_secs(3)).await; 122 tokio::time::sleep(Duration::from_secs(3)).await;
90 123
@@ -114,15 +147,13 @@ async fn test_maintainer_announcement_reprocessed_immediately() {
114 147
115 // Step 5: Verify it happened quickly (not 24 hours!) 148 // Step 5: Verify it happened quickly (not 24 hours!)
116 assert!( 149 assert!(
117 elapsed.as_secs() < 10, 150 elapsed.as_secs() < 15,
118 "Re-processing should happen in <10 seconds, took {:?}", 151 "Re-processing should happen in <15 seconds, took {:?}",
119 elapsed 152 elapsed
120 ); 153 );
121 154
122 println!("✅ Maintainer announcement re-processed in {:?}", elapsed); 155 println!("✅ Maintainer announcement re-processed in {:?}", elapsed);
123 156
124 client_a.disconnect().await;
125 client_b.disconnect().await;
126 relay_a.stop().await; 157 relay_a.stop().await;
127 relay_b.stop().await; 158 relay_b.stop().await;
128} 159}
@@ -253,15 +284,18 @@ async fn test_multiple_maintainers_all_reprocessed() {
253 284
254 let identifier = "multi-maintainer-repo"; 285 let identifier = "multi-maintainer-repo";
255 286
256 // Step 1: Send three maintainer announcements to relay_a 287 // Step 1: Send three maintainer announcements to relay_a with git data
257 let client_a = TestClient::new(relay_a.url(), maintainer1_keys.clone()) 288 // (purgatory requires git data before announcements are accepted)
258 .await 289 let mut git_dirs_maintainers = Vec::new();
259 .expect("Failed to connect to relay_a");
260
261 for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys] 290 for (idx, maintainer_keys) in [&maintainer1_keys, &maintainer2_keys, &maintainer3_keys]
262 .iter() 291 .iter()
263 .enumerate() 292 .enumerate()
264 { 293 {
294 let m_npub = maintainer_keys
295 .public_key()
296 .to_bech32()
297 .expect("Failed to get npub");
298
265 let announcement = EventBuilder::new( 299 let announcement = EventBuilder::new(
266 Kind::GitRepoAnnouncement, 300 Kind::GitRepoAnnouncement,
267 format!("Maintainer {} repository", idx + 1), 301 format!("Maintainer {} repository", idx + 1),
@@ -270,28 +304,45 @@ async fn test_multiple_maintainers_all_reprocessed() {
270 Tag::identifier(identifier), 304 Tag::identifier(identifier),
271 Tag::custom( 305 Tag::custom(
272 TagKind::custom("clone"), 306 TagKind::custom("clone"),
273 vec![format!("https://{}/{}.git", relay_a.domain(), identifier)], 307 vec![format!(
308 "http://{}/{}/{}.git",
309 relay_a.domain(),
310 m_npub,
311 identifier
312 )],
274 ), 313 ),
275 Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]), 314 Tag::custom(TagKind::custom("relays"), vec![relay_a.url().to_string()]),
276 ]) 315 ])
277 .sign_with_keys(maintainer_keys) 316 .sign_with_keys(maintainer_keys)
278 .unwrap(); 317 .unwrap();
279 318
280 client_a.send_event(&announcement).await.unwrap(); 319 send_to_relay(&relay_a, &announcement).await.unwrap();
320
321 // Push git data to release each maintainer's announcement from purgatory
322 let git_dir =
323 push_git_data_to_relay(&relay_a, maintainer_keys, identifier, &[&relay_a.domain()])
324 .await;
325 git_dirs_maintainers.push(git_dir);
281 } 326 }
282 println!("✓ Three maintainer announcements sent to relay_a"); 327 println!("✓ Three maintainer announcements sent to relay_a with git data");
283 328
284 // Step 2: Send owner announcement to relay_b (lists relay_a + all three maintainers) 329 // Step 2: Send owner announcement to relay_b (lists relay_a + all three maintainers)
285 let client_b = TestClient::new(relay_b.url(), owner_keys.clone()) 330 let owner_npub = owner_keys
286 .await 331 .public_key()
287 .expect("Failed to connect to relay_b"); 332 .to_bech32()
333 .expect("Failed to get npub");
288 334
289 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") 335 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
290 .tags(vec![ 336 .tags(vec![
291 Tag::identifier(identifier), 337 Tag::identifier(identifier),
292 Tag::custom( 338 Tag::custom(
293 TagKind::custom("clone"), 339 TagKind::custom("clone"),
294 vec![format!("https://{}/{}.git", relay_b.domain(), identifier)], 340 vec![format!(
341 "http://{}/{}/{}.git",
342 relay_b.domain(),
343 owner_npub,
344 identifier
345 )],
295 ), 346 ),
296 Tag::custom( 347 Tag::custom(
297 TagKind::custom("relays"), 348 TagKind::custom("relays"),
@@ -309,9 +360,14 @@ async fn test_multiple_maintainers_all_reprocessed() {
309 .sign_with_keys(&owner_keys) 360 .sign_with_keys(&owner_keys)
310 .unwrap(); 361 .unwrap();
311 362
312 client_b.send_event(&owner_announcement).await.unwrap(); 363 send_to_relay(&relay_b, &owner_announcement).await.unwrap();
313 println!("✓ Owner announcement sent to relay_b"); 364 println!("✓ Owner announcement sent to relay_b");
314 365
366 // Push git data for owner to relay_b → releases owner announcement from purgatory
367 let _git_dir_owner =
368 push_git_data_to_relay(&relay_b, &owner_keys, identifier, &[&relay_b.domain()]).await;
369 println!("✓ Owner git data pushed to relay_b (announcement released from purgatory)");
370
315 // Step 3: Wait for sync and re-processing 371 // Step 3: Wait for sync and re-processing
316 tokio::time::sleep(Duration::from_secs(3)).await; 372 tokio::time::sleep(Duration::from_secs(3)).await;
317 373
@@ -333,8 +389,6 @@ async fn test_multiple_maintainers_all_reprocessed() {
333 389
334 println!("✅ All three maintainer announcements re-processed successfully"); 390 println!("✅ All three maintainer announcements re-processed successfully");
335 391
336 client_a.disconnect().await;
337 client_b.disconnect().await;
338 relay_a.stop().await; 392 relay_a.stop().await;
339 relay_b.stop().await; 393 relay_b.stop().await;
340} 394}
@@ -356,12 +410,8 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
356 410
357 let identifier = "invalid-maintainer-repo"; 411 let identifier = "invalid-maintainer-repo";
358 412
359 // Create client using TestClient helper
360 let client = TestClient::new(relay.url(), owner_keys.clone())
361 .await
362 .expect("Failed to connect to relay");
363
364 // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay) 413 // Step 1: Send maintainer announcement (will be rejected - doesn't list our relay)
414 // This one uses example.com clone URL - it goes to purgatory on relay, never promoted
365 let maintainer_announcement = 415 let maintainer_announcement =
366 EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository") 416 EventBuilder::new(Kind::GitRepoAnnouncement, "Maintainer's repository")
367 .tags(vec![ 417 .tags(vec![
@@ -378,17 +428,28 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
378 .sign_with_keys(&maintainer_keys) 428 .sign_with_keys(&maintainer_keys)
379 .unwrap(); 429 .unwrap();
380 430
381 // Send maintainer announcement - expect it to be rejected 431 // Send maintainer announcement - expect it to be rejected (purgatory / policy)
382 let _ = client.send_event(&maintainer_announcement).await; 432 send_to_relay(&relay, &maintainer_announcement).await.ok();
383 tokio::time::sleep(Duration::from_millis(200)).await; 433 tokio::time::sleep(Duration::from_millis(200)).await;
384 434
385 // Step 2: Send owner announcement with INVALID maintainer hex 435 // Step 2: Set up owner announcement with INVALID maintainer hex and git data
436 // Use HTTP clone URL to relay's git endpoint so it can be released from purgatory
437 let owner_npub = owner_keys
438 .public_key()
439 .to_bech32()
440 .expect("Failed to get npub");
441
386 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository") 442 let owner_announcement = EventBuilder::new(Kind::GitRepoAnnouncement, "Owner's repository")
387 .tags(vec![ 443 .tags(vec![
388 Tag::identifier(identifier), 444 Tag::identifier(identifier),
389 Tag::custom( 445 Tag::custom(
390 TagKind::custom("clone"), 446 TagKind::custom("clone"),
391 vec![format!("https://{}/{}.git", relay.domain(), identifier)], 447 vec![format!(
448 "http://{}/{}/{}.git",
449 relay.domain(),
450 owner_npub,
451 identifier
452 )],
392 ), 453 ),
393 Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]), 454 Tag::custom(TagKind::custom("relays"), vec![relay.url().to_string()]),
394 Tag::custom( 455 Tag::custom(
@@ -399,7 +460,14 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
399 .sign_with_keys(&owner_keys) 460 .sign_with_keys(&owner_keys)
400 .unwrap(); 461 .unwrap();
401 462
402 client.send_event(&owner_announcement).await.unwrap(); 463 send_to_relay(&relay, &owner_announcement).await.unwrap();
464
465 // Push git data to relay → releases owner announcement from purgatory
466 let _git_dir =
467 push_git_data_to_relay(&relay, &owner_keys, identifier, &[&relay.domain()]).await;
468 println!("✓ Owner git data pushed to relay (announcement released from purgatory)");
469
470 // Wait for processing
403 tokio::time::sleep(Duration::from_millis(500)).await; 471 tokio::time::sleep(Duration::from_millis(500)).await;
404 472
405 // Step 3: Verify owner announcement accepted, maintainer not re-processed 473 // Step 3: Verify owner announcement accepted, maintainer not re-processed
@@ -429,6 +497,5 @@ async fn test_invalid_maintainer_pubkey_handled_gracefully() {
429 497
430 println!("✅ Invalid maintainer pubkey handled gracefully without panic"); 498 println!("✅ Invalid maintainer pubkey handled gracefully without panic");
431 499
432 client.disconnect().await;
433 relay.stop().await; 500 relay.stop().await;
434} 501}
diff --git a/tests/sync/metrics.rs b/tests/sync/metrics.rs
index e8c75c7..e973bbb 100644
--- a/tests/sync/metrics.rs
+++ b/tests/sync/metrics.rs
@@ -16,8 +16,8 @@ use nostr_sdk::prelude::*;
16 16
17use crate::common::{ 17use crate::common::{
18 sync_helpers::{ 18 sync_helpers::{
19 create_repo_announcement, fetch_metrics, wait_for_sync_connection, MetricsTestHarness, 19 create_repo_announcement, fetch_metrics, setup_announcement_on_relay,
20 ParsedMetrics, TestClient, 20 wait_for_sync_connection, MetricsTestHarness, ParsedMetrics, TestClient,
21 }, 21 },
22 TestRelay, 22 TestRelay,
23}; 23};
@@ -224,16 +224,17 @@ async fn test_startup_sync_event_count() {
224 // 3. Create test keys 224 // 3. Create test keys
225 let keys = Keys::generate(); 225 let keys = Keys::generate();
226 226
227 // 4. Create an announcement that lists BOTH relays (required for discovery) 227 // 4. Set up announcement on SOURCE relay with git data
228 let announcement = create_repo_announcement( 228 // (purgatory requires git data before announcements are accepted)
229 &keys, 229 let repo_id = "test-repo-metrics";
230 &[&source_relay.domain(), &syncing_relay.domain()], 230 let domains = vec![source_relay.domain(), syncing_relay.domain()];
231 "test-repo-metrics", 231 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
232 ); 232
233 let (announcement, _git_dir_source) =
234 setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
233 println!( 235 println!(
234 "Created announcement {} (kind {})", 236 "Announcement {} set up on source relay with git data",
235 announcement.id, 237 announcement.id
236 announcement.kind.as_u16()
237 ); 238 );
238 239
239 // 5. Build the repo coordinate for the 'a' tag in the patches 240 // 5. Build the repo coordinate for the 'a' tag in the patches
@@ -241,7 +242,7 @@ async fn test_startup_sync_event_count() {
241 "{}:{}:{}", 242 "{}:{}:{}",
242 Kind::GitRepoAnnouncement.as_u16(), 243 Kind::GitRepoAnnouncement.as_u16(),
243 keys.public_key().to_hex(), 244 keys.public_key().to_hex(),
244 "test-repo-metrics" 245 repo_id
245 ); 246 );
246 247
247 // 6. Create 3 patch events (Layer 2) that reference the announcement 248 // 6. Create 3 patch events (Layer 2) that reference the announcement
@@ -257,17 +258,11 @@ async fn test_startup_sync_event_count() {
257 .collect(); 258 .collect();
258 println!("Created {} patches", patches.len()); 259 println!("Created {} patches", patches.len());
259 260
260 // 7. Send announcement + patches to SOURCE relay ONLY 261 // 7. Send patches to SOURCE relay
261 let source_client = TestClient::new(source_relay.url(), keys.clone()) 262 let source_client = TestClient::new(source_relay.url(), keys.clone())
262 .await 263 .await
263 .expect("Failed to connect to source relay"); 264 .expect("Failed to connect to source relay");
264 265
265 source_client
266 .send_event(&announcement)
267 .await
268 .expect("Failed to send announcement to source");
269 println!("Announcement sent to source relay");
270
271 for patch in &patches { 266 for patch in &patches {
272 source_client 267 source_client
273 .send_event(patch) 268 .send_event(patch)
@@ -277,17 +272,10 @@ async fn test_startup_sync_event_count() {
277 println!("Patches sent to source relay"); 272 println!("Patches sent to source relay");
278 source_client.disconnect().await; 273 source_client.disconnect().await;
279 274
280 // 8. Send announcement to SYNCING relay (triggers discovery of source relay) 275 // 8. Set up announcement on SYNCING relay (triggers discovery of source relay)
281 let syncing_client = TestClient::new(syncing_relay.url(), keys.clone()) 276 let (_announcement_syncing, _git_dir_syncing) =
282 .await 277 setup_announcement_on_relay(&syncing_relay, &keys, &domain_refs, repo_id).await;
283 .expect("Failed to connect to syncing relay"); 278 println!("Announcement set up on syncing relay (triggers discovery of source)");
284
285 syncing_client
286 .send_event(&announcement)
287 .await
288 .expect("Failed to send announcement to syncing relay");
289 println!("Announcement sent to syncing relay (triggers discovery of source)");
290 syncing_client.disconnect().await;
291 279
292 // 9. Wait for discovery + sync to complete 280 // 9. Wait for discovery + sync to complete
293 println!("Waiting 5s for discovery and sync..."); 281 println!("Waiting 5s for discovery and sync...");
@@ -404,18 +392,35 @@ async fn test_connection_failure_increments_counter() {
404/// Test that live sync events are counted in metrics. 392/// Test that live sync events are counted in metrics.
405/// 393///
406/// This test validates that events received via live subscription 394/// This test validates that events received via live subscription
407/// (after sync connection is established) are counted separately 395/// (after sync connection is established) are counted in metrics.
408/// from startup/bootstrap events. 396/// Uses Layer 2 patch events (not announcements) to avoid purgatory,
397/// since Layer 2 events are accepted directly to the DB.
409#[tokio::test] 398#[tokio::test]
410async fn test_live_sync_event_count() { 399async fn test_live_sync_event_count() {
411 let mut harness = MetricsTestHarness::with_sources(1).await;
412
413 // Pre-allocate syncing relay port to include in announcements 400 // Pre-allocate syncing relay port to include in announcements
414 let sync_port = TestRelay::find_free_port(); 401 let sync_port = TestRelay::find_free_port();
415 let sync_domain = format!("127.0.0.1:{}", sync_port); 402 let sync_domain = format!("127.0.0.1:{}", sync_port);
416 403
404 // Start source relay
405 let source_relay = TestRelay::start().await;
406 println!("Source relay started at {}", source_relay.url());
407
408 // Set up announcement on source relay BEFORE starting syncing relay
409 // This allows discovery when syncing relay connects
410 let keys = Keys::generate();
411 let repo_id = "live-metrics-repo";
412 let domains = vec![source_relay.domain(), sync_domain.clone()];
413 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
414
415 let (_announcement, _git_dir) =
416 setup_announcement_on_relay(&source_relay, &keys, &domain_refs, repo_id).await;
417 println!("Announcement set up on source relay with git data");
418
417 // Start syncing relay with pre-allocated port 419 // Start syncing relay with pre-allocated port
418 harness.start_syncing_relay_on_port(0, sync_port).await; 420 let syncing_relay =
421 TestRelay::start_on_port_with_options(sync_port, Some(source_relay.url().to_string()), false)
422 .await;
423 println!("Syncing relay started at {}", syncing_relay.url());
419 424
420 // Wait for sync connection to be fully established with EOSE received 425 // Wait for sync connection to be fully established with EOSE received
421 // This ensures we're in "live" mode before submitting test events 426 // This ensures we're in "live" mode before submitting test events
@@ -424,33 +429,61 @@ async fn test_live_sync_event_count() {
424 .await 429 .await
425 .expect("Sync connection should be established"); 430 .expect("Sync connection should be established");
426 431
427 // Additional small delay to ensure EOSE has been processed 432 // Additional delay to ensure purgatory promotion completes on syncing relay
428 tokio::time::sleep(Duration::from_millis(500)).await; 433 tokio::time::sleep(Duration::from_secs(4)).await;
429 434
430 // Now add events - these should be "live" not "startup" 435 // Now add Layer 2 patch events (not announcements) - these are accepted immediately
431 // Include BOTH domains so events are accepted by both relays 436 // (Layer 2 events are accepted directly to DB, no purgatory)
432 let keys = Keys::generate(); 437 let repo_coord_str = format!(
433 let events: Vec<_> = (0..2) 438 "{}:{}:{}",
434 .map(|i| { 439 Kind::GitRepoAnnouncement.as_u16(),
435 create_repo_announcement( 440 keys.public_key().to_hex(),
436 &keys, 441 repo_id
437 &[&harness.source_domain(0), &sync_domain], 442 );
438 &format!("live-{}", i), 443
439 ) 444 let patch1 = create_event_referencing_repo(
440 }) 445 &keys,
441 .collect(); 446 &repo_coord_str,
442 harness.submit_events(0, &events).await.unwrap(); 447 Kind::GitPatch.as_u16(),
448 "Live test patch 1",
449 );
450 let patch2 = create_event_referencing_repo(
451 &keys,
452 &repo_coord_str,
453 Kind::GitPatch.as_u16(),
454 "Live test patch 2",
455 );
456
457 // Send patches to source AFTER sync connection established (live mode)
458 let client = TestClient::new(source_relay.url(), keys.clone())
459 .await
460 .expect("Failed to connect to source");
461 client.send_event(&patch1).await.expect("Failed to send patch 1");
462 client.send_event(&patch2).await.expect("Failed to send patch 2");
463 client.disconnect().await;
464 println!("Two patches sent to source relay (live mode)");
443 465
444 // Wait for live events to be processed and metrics updated 466 // Wait for live events to be processed and metrics updated
445 tokio::time::sleep(Duration::from_secs(4)).await; 467 tokio::time::sleep(Duration::from_secs(4)).await;
446 let metrics = harness.get_metrics().await.unwrap(); 468
469 // Fetch metrics from syncing relay
470 let raw_metrics = fetch_metrics(&sync_url)
471 .await
472 .expect("Failed to fetch metrics");
473 let metrics = ParsedMetrics::parse(&raw_metrics);
447 474
448 let synced_count = metrics.events_synced_total(); 475 let synced_count = metrics.events_synced_total();
449 println!("Events synced total: {:?}", synced_count); 476 println!("Events synced total: {:?}", synced_count);
450 477
451 assert_eq!(synced_count, Some(2), "Should have 2 synced events"); 478 // Cleanup
479 syncing_relay.stop().await;
480 source_relay.stop().await;
452 481
453 harness.stop_all().await; 482 assert!(
483 synced_count.is_some() && synced_count.unwrap() >= 2,
484 "Should have synced at least 2 events, got {:?}",
485 synced_count
486 );
454} 487}
455 488
456/// Test that relay connected status is tracked in metrics. 489/// Test that relay connected status is tracked in metrics.
diff --git a/tests/sync/tag_variations.rs b/tests/sync/tag_variations.rs
index 46b1203..021ad0e 100644
--- a/tests/sync/tag_variations.rs
+++ b/tests/sync/tag_variations.rs
@@ -55,30 +55,19 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
55 55
56 let keys = Keys::generate(); 56 let keys = Keys::generate();
57 57
58 // 2. Create and send repository announcement to both relays 58 // 2. Create and send repository announcement to both relays with git data
59 // (purgatory requires git data before announcements are accepted)
59 let repo_id = "test-repo-tag-8a"; 60 let repo_id = "test-repo-tag-8a";
60 let announcement = 61 let domains = vec![relay_a.domain(), relay_b.domain()];
61 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 62 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
62 63
63 let client_a = TestClient::new(relay_a.url(), keys.clone()) 64 let (_announcement, _git_dir_a) =
64 .await 65 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
65 .expect("Failed to connect to relay_a"); 66 println!("Announcement set up on relay_a with git data");
66
67 let client_b = TestClient::new(relay_b.url(), keys.clone())
68 .await
69 .expect("Failed to connect to relay_b");
70 67
71 client_a 68 let (_announcement_b, _git_dir_b) =
72 .send_event(&announcement) 69 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
73 .await 70 println!("Announcement set up on relay_b with git data (triggers discovery)");
74 .expect("Failed to send announcement to relay_a");
75 println!("Announcement sent to relay_a");
76
77 client_b
78 .send_event(&announcement)
79 .await
80 .expect("Failed to send announcement to relay_b");
81 println!("Announcement sent to relay_b (triggers discovery)");
82 71
83 // 3. Wait for discovery 72 // 3. Wait for discovery
84 tokio::time::sleep(Duration::from_secs(1)).await; 73 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -95,9 +84,10 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
95 issue_id, 84 issue_id,
96 issue.kind.as_u16() 85 issue.kind.as_u16()
97 ); 86 );
98 for tag in issue.tags.iter() { 87
99 println!(" Tag: {:?}", tag.as_slice()); 88 let client_a = TestClient::new(relay_a.url(), keys.clone())
100 } 89 .await
90 .expect("Failed to connect to relay_a");
101 91
102 client_a 92 client_a
103 .send_event(&issue) 93 .send_event(&issue)
@@ -106,7 +96,6 @@ async fn test_layer2_sync_with_lowercase_a_tag() {
106 println!("Issue sent to relay_a"); 96 println!("Issue sent to relay_a");
107 97
108 client_a.disconnect().await; 98 client_a.disconnect().await;
109 client_b.disconnect().await;
110 99
111 // 5. Wait and verify event syncs to relay_b 100 // 5. Wait and verify event syncs to relay_b
112 let filter = Filter::new() 101 let filter = Filter::new()
@@ -154,30 +143,18 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
154 143
155 let keys = Keys::generate(); 144 let keys = Keys::generate();
156 145
157 // 2. Create and send repository announcement to both relays 146 // 2. Create and send repository announcement to both relays with git data
158 let repo_id = "test-repo-tag-8b"; 147 let repo_id = "test-repo-tag-8b";
159 let announcement = 148 let domains = vec![relay_a.domain(), relay_b.domain()];
160 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 149 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
161
162 let client_a = TestClient::new(relay_a.url(), keys.clone())
163 .await
164 .expect("Failed to connect to relay_a");
165 150
166 let client_b = TestClient::new(relay_b.url(), keys.clone()) 151 let (_announcement, _git_dir_a) =
167 .await 152 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
168 .expect("Failed to connect to relay_b"); 153 println!("Announcement set up on relay_a with git data");
169 154
170 client_a 155 let (_announcement_b, _git_dir_b) =
171 .send_event(&announcement) 156 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
172 .await 157 println!("Announcement set up on relay_b with git data (triggers discovery)");
173 .expect("Failed to send announcement to relay_a");
174 println!("Announcement sent to relay_a");
175
176 client_b
177 .send_event(&announcement)
178 .await
179 .expect("Failed to send announcement to relay_b");
180 println!("Announcement sent to relay_b (triggers discovery)");
181 158
182 // 3. Wait for discovery 159 // 3. Wait for discovery
183 tokio::time::sleep(Duration::from_secs(1)).await; 160 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -197,9 +174,10 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
197 issue_id, 174 issue_id,
198 issue.kind.as_u16() 175 issue.kind.as_u16()
199 ); 176 );
200 for tag in issue.tags.iter() { 177
201 println!(" Tag: {:?}", tag.as_slice()); 178 let client_a = TestClient::new(relay_a.url(), keys.clone())
202 } 179 .await
180 .expect("Failed to connect to relay_a");
203 181
204 client_a 182 client_a
205 .send_event(&issue) 183 .send_event(&issue)
@@ -208,7 +186,6 @@ async fn test_layer2_sync_with_uppercase_a_tag() {
208 println!("Issue sent to relay_a"); 186 println!("Issue sent to relay_a");
209 187
210 client_a.disconnect().await; 188 client_a.disconnect().await;
211 client_b.disconnect().await;
212 189
213 // 5. Wait and verify event syncs to relay_b 190 // 5. Wait and verify event syncs to relay_b
214 let filter = Filter::new() 191 let filter = Filter::new()
@@ -255,30 +232,18 @@ async fn test_layer2_sync_with_q_tag() {
255 232
256 let keys = Keys::generate(); 233 let keys = Keys::generate();
257 234
258 // 2. Create and send repository announcement to both relays 235 // 2. Create and send repository announcement to both relays with git data
259 let repo_id = "test-repo-tag-8c"; 236 let repo_id = "test-repo-tag-8c";
260 let announcement = 237 let domains = vec![relay_a.domain(), relay_b.domain()];
261 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 238 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
262 239
263 let client_a = TestClient::new(relay_a.url(), keys.clone()) 240 let (_announcement, _git_dir_a) =
264 .await 241 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
265 .expect("Failed to connect to relay_a"); 242 println!("Announcement set up on relay_a with git data");
266 243
267 let client_b = TestClient::new(relay_b.url(), keys.clone()) 244 let (_announcement_b, _git_dir_b) =
268 .await 245 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
269 .expect("Failed to connect to relay_b"); 246 println!("Announcement set up on relay_b with git data (triggers discovery)");
270
271 client_a
272 .send_event(&announcement)
273 .await
274 .expect("Failed to send announcement to relay_a");
275 println!("Announcement sent to relay_a");
276
277 client_b
278 .send_event(&announcement)
279 .await
280 .expect("Failed to send announcement to relay_b");
281 println!("Announcement sent to relay_b (triggers discovery)");
282 247
283 // 3. Wait for discovery 248 // 3. Wait for discovery
284 tokio::time::sleep(Duration::from_secs(1)).await; 249 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -294,9 +259,10 @@ async fn test_layer2_sync_with_q_tag() {
294 issue_id, 259 issue_id,
295 issue.kind.as_u16() 260 issue.kind.as_u16()
296 ); 261 );
297 for tag in issue.tags.iter() { 262
298 println!(" Tag: {:?}", tag.as_slice()); 263 let client_a = TestClient::new(relay_a.url(), keys.clone())
299 } 264 .await
265 .expect("Failed to connect to relay_a");
300 266
301 client_a 267 client_a
302 .send_event(&issue) 268 .send_event(&issue)
@@ -305,7 +271,6 @@ async fn test_layer2_sync_with_q_tag() {
305 println!("Issue sent to relay_a"); 271 println!("Issue sent to relay_a");
306 272
307 client_a.disconnect().await; 273 client_a.disconnect().await;
308 client_b.disconnect().await;
309 274
310 // 5. Wait and verify event syncs to relay_b 275 // 5. Wait and verify event syncs to relay_b
311 let filter = Filter::new() 276 let filter = Filter::new()
@@ -362,30 +327,18 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
362 327
363 let keys = Keys::generate(); 328 let keys = Keys::generate();
364 329
365 // 2. Create and send repository announcement to both relays 330 // 2. Create and send repository announcement to both relays with git data
366 let repo_id = "test-repo-tag-9a"; 331 let repo_id = "test-repo-tag-9a";
367 let announcement = 332 let domains = vec![relay_a.domain(), relay_b.domain()];
368 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 333 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
369 334
370 let client_a = TestClient::new(relay_a.url(), keys.clone()) 335 let (_announcement, _git_dir_a) =
371 .await 336 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
372 .expect("Failed to connect to relay_a"); 337 println!("Announcement set up on relay_a with git data");
373
374 let client_b = TestClient::new(relay_b.url(), keys.clone())
375 .await
376 .expect("Failed to connect to relay_b");
377 338
378 client_a 339 let (_announcement_b, _git_dir_b) =
379 .send_event(&announcement) 340 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
380 .await 341 println!("Announcement set up on relay_b with git data (triggers discovery)");
381 .expect("Failed to send announcement to relay_a");
382 println!("Announcement sent to relay_a");
383
384 client_b
385 .send_event(&announcement)
386 .await
387 .expect("Failed to send announcement to relay_b");
388 println!("Announcement sent to relay_b (triggers discovery)");
389 342
390 // 3. Wait for discovery 343 // 3. Wait for discovery
391 tokio::time::sleep(Duration::from_secs(1)).await; 344 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -396,6 +349,10 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
396 .expect("Failed to create issue"); 349 .expect("Failed to create issue");
397 let issue_id = issue.id; 350 let issue_id = issue.id;
398 351
352 let client_a = TestClient::new(relay_a.url(), keys.clone())
353 .await
354 .expect("Failed to connect to relay_a");
355
399 client_a 356 client_a
400 .send_event(&issue) 357 .send_event(&issue)
401 .await 358 .await
@@ -410,11 +367,6 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
410 assert!(issue_synced, "Layer 2 issue should sync first"); 367 assert!(issue_synced, "Layer 2 issue should sync first");
411 368
412 // Wait for Layer 3 subscriptions to be established 369 // Wait for Layer 3 subscriptions to be established
413 // After issue syncs, relay_b's SelfSubscriber needs time to:
414 // 1. Receive the synced issue via notify_event broadcast
415 // 2. Batch timer to tick (up to 200ms in tests)
416 // 3. Process batch and create Layer 3 filters
417 // 4. Subscribe to relay_a with Layer 3 filters
418 tokio::time::sleep(Duration::from_millis(500)).await; 370 tokio::time::sleep(Duration::from_millis(500)).await;
419 371
420 // 6. Create and send Layer 3 reply with lowercase 'e' tag (kind 1) 372 // 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() {
427 reply_id, 379 reply_id,
428 reply.kind.as_u16() 380 reply.kind.as_u16()
429 ); 381 );
430 for tag in reply.tags.iter() {
431 println!(" Tag: {:?}", tag.as_slice());
432 }
433 382
434 client_a 383 client_a
435 .send_event(&reply) 384 .send_event(&reply)
@@ -438,7 +387,6 @@ async fn test_layer3_sync_with_lowercase_e_tag() {
438 println!("Layer 3 reply {} sent to relay_a", reply_id); 387 println!("Layer 3 reply {} sent to relay_a", reply_id);
439 388
440 client_a.disconnect().await; 389 client_a.disconnect().await;
441 client_b.disconnect().await;
442 390
443 // 7. Wait and verify reply syncs to relay_b 391 // 7. Wait and verify reply syncs to relay_b
444 let reply_filter = Filter::new() 392 let reply_filter = Filter::new()
@@ -486,30 +434,18 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
486 434
487 let keys = Keys::generate(); 435 let keys = Keys::generate();
488 436
489 // 2. Create and send repository announcement to both relays 437 // 2. Create and send repository announcement to both relays with git data
490 let repo_id = "test-repo-tag-9b"; 438 let repo_id = "test-repo-tag-9b";
491 let announcement = 439 let domains = vec![relay_a.domain(), relay_b.domain()];
492 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 440 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
493
494 let client_a = TestClient::new(relay_a.url(), keys.clone())
495 .await
496 .expect("Failed to connect to relay_a");
497 441
498 let client_b = TestClient::new(relay_b.url(), keys.clone()) 442 let (_announcement, _git_dir_a) =
499 .await 443 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
500 .expect("Failed to connect to relay_b"); 444 println!("Announcement set up on relay_a with git data");
501 445
502 client_a 446 let (_announcement_b, _git_dir_b) =
503 .send_event(&announcement) 447 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
504 .await 448 println!("Announcement set up on relay_b with git data (triggers discovery)");
505 .expect("Failed to send announcement to relay_a");
506 println!("Announcement sent to relay_a");
507
508 client_b
509 .send_event(&announcement)
510 .await
511 .expect("Failed to send announcement to relay_b");
512 println!("Announcement sent to relay_b (triggers discovery)");
513 449
514 // 3. Wait for discovery 450 // 3. Wait for discovery
515 tokio::time::sleep(Duration::from_secs(1)).await; 451 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -520,6 +456,10 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
520 .expect("Failed to create issue"); 456 .expect("Failed to create issue");
521 let issue_id = issue.id; 457 let issue_id = issue.id;
522 458
459 let client_a = TestClient::new(relay_a.url(), keys.clone())
460 .await
461 .expect("Failed to connect to relay_a");
462
523 client_a 463 client_a
524 .send_event(&issue) 464 .send_event(&issue)
525 .await 465 .await
@@ -534,11 +474,6 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
534 assert!(issue_synced, "Layer 2 issue should sync first"); 474 assert!(issue_synced, "Layer 2 issue should sync first");
535 475
536 // Wait for Layer 3 subscriptions to be established 476 // Wait for Layer 3 subscriptions to be established
537 // After issue syncs, relay_b's SelfSubscriber needs time to:
538 // 1. Receive the synced issue via notify_event broadcast
539 // 2. Batch timer to tick (up to 200ms in tests)
540 // 3. Process batch and create Layer 3 filters
541 // 4. Subscribe to relay_a with Layer 3 filters
542 tokio::time::sleep(Duration::from_millis(500)).await; 477 tokio::time::sleep(Duration::from_millis(500)).await;
543 478
544 // 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111) 479 // 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() {
552 comment_id, 487 comment_id,
553 comment.kind.as_u16() 488 comment.kind.as_u16()
554 ); 489 );
555 for tag in comment.tags.iter() {
556 println!(" Tag: {:?}", tag.as_slice());
557 }
558 490
559 client_a 491 client_a
560 .send_event(&comment) 492 .send_event(&comment)
@@ -563,7 +495,6 @@ async fn test_layer3_sync_with_uppercase_e_tag() {
563 println!("Layer 3 comment {} sent to relay_a", comment_id); 495 println!("Layer 3 comment {} sent to relay_a", comment_id);
564 496
565 client_a.disconnect().await; 497 client_a.disconnect().await;
566 client_b.disconnect().await;
567 498
568 // 7. Wait and verify comment syncs to relay_b 499 // 7. Wait and verify comment syncs to relay_b
569 let comment_filter = Filter::new() 500 let comment_filter = Filter::new()
@@ -614,30 +545,18 @@ async fn test_layer3_sync_with_q_tag() {
614 545
615 let keys = Keys::generate(); 546 let keys = Keys::generate();
616 547
617 // 2. Create and send repository announcement to both relays 548 // 2. Create and send repository announcement to both relays with git data
618 let repo_id = "test-repo-tag-9c"; 549 let repo_id = "test-repo-tag-9c";
619 let announcement = 550 let domains = vec![relay_a.domain(), relay_b.domain()];
620 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], repo_id); 551 let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect();
621 552
622 let client_a = TestClient::new(relay_a.url(), keys.clone()) 553 let (_announcement, _git_dir_a) =
623 .await 554 setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await;
624 .expect("Failed to connect to relay_a"); 555 println!("Announcement set up on relay_a with git data");
625 556
626 let client_b = TestClient::new(relay_b.url(), keys.clone()) 557 let (_announcement_b, _git_dir_b) =
627 .await 558 setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await;
628 .expect("Failed to connect to relay_b"); 559 println!("Announcement set up on relay_b with git data (triggers discovery)");
629
630 client_a
631 .send_event(&announcement)
632 .await
633 .expect("Failed to send announcement to relay_a");
634 println!("Announcement sent to relay_a");
635
636 client_b
637 .send_event(&announcement)
638 .await
639 .expect("Failed to send announcement to relay_b");
640 println!("Announcement sent to relay_b (triggers discovery)");
641 560
642 // 3. Wait for discovery 561 // 3. Wait for discovery
643 tokio::time::sleep(Duration::from_secs(1)).await; 562 tokio::time::sleep(Duration::from_secs(1)).await;
@@ -648,6 +567,10 @@ async fn test_layer3_sync_with_q_tag() {
648 .expect("Failed to create issue"); 567 .expect("Failed to create issue");
649 let issue_id = issue.id; 568 let issue_id = issue.id;
650 569
570 let client_a = TestClient::new(relay_a.url(), keys.clone())
571 .await
572 .expect("Failed to connect to relay_a");
573
651 client_a 574 client_a
652 .send_event(&issue) 575 .send_event(&issue)
653 .await 576 .await
@@ -662,11 +585,6 @@ async fn test_layer3_sync_with_q_tag() {
662 assert!(issue_synced, "Layer 2 issue should sync first"); 585 assert!(issue_synced, "Layer 2 issue should sync first");
663 586
664 // Wait for Layer 3 subscriptions to be established 587 // Wait for Layer 3 subscriptions to be established
665 // After issue syncs, relay_b's SelfSubscriber needs time to:
666 // 1. Receive the synced issue via notify_event broadcast
667 // 2. Batch timer to tick (up to 200ms in tests)
668 // 3. Process batch and create Layer 3 filters
669 // 4. Subscribe to relay_a with Layer 3 filters
670 tokio::time::sleep(Duration::from_millis(500)).await; 588 tokio::time::sleep(Duration::from_millis(500)).await;
671 589
672 // 6. Create and send Layer 3 quote with 'q' tag (kind 1) 590 // 6. Create and send Layer 3 quote with 'q' tag (kind 1)
@@ -679,9 +597,6 @@ async fn test_layer3_sync_with_q_tag() {
679 quote_id, 597 quote_id,
680 quote.kind.as_u16() 598 quote.kind.as_u16()
681 ); 599 );
682 for tag in quote.tags.iter() {
683 println!(" Tag: {:?}", tag.as_slice());
684 }
685 600
686 client_a 601 client_a
687 .send_event(&quote) 602 .send_event(&quote)
@@ -690,7 +605,6 @@ async fn test_layer3_sync_with_q_tag() {
690 println!("Layer 3 quote {} sent to relay_a", quote_id); 605 println!("Layer 3 quote {} sent to relay_a", quote_id);
691 606
692 client_a.disconnect().await; 607 client_a.disconnect().await;
693 client_b.disconnect().await;
694 608
695 // 7. Wait and verify quote syncs to relay_b 609 // 7. Wait and verify quote syncs to relay_b
696 let quote_filter = Filter::new() 610 let quote_filter = Filter::new()