upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/proactive_sync_basic.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-10 16:35:29 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-10 16:35:29 +0000
commita958a55196537598492c3935ab46e3b6e23ee44b (patch)
treec30f5fe53e7ee5dd9fdffd79dbeb6a7ea618757b /tests/proactive_sync_basic.rs
parent5f3c8b7085f6652fdde4443983df0aad561e3c67 (diff)
Phase 10: Remove obsolete proactive_sync_basic.rs test file
All tests from this file have been migrated to the new modular structure: - tests/sync/bootstrap.rs (Tests 1, 4) - tests/sync/discovery.rs (Tests 2, 3) - tests/sync/live_sync.rs (Tests 5, 6, 7) - tests/sync/tag_variations.rs (Tests 8a-c, 9a-c) - tests/sync/catchup.rs (Test 0) Verified: cargo test --test sync shows 19 passed, 5 ignored
Diffstat (limited to 'tests/proactive_sync_basic.rs')
-rw-r--r--tests/proactive_sync_basic.rs466
1 files changed, 0 insertions, 466 deletions
diff --git a/tests/proactive_sync_basic.rs b/tests/proactive_sync_basic.rs
deleted file mode 100644
index d96d576..0000000
--- a/tests/proactive_sync_basic.rs
+++ /dev/null
@@ -1,466 +0,0 @@
1//! GRASP-02 Phase 1: Proactive Sync Basic Integration Tests
2//!
3//! Tests the basic proactive sync functionality using two TestRelay instances:
4//! - relay_a: Source relay with events
5//! - relay_b: Sync relay configured to sync from relay_a
6//!
7//! # Running Tests
8//!
9//! ```bash
10//! cargo test --test proactive_sync_basic
11//! cargo test --test proactive_sync_basic -- --nocapture
12//! ```
13
14mod common;
15
16use std::time::Duration;
17
18use common::TestRelay;
19use nostr_sdk::prelude::*;
20
21/// Kind 30617 - Repository State (NIP-34)
22const KIND_REPOSITORY_STATE: u16 = 30617;
23
24/// Result of checking if an event syncs between relays
25#[derive(Debug)]
26struct SyncCheckResult {
27 /// Whether the event was successfully stored on the source relay
28 stored_on_source: bool,
29 /// Whether the event was synced to the target relay
30 synced_to_target: bool,
31}
32
33/// Helper to check if an event syncs from source relay to target relay
34///
35/// This function:
36/// 1. Sends the event to the source relay
37/// 2. Verifies if it was stored on the source relay
38/// 3. Waits for potential sync
39/// 4. Checks if the event appears on the target relay
40///
41/// Note: The sync subscription must already be established before calling this.
42async fn check_event_syncs(
43 source_relay: &TestRelay,
44 target_relay: &TestRelay,
45 event: &Event,
46 keys: &Keys,
47) -> SyncCheckResult {
48 let event_id = event.id;
49
50 // Create client and connect to source relay
51 let client_source = create_connected_client(source_relay.url(), keys.clone())
52 .await
53 .expect("Failed to connect to source relay");
54
55 // Send event to source relay
56 let send_result = send_event_reliably(&client_source, event).await;
57 let stored_on_source = send_result.is_ok();
58
59 if stored_on_source {
60 println!("Event {} stored on source relay", event_id);
61 } else {
62 println!(
63 "Event {} NOT stored on source relay: {:?}",
64 event_id,
65 send_result.err()
66 );
67 }
68
69 // Wait for sync to occur
70 tokio::time::sleep(Duration::from_secs(1)).await;
71
72 // Check if event exists on target relay
73 let client_target = create_connected_client(target_relay.url(), Keys::generate())
74 .await
75 .expect("Failed to connect to target relay");
76
77 let filter = Filter::new().kind(event.kind).author(keys.public_key());
78
79 let events_on_target = client_target
80 .fetch_events(filter, Duration::from_secs(3))
81 .await
82 .expect("Failed to fetch from target relay");
83
84 let synced_to_target = events_on_target.iter().any(|e| e.id == event_id);
85
86 if synced_to_target {
87 println!("Event {} found on target relay (synced)", event_id);
88 } else {
89 println!("Event {} NOT found on target relay", event_id);
90 }
91
92 // Clean up
93 client_source.disconnect().await;
94 client_target.disconnect().await;
95
96 SyncCheckResult {
97 stored_on_source,
98 synced_to_target,
99 }
100}
101
102/// Create a client with keys, connect to relay, and wait for connection
103async fn create_connected_client(relay_url: &str, keys: Keys) -> Result<Client, String> {
104 let client = Client::new(keys);
105
106 client
107 .add_relay(relay_url)
108 .await
109 .map_err(|e| e.to_string())?;
110 client.connect().await;
111
112 // Wait for connection to establish (with retries, matching grasp-audit pattern)
113 for _ in 0..30 {
114 tokio::time::sleep(Duration::from_millis(100)).await;
115 let relays = client.relays().await;
116 if relays.values().any(|r| r.is_connected()) {
117 return Ok(client);
118 }
119 }
120
121 Err("Failed to connect to relay after 3 seconds".to_string())
122}
123
124/// Send an event and wait for successful delivery
125async fn send_event_reliably(client: &Client, event: &Event) -> Result<EventId, String> {
126 // Try sending the event with retries
127 for attempt in 1..=5 {
128 let result = client.send_event(event).await;
129 match result {
130 Ok(output) => {
131 if !output.success.is_empty() {
132 return Ok(output.val);
133 }
134 // Check what went wrong
135 if !output.failed.is_empty() {
136 println!(" Attempt {} - failures: {:?}", attempt, output.failed);
137 // If relay not connected, try reconnecting
138 client.connect().await;
139 }
140 }
141 Err(e) => {
142 println!(" Attempt {} - error: {}", attempt, e);
143 }
144 }
145 tokio::time::sleep(Duration::from_millis(500)).await;
146 }
147 Err("Failed to send event after 5 attempts".to_string())
148}
149
150/// Create a valid repository announcement event for testing
151///
152/// This creates a kind 30617 event with required clone and relays tags.
153/// Accepts one or more domains - for sync tests, include all relay domains
154/// so the event will be accepted by each relay's write policy.
155/// Uses TagKind::custom("clone") and TagKind::custom("relays") to match grasp-audit patterns.
156fn create_repo_announcement(keys: &Keys, domains: &[&str], identifier: &str) -> Event {
157 // Build clone URLs for all domains (with .git suffix)
158 let clone_urls: Vec<String> = domains
159 .iter()
160 .map(|d| format!("http://{}/{}.git", d, identifier))
161 .collect();
162
163 // Build relay URLs for all domains
164 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
165
166 // Build tags for repository announcement using custom tag kinds (as grasp-audit does)
167 let tags = vec![
168 Tag::identifier(identifier),
169 Tag::custom(TagKind::custom("clone"), clone_urls),
170 Tag::custom(TagKind::custom("relays"), relay_urls),
171 ];
172
173 EventBuilder::new(Kind::Custom(KIND_REPOSITORY_STATE), "Repository state")
174 .tags(tags)
175 .sign_with_keys(keys)
176 .expect("Failed to sign event")
177}
178
179/// Test that syncing relay connects to source relay
180#[tokio::test]
181async fn test_sync_relay_connects_to_source() {
182 // Start source relay (relay_a)
183 let relay_a = TestRelay::start().await;
184
185 // Start syncing relay (relay_b) configured to sync from relay_a
186 let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await;
187
188 // Give some time for connection to establish
189 tokio::time::sleep(Duration::from_millis(500)).await;
190
191 // If we got here without panicking, the relays started successfully
192 // The sync connection happens in the background
193
194 relay_b.stop().await;
195 relay_a.stop().await;
196}
197
198/// Test that valid events sync from source to bootstrap relay
199#[tokio::test]
200async fn announcement_listing_relay_syncs_from_bootstrap_relay() {
201 // Start source relay (relay_a)
202 let relay_a = TestRelay::start().await;
203 println!(
204 "relay_a started at {} (domain: {})",
205 relay_a.url(),
206 relay_a.domain()
207 );
208
209 // Start syncing relay (relay_b) configured to sync from relay_a
210 let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await;
211 println!(
212 "relay_b started at {} (domain: {})",
213 relay_b.url(),
214 relay_b.domain()
215 );
216
217 // Create test keys
218 let keys = Keys::generate();
219
220 // Wait for relay_b's sync connection to establish
221 println!("Waiting 1s for relay_b sync connection to establish...");
222 tokio::time::sleep(Duration::from_secs(1)).await;
223
224 // Create a repository announcement that lists BOTH relays
225 // This is required for sync - the event must reference both the source relay
226 // and the syncing relay for the write policy to accept it on both sides
227 let event =
228 create_repo_announcement(&keys, &[&relay_a.domain(), &relay_b.domain()], "test-repo");
229 let event_id = event.id;
230
231 // Print event details for debugging
232 println!("Created event {} (kind {})", event_id, event.kind.as_u16());
233 for tag in event.tags.iter() {
234 println!(" Tag: {:?}", tag.as_slice());
235 }
236
237 // Use helper to send and check sync
238 let result = check_event_syncs(&relay_a, &relay_b, &event, &keys).await;
239
240 // Clean up
241 relay_b.stop().await;
242 relay_a.stop().await;
243
244 assert!(
245 result.stored_on_source,
246 "Event {} was not stored on relay_a! This is a prerequisite for sync.",
247 event_id
248 );
249 assert!(
250 result.synced_to_target,
251 "Event {} was not synced to relay_b",
252 event_id
253 );
254}
255
256/// Test that events not listing relay_b in their relays tag are NOT synced
257///
258/// This verifies that relay_b's write policy correctly rejects events during sync
259/// if they don't list relay_b as one of their relays.
260#[tokio::test]
261async fn test_announcement_not_listing_relay_is_not_synced_from_boostrap_relay() {
262 // Start source relay (relay_a)
263 let relay_a = TestRelay::start().await;
264 println!(
265 "relay_a started at {} (domain: {})",
266 relay_a.url(),
267 relay_a.domain()
268 );
269
270 // Start syncing relay (relay_b) configured to sync from relay_a
271 let relay_b = TestRelay::start_with_sync(Some(relay_a.url().into())).await;
272 println!(
273 "relay_b started at {} (domain: {})",
274 relay_b.url(),
275 relay_b.domain()
276 );
277
278 // Create test keys
279 let keys = Keys::generate();
280
281 // Wait for relay_b's sync connection to establish
282 println!("Waiting 1s for relay_b sync connection to establish...");
283 tokio::time::sleep(Duration::from_secs(1)).await;
284
285 // Create a repository announcement that lists ONLY relay_a (NOT relay_b)
286 // This event is valid and will be accepted by relay_a, but should be
287 // rejected by relay_b's write policy during sync
288 let event = create_repo_announcement(&keys, &[&relay_a.domain()], "test-repo-only-a");
289 let event_id = event.id;
290
291 // Print event details for debugging
292 println!("Created event {} (kind {})", event_id, event.kind.as_u16());
293 for tag in event.tags.iter() {
294 println!(" Tag: {:?}", tag.as_slice());
295 }
296 println!("Note: This event only lists relay_a, not relay_b");
297
298 // Use helper to send and check sync
299 let result = check_event_syncs(&relay_a, &relay_b, &event, &keys).await;
300
301 // Clean up
302 relay_b.stop().await;
303 relay_a.stop().await;
304
305 // Event should be stored on relay_a (it lists relay_a)
306 assert!(
307 result.stored_on_source,
308 "Event {} should have been stored on relay_a (it lists relay_a)",
309 event_id
310 );
311
312 // Event should NOT be synced to relay_b (it doesn't list relay_b)
313 assert!(
314 !result.synced_to_target,
315 "Event {} should NOT have been synced to relay_b (it doesn't list relay_b)",
316 event_id
317 );
318}
319
320fn create_kind_event_referencing_repo(keys: &Keys, repo_coord: &str) -> Event {
321 // TODO this breaks with kind 1
322 EventBuilder::new(Kind::Custom(1617), "Test patch proposal")
323 .tags(vec![Tag::custom(
324 TagKind::custom("a"),
325 vec![repo_coord.to_string()],
326 )])
327 .sign_with_keys(keys)
328 .expect("Failed to sign event")
329}
330
331/// Test that when a relay is discovered (via an announcement event), events are synced from it
332///
333/// This test verifies dynamic relay discovery from direct submissions:
334/// 1. relay_a has an announcement and a patch event
335/// 2. relay_b (sync enabled, NO bootstrap) receives the announcement directly
336/// 3. relay_b discovers relay_a from the announcement and connects to sync
337/// 4. relay_b syncs the patch event from relay_a
338///
339/// This tests the scenario where relays discover each other through announcements
340/// submitted by users, not through an existing sync connection.
341#[tokio::test]
342async fn repo_events_synced_from_discovered_relay_after_announcement_received() {
343 // relay_a: source relay with the patch event
344 let relay_a = TestRelay::start().await;
345 println!(
346 "relay_a started at {} (domain: {})",
347 relay_a.url(),
348 relay_a.domain()
349 );
350
351 // relay_b: sync enabled but NO bootstrap relay - will discover relay_a
352 let relay_b = TestRelay::start_with_sync(None).await;
353 println!(
354 "relay_b started at {} (domain: {})",
355 relay_b.url(),
356 relay_b.domain()
357 );
358
359 // Create test keys
360 let keys = Keys::generate();
361
362 // Create a repository announcement that lists BOTH relays
363 let announcement = create_repo_announcement(
364 &keys,
365 &[&relay_a.domain(), &relay_b.domain()],
366 "test-repo-discovery",
367 );
368 let announcement_id = announcement.id;
369
370 println!(
371 "Created announcement {} (kind {})",
372 announcement_id,
373 announcement.kind.as_u16()
374 );
375 for tag in announcement.tags.iter() {
376 println!(" Tag: {:?}", tag.as_slice());
377 }
378
379 // Build the repo coordinate for the 'a' tag in the patch
380 let repo_coord = format!(
381 "{}:{}:{}",
382 KIND_REPOSITORY_STATE,
383 keys.public_key().to_hex(),
384 "test-repo-discovery"
385 );
386
387 // Create a patch event that references the announcement
388 let patch = create_kind_event_referencing_repo(&keys, &repo_coord);
389 let patch_id = patch.id;
390
391 println!("Created patch {} (kind {})", patch_id, patch.kind.as_u16());
392 for tag in patch.tags.iter() {
393 println!(" Tag: {:?}", tag.as_slice());
394 }
395
396 // Step 1: Send announcement to relay_a
397 let client_a = create_connected_client(relay_a.url(), keys.clone())
398 .await
399 .expect("Failed to connect to relay_a");
400 send_event_reliably(&client_a, &announcement)
401 .await
402 .expect("Failed to send announcement to relay_a");
403 println!("Announcement sent to relay_a");
404
405 // Step 2: Send patch to relay_a ONLY
406 send_event_reliably(&client_a, &patch)
407 .await
408 .expect("Failed to send patch to relay_a");
409 println!("Patch sent to relay_a");
410 client_a.disconnect().await;
411
412 // Step 3: Send announcement to relay_b directly (this should trigger discovery of relay_a)
413 let client_b = create_connected_client(relay_b.url(), keys.clone())
414 .await
415 .expect("Failed to connect to relay_b");
416 send_event_reliably(&client_b, &announcement)
417 .await
418 .expect("Failed to send announcement to relay_b");
419 println!("Announcement sent to relay_b (should trigger discovery of relay_a)");
420 client_b.disconnect().await;
421
422 // Step 4: Wait for relay_b to discover relay_a and sync the patch
423 println!("Waiting 3s for relay_b to discover relay_a and sync patch...");
424 tokio::time::sleep(Duration::from_secs(3)).await;
425
426 // Step 5: Verify patch was synced to relay_b
427 let client_b_check = create_connected_client(relay_b.url(), Keys::generate())
428 .await
429 .expect("Failed to connect to relay_b for verification");
430
431 let filter = Filter::new()
432 .kind(Kind::Custom(1617))
433 .author(keys.public_key());
434
435 let events_on_b = client_b_check
436 .fetch_events(filter, Duration::from_secs(3))
437 .await
438 .expect("Failed to fetch from relay_b");
439
440 let patch_synced = events_on_b.iter().any(|e| e.id == patch_id);
441
442 if patch_synced {
443 println!(
444 "Patch {} found on relay_b (synced from discovered relay_a)",
445 patch_id
446 );
447 } else {
448 println!("Patch {} NOT found on relay_b", patch_id);
449 println!(
450 "Events on relay_b: {:?}",
451 events_on_b.iter().map(|e| e.id).collect::<Vec<_>>()
452 );
453 }
454
455 client_b_check.disconnect().await;
456
457 // Clean up
458 relay_b.stop().await;
459 relay_a.stop().await;
460
461 assert!(
462 patch_synced,
463 "Patch {} should have been synced to relay_b from discovered relay_a",
464 patch_id
465 );
466}