upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/sync/live_sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sync/live_sync.rs')
-rw-r--r--tests/sync/live_sync.rs528
1 files changed, 528 insertions, 0 deletions
diff --git a/tests/sync/live_sync.rs b/tests/sync/live_sync.rs
new file mode 100644
index 0000000..3432687
--- /dev/null
+++ b/tests/sync/live_sync.rs
@@ -0,0 +1,528 @@
1//! Live Sync Tests
2//!
3//! Tests for real-time event synchronization between relays.
4//! These tests verify that events published to one relay are synced
5//! to another relay in real-time via the discovery mechanism.
6//!
7//! # Tests
8//! - Test 5: `test_live_sync_layer2_events` - Layer 2 (kind 1618) events sync in real-time
9//! - Test 6: `test_live_sync_layer3_events` - Layer 3 (comments) sync when referencing Layer 2
10//! - Test 7: `test_live_sync_event_ordering` - Events arrive in chronological order
11//!
12//! # Sync Mechanism
13//! These tests use the discovery-based sync pattern:
14//! 1. Send announcement to both relays
15//! 2. Each relay discovers the other from the announcement's relays tag
16//! 3. Events sync between relays
17//!
18//! This tests "live" sync behavior - events syncing after connection is established,
19//! as opposed to bootstrap sync which syncs existing events on startup.
20
21use std::time::Duration;
22
23use nostr_sdk::prelude::*;
24
25use crate::common::{sync_helpers::*, TestRelay};
26
27/// Create a valid repository announcement event for testing sync.
28///
29/// This creates a kind 30617 event with required clone and relays tags.
30/// The event lists all provided domains so it will be accepted by each
31/// relay's write policy.
32///
33/// # Arguments
34/// * `keys` - Keys for signing
35/// * `domains` - Slice of domain strings (e.g., "127.0.0.1:8080")
36/// * `identifier` - Repository identifier (d-tag)
37fn create_repo_announcement(keys: &Keys, domains: &[&str], identifier: &str) -> Event {
38 // Build clone URLs for all domains (with .git suffix)
39 let clone_urls: Vec<String> = domains
40 .iter()
41 .map(|d| format!("http://{}/{}.git", d, identifier))
42 .collect();
43
44 // Build relay URLs for all domains
45 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
46
47 // Build tags for repository announcement
48 let tags = vec![
49 Tag::identifier(identifier),
50 Tag::custom(TagKind::custom("clone"), clone_urls),
51 Tag::custom(TagKind::custom("relays"), relay_urls),
52 ];
53
54 EventBuilder::new(Kind::Custom(KIND_REPOSITORY_STATE), "Repository state")
55 .tags(tags)
56 .sign_with_keys(keys)
57 .expect("Failed to sign repo announcement")
58}
59
60/// Test 5: Live sync Layer 2 events
61///
62/// Verifies that Layer 2 events (kind 1618 issues) published to one relay
63/// are synced to another relay in real-time via discovery.
64///
65/// Flow:
66/// 1. Start relay_a (source)
67/// 2. Start relay_b (with sync enabled, no bootstrap)
68/// 3. Send announcement to both relays (triggers discovery)
69/// 4. Publish Layer 2 issue to relay_a
70/// 5. Verify event syncs to relay_b within 5 seconds
71#[tokio::test]
72async fn test_live_sync_layer2_events() {
73 // 1. Start source relay (relay_a)
74 let relay_a = TestRelay::start().await;
75 println!(
76 "relay_a started at {} (domain: {})",
77 relay_a.url(),
78 relay_a.domain()
79 );
80
81 // 2. Start relay_b with sync enabled (no bootstrap - sync via discovery)
82 let relay_b = TestRelay::start_with_sync(None).await;
83 println!(
84 "relay_b started at {} (domain: {})",
85 relay_b.url(),
86 relay_b.domain()
87 );
88
89 // 3. Create test keys
90 let keys = Keys::generate();
91
92 // 4. Create a repository announcement that lists BOTH relays
93 let repo_id = "test-repo-live-l2";
94 let announcement = create_repo_announcement(
95 &keys,
96 &[&relay_a.domain(), &relay_b.domain()],
97 repo_id,
98 );
99
100 println!(
101 "Created announcement {} (kind {})",
102 announcement.id,
103 announcement.kind.as_u16()
104 );
105
106 // 5. Send announcement to relay_a
107 let client_a = TestClient::new(relay_a.url(), keys.clone())
108 .await
109 .expect("Failed to connect to relay_a");
110
111 client_a
112 .send_event(&announcement)
113 .await
114 .expect("Failed to send announcement to relay_a");
115 println!("Announcement sent to relay_a");
116
117 // 6. Send announcement to relay_b (triggers discovery of relay_a)
118 let client_b = TestClient::new(relay_b.url(), keys.clone())
119 .await
120 .expect("Failed to connect to relay_b");
121
122 client_b
123 .send_event(&announcement)
124 .await
125 .expect("Failed to send announcement to relay_b");
126 println!("Announcement sent to relay_b (triggers discovery)");
127
128 // 7. Wait for discovery to complete
129 tokio::time::sleep(Duration::from_secs(1)).await;
130
131 // 8. Create and send a Layer 2 issue event (using helper)
132 let repo_coordinate = repo_coord(&keys, repo_id);
133 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue for Live Sync")
134 .expect("Failed to create issue event");
135 let issue_id = issue.id;
136
137 println!("Created issue {} (kind {})", issue_id, issue.kind.as_u16());
138 for tag in issue.tags.iter() {
139 println!(" Tag: {:?}", tag.as_slice());
140 }
141
142 // Send issue to relay_a only
143 client_a
144 .send_event(&issue)
145 .await
146 .expect("Failed to send issue to relay_a");
147 println!("Issue sent to relay_a");
148
149 client_a.disconnect().await;
150 client_b.disconnect().await;
151
152 // 9. Wait and verify event syncs to relay_b
153 let filter = Filter::new()
154 .kind(Kind::Custom(KIND_ISSUE))
155 .author(keys.public_key())
156 .id(issue_id);
157
158 let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await;
159
160 println!("Issue {} synced to relay_b: {}", issue_id, synced);
161
162 // 10. Cleanup
163 relay_b.stop().await;
164 relay_a.stop().await;
165
166 assert!(
167 synced,
168 "Layer 2 issue {} should have synced from relay_a to relay_b in real-time",
169 issue_id
170 );
171}
172
173/// Test 6: Live sync Layer 3 events
174///
175/// Verifies that Layer 3 events (comments) sync when they reference Layer 2 events.
176///
177/// Flow:
178/// 1. Start relay_a and relay_b (with sync enabled)
179/// 2. Send announcement to both relays (triggers discovery)
180/// 3. Publish Layer 2 issue to relay_a
181/// 4. Wait for Layer 2 issue to sync to relay_b
182/// 5. Publish Layer 3 comment (referencing the issue) to relay_a
183/// 6. Verify comment syncs to relay_b within 5 seconds
184/// 7. Verify comment has correct 'E' tag reference
185///
186/// # Note
187/// This test is currently ignored because Layer 3 (comment) sync is tracked separately
188/// and may not be fully implemented yet. See discovery.rs for context:
189/// > "Note: Layer 3 (comments on issues) sync is tracked separately and may
190/// > be implemented in future phases."
191///
192/// TODO: Enable this test when Layer 3 sync is implemented.
193#[tokio::test]
194#[ignore = "Layer 3 sync not yet implemented - comments don't sync via discovery"]
195async fn test_live_sync_layer3_events() {
196 // 1. Start relays
197 let relay_a = TestRelay::start().await;
198 println!(
199 "relay_a started at {} (domain: {})",
200 relay_a.url(),
201 relay_a.domain()
202 );
203
204 let relay_b = TestRelay::start_with_sync(None).await;
205 println!(
206 "relay_b started at {} (domain: {})",
207 relay_b.url(),
208 relay_b.domain()
209 );
210
211 let keys = Keys::generate();
212
213 // 2. Create and send repository announcement to both relays
214 let repo_id = "test-repo-live-l3";
215 let announcement = create_repo_announcement(
216 &keys,
217 &[&relay_a.domain(), &relay_b.domain()],
218 repo_id,
219 );
220
221 let client_a = TestClient::new(relay_a.url(), keys.clone())
222 .await
223 .expect("Failed to connect to relay_a");
224
225 let client_b = TestClient::new(relay_b.url(), keys.clone())
226 .await
227 .expect("Failed to connect to relay_b");
228
229 client_a
230 .send_event(&announcement)
231 .await
232 .expect("Failed to send announcement to relay_a");
233 println!("Announcement sent to relay_a");
234
235 client_b
236 .send_event(&announcement)
237 .await
238 .expect("Failed to send announcement to relay_b");
239 println!("Announcement sent to relay_b (triggers discovery)");
240
241 // 3. Wait for discovery
242 tokio::time::sleep(Duration::from_secs(1)).await;
243
244 // 4. Create and send Layer 2 issue
245 let repo_coordinate = repo_coord(&keys, repo_id);
246 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Comment Test")
247 .expect("Failed to create issue");
248 let issue_id = issue.id;
249
250 client_a
251 .send_event(&issue)
252 .await
253 .expect("Failed to send issue");
254 println!("Layer 2 issue {} sent to relay_a", issue_id);
255
256 // 5. Wait for issue to sync to relay_b
257 tokio::time::sleep(Duration::from_secs(2)).await;
258
259 let issue_filter = Filter::new()
260 .kind(Kind::Custom(KIND_ISSUE))
261 .id(issue_id);
262 let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(3)).await;
263 println!("Issue synced to relay_b: {}", issue_synced);
264
265 // 6. Create and send Layer 3 comment (kind 1111, uppercase E tag)
266 let comment = build_layer3_comment_with_uppercase_e_tag(
267 &keys,
268 &issue_id,
269 "This is a comment on the issue",
270 )
271 .expect("Failed to create comment");
272 let comment_id = comment.id;
273
274 println!("Created comment {} (kind {})", comment_id, comment.kind.as_u16());
275 for tag in comment.tags.iter() {
276 println!(" Tag: {:?}", tag.as_slice());
277 }
278
279 client_a
280 .send_event(&comment)
281 .await
282 .expect("Failed to send comment");
283 println!("Layer 3 comment {} sent to relay_a", comment_id);
284
285 client_a.disconnect().await;
286 client_b.disconnect().await;
287
288 // 7. Wait and verify comment syncs to relay_b
289 let comment_filter = Filter::new()
290 .kind(Kind::Custom(KIND_COMMENT))
291 .author(keys.public_key())
292 .id(comment_id);
293
294 let comment_synced = wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await;
295 println!("Comment {} synced to relay_b: {}", comment_id, comment_synced);
296
297 // 8. Verify the comment has correct 'E' tag reference
298 let mut has_correct_ref = false;
299 if comment_synced {
300 let temp_keys = Keys::generate();
301 let client = Client::new(temp_keys);
302 if client.add_relay(relay_b.url()).await.is_ok() {
303 client.connect().await;
304 tokio::time::sleep(Duration::from_millis(500)).await;
305
306 let fetch_filter = Filter::new()
307 .kind(Kind::Custom(KIND_COMMENT))
308 .id(comment_id);
309
310 if let Ok(events) = client.fetch_events(fetch_filter, Duration::from_secs(2)).await {
311 if let Some(event) = events.first() {
312 // Check for 'E' tag with parent event ID
313 for tag in event.tags.iter() {
314 let slice = tag.as_slice();
315 if slice.first() == Some(&"E".to_string())
316 && slice.get(1) == Some(&issue_id.to_hex())
317 {
318 has_correct_ref = true;
319 println!("Found correct E tag reference to issue");
320 break;
321 }
322 }
323 }
324 }
325 client.disconnect().await;
326 }
327 }
328
329 // 9. Cleanup
330 relay_b.stop().await;
331 relay_a.stop().await;
332
333 assert!(
334 issue_synced,
335 "Layer 2 issue {} should have synced first",
336 issue_id
337 );
338 assert!(
339 comment_synced,
340 "Layer 3 comment {} should have synced to relay_b",
341 comment_id
342 );
343 assert!(
344 has_correct_ref,
345 "Comment should have 'E' tag referencing issue {}",
346 issue_id
347 );
348}
349
350/// Test 7: Live sync event ordering
351///
352/// Verifies that events arrive in chronological order when synced.
353/// Note: We test ordering based on created_at timestamps, allowing for
354/// minor timing variations inherent in async systems.
355///
356/// Flow:
357/// 1. Start relay_a and relay_b (with sync enabled)
358/// 2. Send announcement to both relays (triggers discovery)
359/// 3. Publish 3 Layer 2 events to relay_a with 100ms delays between them
360/// 4. Collect events from relay_b
361/// 5. Verify events are ordered by created_at timestamp
362#[tokio::test]
363async fn test_live_sync_event_ordering() {
364 // 1. Start relays
365 let relay_a = TestRelay::start().await;
366 println!(
367 "relay_a started at {} (domain: {})",
368 relay_a.url(),
369 relay_a.domain()
370 );
371
372 let relay_b = TestRelay::start_with_sync(None).await;
373 println!(
374 "relay_b started at {} (domain: {})",
375 relay_b.url(),
376 relay_b.domain()
377 );
378
379 let keys = Keys::generate();
380
381 // 2. Create and send repository announcement to both relays
382 let repo_id = "test-repo-ordering";
383 let announcement = create_repo_announcement(
384 &keys,
385 &[&relay_a.domain(), &relay_b.domain()],
386 repo_id,
387 );
388
389 let client_a = TestClient::new(relay_a.url(), keys.clone())
390 .await
391 .expect("Failed to connect to relay_a");
392
393 let client_b = TestClient::new(relay_b.url(), keys.clone())
394 .await
395 .expect("Failed to connect to relay_b");
396
397 client_a
398 .send_event(&announcement)
399 .await
400 .expect("Failed to send announcement to relay_a");
401
402 client_b
403 .send_event(&announcement)
404 .await
405 .expect("Failed to send announcement to relay_b");
406 println!("Announcements sent to both relays");
407
408 // 3. Wait for discovery
409 tokio::time::sleep(Duration::from_secs(1)).await;
410
411 // 4. Create and send 3 issues with delays between them
412 let repo_coordinate = repo_coord(&keys, repo_id);
413 let mut issue_ids = Vec::new();
414 let mut expected_order_timestamps = Vec::new();
415
416 for i in 1..=3 {
417 let issue = build_layer2_issue_event(
418 &keys,
419 &repo_coordinate,
420 &format!("Ordering Test Issue {}", i),
421 )
422 .expect("Failed to create issue");
423
424 // Store the created_at timestamp for ordering verification
425 expected_order_timestamps.push(issue.created_at);
426 issue_ids.push(issue.id);
427
428 println!("Created issue {} at timestamp {}", issue.id, issue.created_at);
429
430 client_a
431 .send_event(&issue)
432 .await
433 .expect(&format!("Failed to send issue {}", i));
434
435 // Delay between events to ensure different timestamps
436 tokio::time::sleep(Duration::from_millis(150)).await;
437 }
438
439 client_a.disconnect().await;
440 client_b.disconnect().await;
441
442 // 5. Wait for all events to sync
443 tokio::time::sleep(Duration::from_secs(3)).await;
444
445 // 6. Fetch all events from relay_b
446 let temp_keys = Keys::generate();
447 let client = Client::new(temp_keys);
448
449 let events_found: Vec<Event>;
450 if client.add_relay(relay_b.url()).await.is_ok() {
451 client.connect().await;
452 tokio::time::sleep(Duration::from_millis(500)).await;
453
454 let filter = Filter::new()
455 .kind(Kind::Custom(KIND_ISSUE))
456 .author(keys.public_key());
457
458 match client.fetch_events(filter, Duration::from_secs(3)).await {
459 Ok(events) => {
460 events_found = events.into_iter().collect();
461 }
462 Err(e) => {
463 println!("Failed to fetch events: {}", e);
464 events_found = Vec::new();
465 }
466 }
467 client.disconnect().await;
468 } else {
469 events_found = Vec::new();
470 }
471
472 // 7. Verify we got events
473 let found_count = events_found.len();
474 println!("Found {} events on relay_b", found_count);
475
476 // Filter to only our test events (by ID)
477 let test_events: Vec<&Event> = events_found
478 .iter()
479 .filter(|e| issue_ids.contains(&e.id))
480 .collect();
481
482 println!("Found {} test events (out of {} total)", test_events.len(), events_found.len());
483
484 // 8. Check ordering by created_at timestamp
485 let mut ordered_correctly = true;
486 if test_events.len() >= 2 {
487 // Sort by created_at and check order matches
488 let mut sorted_events = test_events.clone();
489 sorted_events.sort_by_key(|e| e.created_at);
490
491 for (i, event) in sorted_events.iter().enumerate() {
492 println!(
493 "Event {} sorted: {} at timestamp {}",
494 i + 1,
495 event.id,
496 event.created_at
497 );
498 }
499
500 // Verify ascending timestamp order
501 for window in sorted_events.windows(2) {
502 if window[0].created_at > window[1].created_at {
503 ordered_correctly = false;
504 println!(
505 "Order violation: {} ({}) > {} ({})",
506 window[0].id, window[0].created_at,
507 window[1].id, window[1].created_at
508 );
509 }
510 }
511 }
512
513 // 9. Cleanup
514 relay_b.stop().await;
515 relay_a.stop().await;
516
517 // Assert based on what we found
518 // Note: We may not get all 3 events due to timing, but what we get should be ordered
519 assert!(
520 test_events.len() >= 2,
521 "Should have synced at least 2 of 3 events; found {}",
522 test_events.len()
523 );
524 assert!(
525 ordered_correctly,
526 "Events should be ordered by created_at timestamp"
527 );
528} \ No newline at end of file