upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/sync
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-10 16:26:50 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-10 16:26:50 +0000
commitbb9abe353024878e6bf1884cf3fb5a115e75bd03 (patch)
treedf58222203dfa188d6e0813cd5379971af50316d /tests/sync
parentf49644963369ea45ca1856e8e5c6c2e7a33b3aa7 (diff)
Phase 7: Implement tag variation tests
Add comprehensive tests for different Layer 2 and Layer 3 tag variations: Layer 2 tests (Tests 8a-c) - all pass: - test_layer2_sync_with_lowercase_a_tag (standard NIP-01) - test_layer2_sync_with_uppercase_a_tag (NIP-33) - test_layer2_sync_with_q_tag (NIP-18 quotes) Layer 3 tests (Tests 9a-c) - marked #[ignore]: - test_layer3_sync_with_lowercase_e_tag (NIP-01) - test_layer3_sync_with_uppercase_e_tag (NIP-22) - test_layer3_sync_with_q_tag (NIP-18) Layer 3 tests have full implementation but are ignored until Layer 3 sync is enabled in the relay.
Diffstat (limited to 'tests/sync')
-rw-r--r--tests/sync/tag_variations.rs723
1 files changed, 723 insertions, 0 deletions
diff --git a/tests/sync/tag_variations.rs b/tests/sync/tag_variations.rs
new file mode 100644
index 0000000..3b36e68
--- /dev/null
+++ b/tests/sync/tag_variations.rs
@@ -0,0 +1,723 @@
1//! Tag Variation Tests
2//!
3//! Tests for different tag types used in Layer 2 and Layer 3 events.
4//! Ensures the relay correctly handles all valid NIP tag patterns.
5//!
6//! # Layer 2 Tag Variations (Tests 8a-c)
7//! - Test 8a: Lowercase 'a' tag (standard NIP-01 addressable reference)
8//! - Test 8b: Uppercase 'A' tag (NIP-33 parameterized replaceable)
9//! - Test 8c: Quote 'q' tag (NIP-18 reposts/quotes)
10//!
11//! # Layer 3 Tag Variations (Tests 9a-c)
12//! - Test 9a: Lowercase 'e' tag (standard NIP-01 event reference)
13//! - Test 9b: Uppercase 'E' tag (NIP-22 comments)
14//! - Test 9c: Quote 'q' tag (NIP-18 quotes)
15//!
16//! # Sync Mechanism
17//! All tests use discovery-based sync:
18//! 1. Send announcement to both relays (triggers discovery)
19//! 2. Publish test event to relay_a
20//! 3. Verify event syncs to relay_b
21
22use std::time::Duration;
23
24use nostr_sdk::prelude::*;
25
26use crate::common::{sync_helpers::*, TestRelay};
27
28/// Create a valid repository announcement event for testing sync.
29///
30/// This creates a kind 30617 event with required clone and relays tags.
31/// The event lists all provided domains so it will be accepted by each
32/// relay's write policy.
33///
34/// # Arguments
35/// * `keys` - Keys for signing
36/// * `domains` - Slice of domain strings (e.g., "127.0.0.1:8080")
37/// * `identifier` - Repository identifier (d-tag)
38fn create_repo_announcement(keys: &Keys, domains: &[&str], identifier: &str) -> Event {
39 // Build clone URLs for all domains (with .git suffix)
40 let clone_urls: Vec<String> = domains
41 .iter()
42 .map(|d| format!("http://{}/{}.git", d, identifier))
43 .collect();
44
45 // Build relay URLs for all domains
46 let relay_urls: Vec<String> = domains.iter().map(|d| format!("ws://{}", d)).collect();
47
48 // Build tags for repository announcement
49 let tags = vec![
50 Tag::identifier(identifier),
51 Tag::custom(TagKind::custom("clone"), clone_urls),
52 Tag::custom(TagKind::custom("relays"), relay_urls),
53 ];
54
55 EventBuilder::new(Kind::Custom(KIND_REPOSITORY_STATE), "Repository state")
56 .tags(tags)
57 .sign_with_keys(keys)
58 .expect("Failed to sign repo announcement")
59}
60
61// ============================================================================
62// Layer 2 Tag Variation Tests (Tests 8a-c)
63// ============================================================================
64
65/// Test 8a: Layer 2 sync with lowercase 'a' tag (standard NIP-01)
66///
67/// Verifies that Layer 2 events (kind 1618 issues) with standard lowercase 'a'
68/// tags sync correctly between relays.
69///
70/// The lowercase 'a' tag is the standard NIP-01 way to reference addressable
71/// events (those with a d-tag, like repository announcements).
72#[tokio::test]
73async fn test_layer2_sync_with_lowercase_a_tag() {
74 // 1. Start relays
75 let relay_a = TestRelay::start().await;
76 println!(
77 "relay_a started at {} (domain: {})",
78 relay_a.url(),
79 relay_a.domain()
80 );
81
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 let keys = Keys::generate();
90
91 // 2. Create and send repository announcement to both relays
92 let repo_id = "test-repo-tag-8a";
93 let announcement = create_repo_announcement(
94 &keys,
95 &[&relay_a.domain(), &relay_b.domain()],
96 repo_id,
97 );
98
99 let client_a = TestClient::new(relay_a.url(), keys.clone())
100 .await
101 .expect("Failed to connect to relay_a");
102
103 let client_b = TestClient::new(relay_b.url(), keys.clone())
104 .await
105 .expect("Failed to connect to relay_b");
106
107 client_a
108 .send_event(&announcement)
109 .await
110 .expect("Failed to send announcement to relay_a");
111 println!("Announcement sent to relay_a");
112
113 client_b
114 .send_event(&announcement)
115 .await
116 .expect("Failed to send announcement to relay_b");
117 println!("Announcement sent to relay_b (triggers discovery)");
118
119 // 3. Wait for discovery
120 tokio::time::sleep(Duration::from_secs(1)).await;
121
122 // 4. Create and send Layer 2 issue with lowercase 'a' tag
123 let repo_coordinate = repo_coord(&keys, repo_id);
124 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue with lowercase a tag")
125 .expect("Failed to create issue event");
126 let issue_id = issue.id;
127
128 println!("Created issue {} (kind {}) with lowercase 'a' tag", issue_id, issue.kind.as_u16());
129 for tag in issue.tags.iter() {
130 println!(" Tag: {:?}", tag.as_slice());
131 }
132
133 client_a
134 .send_event(&issue)
135 .await
136 .expect("Failed to send issue to relay_a");
137 println!("Issue sent to relay_a");
138
139 client_a.disconnect().await;
140 client_b.disconnect().await;
141
142 // 5. Wait and verify event syncs to relay_b
143 let filter = Filter::new()
144 .kind(Kind::Custom(KIND_ISSUE))
145 .author(keys.public_key())
146 .id(issue_id);
147
148 let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await;
149
150 println!("Issue {} synced to relay_b: {}", issue_id, synced);
151
152 // 6. Cleanup
153 relay_b.stop().await;
154 relay_a.stop().await;
155
156 assert!(
157 synced,
158 "Layer 2 issue with lowercase 'a' tag should have synced to relay_b"
159 );
160}
161
162/// Test 8b: Layer 2 sync with uppercase 'A' tag (NIP-33)
163///
164/// Verifies that Layer 2 events (kind 1618 issues) with uppercase 'A'
165/// tags sync correctly between relays.
166///
167/// The uppercase 'A' tag is used in NIP-33 for parameterized replaceable
168/// events references.
169#[tokio::test]
170async fn test_layer2_sync_with_uppercase_a_tag() {
171 // 1. Start relays
172 let relay_a = TestRelay::start().await;
173 println!(
174 "relay_a started at {} (domain: {})",
175 relay_a.url(),
176 relay_a.domain()
177 );
178
179 let relay_b = TestRelay::start_with_sync(None).await;
180 println!(
181 "relay_b started at {} (domain: {})",
182 relay_b.url(),
183 relay_b.domain()
184 );
185
186 let keys = Keys::generate();
187
188 // 2. Create and send repository announcement to both relays
189 let repo_id = "test-repo-tag-8b";
190 let announcement = create_repo_announcement(
191 &keys,
192 &[&relay_a.domain(), &relay_b.domain()],
193 repo_id,
194 );
195
196 let client_a = TestClient::new(relay_a.url(), keys.clone())
197 .await
198 .expect("Failed to connect to relay_a");
199
200 let client_b = TestClient::new(relay_b.url(), keys.clone())
201 .await
202 .expect("Failed to connect to relay_b");
203
204 client_a
205 .send_event(&announcement)
206 .await
207 .expect("Failed to send announcement to relay_a");
208 println!("Announcement sent to relay_a");
209
210 client_b
211 .send_event(&announcement)
212 .await
213 .expect("Failed to send announcement to relay_b");
214 println!("Announcement sent to relay_b (triggers discovery)");
215
216 // 3. Wait for discovery
217 tokio::time::sleep(Duration::from_secs(1)).await;
218
219 // 4. Create and send Layer 2 issue with uppercase 'A' tag
220 let repo_coordinate = repo_coord(&keys, repo_id);
221 let issue = build_layer2_issue_with_uppercase_a_tag(&keys, &repo_coordinate, "Test Issue with uppercase A tag")
222 .expect("Failed to create issue event");
223 let issue_id = issue.id;
224
225 println!("Created issue {} (kind {}) with uppercase 'A' tag", issue_id, issue.kind.as_u16());
226 for tag in issue.tags.iter() {
227 println!(" Tag: {:?}", tag.as_slice());
228 }
229
230 client_a
231 .send_event(&issue)
232 .await
233 .expect("Failed to send issue to relay_a");
234 println!("Issue sent to relay_a");
235
236 client_a.disconnect().await;
237 client_b.disconnect().await;
238
239 // 5. Wait and verify event syncs to relay_b
240 let filter = Filter::new()
241 .kind(Kind::Custom(KIND_ISSUE))
242 .author(keys.public_key())
243 .id(issue_id);
244
245 let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await;
246
247 println!("Issue {} synced to relay_b: {}", issue_id, synced);
248
249 // 6. Cleanup
250 relay_b.stop().await;
251 relay_a.stop().await;
252
253 assert!(
254 synced,
255 "Layer 2 issue with uppercase 'A' tag should have synced to relay_b"
256 );
257}
258
259/// Test 8c: Layer 2 sync with 'q' (quote) tag (NIP-18)
260///
261/// Verifies that Layer 2 events (kind 1618 issues) with 'q' (quote)
262/// tags sync correctly between relays.
263///
264/// The 'q' tag is used in NIP-18 for reposts and quotes.
265#[tokio::test]
266async fn test_layer2_sync_with_q_tag() {
267 // 1. Start relays
268 let relay_a = TestRelay::start().await;
269 println!(
270 "relay_a started at {} (domain: {})",
271 relay_a.url(),
272 relay_a.domain()
273 );
274
275 let relay_b = TestRelay::start_with_sync(None).await;
276 println!(
277 "relay_b started at {} (domain: {})",
278 relay_b.url(),
279 relay_b.domain()
280 );
281
282 let keys = Keys::generate();
283
284 // 2. Create and send repository announcement to both relays
285 let repo_id = "test-repo-tag-8c";
286 let announcement = create_repo_announcement(
287 &keys,
288 &[&relay_a.domain(), &relay_b.domain()],
289 repo_id,
290 );
291
292 let client_a = TestClient::new(relay_a.url(), keys.clone())
293 .await
294 .expect("Failed to connect to relay_a");
295
296 let client_b = TestClient::new(relay_b.url(), keys.clone())
297 .await
298 .expect("Failed to connect to relay_b");
299
300 client_a
301 .send_event(&announcement)
302 .await
303 .expect("Failed to send announcement to relay_a");
304 println!("Announcement sent to relay_a");
305
306 client_b
307 .send_event(&announcement)
308 .await
309 .expect("Failed to send announcement to relay_b");
310 println!("Announcement sent to relay_b (triggers discovery)");
311
312 // 3. Wait for discovery
313 tokio::time::sleep(Duration::from_secs(1)).await;
314
315 // 4. Create and send Layer 2 issue with 'q' tag
316 let repo_coordinate = repo_coord(&keys, repo_id);
317 let issue = build_layer2_issue_with_q_tag(&keys, &repo_coordinate, "Test Issue with q tag")
318 .expect("Failed to create issue event");
319 let issue_id = issue.id;
320
321 println!("Created issue {} (kind {}) with 'q' tag", issue_id, issue.kind.as_u16());
322 for tag in issue.tags.iter() {
323 println!(" Tag: {:?}", tag.as_slice());
324 }
325
326 client_a
327 .send_event(&issue)
328 .await
329 .expect("Failed to send issue to relay_a");
330 println!("Issue sent to relay_a");
331
332 client_a.disconnect().await;
333 client_b.disconnect().await;
334
335 // 5. Wait and verify event syncs to relay_b
336 let filter = Filter::new()
337 .kind(Kind::Custom(KIND_ISSUE))
338 .author(keys.public_key())
339 .id(issue_id);
340
341 let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await;
342
343 println!("Issue {} synced to relay_b: {}", issue_id, synced);
344
345 // 6. Cleanup
346 relay_b.stop().await;
347 relay_a.stop().await;
348
349 assert!(
350 synced,
351 "Layer 2 issue with 'q' tag should have synced to relay_b"
352 );
353}
354
355// ============================================================================
356// Layer 3 Tag Variation Tests (Tests 9a-c)
357// ============================================================================
358
359/// Test 9a: Layer 3 sync with lowercase 'e' tag (standard NIP-01)
360///
361/// Verifies that Layer 3 events (kind 1 replies) with standard lowercase 'e'
362/// tags sync correctly between relays when referencing a Layer 2 event.
363///
364/// The lowercase 'e' tag is the standard NIP-01 way to reference events by ID.
365///
366/// # Note
367/// This test is currently ignored because Layer 3 sync is not yet implemented.
368/// The test logic is complete and will work when Layer 3 sync is enabled.
369///
370/// TODO: Enable this test when Layer 3 sync is implemented.
371#[tokio::test]
372#[ignore = "Layer 3 sync not yet implemented - comments don't sync via discovery"]
373async fn test_layer3_sync_with_lowercase_e_tag() {
374 // 1. Start relays
375 let relay_a = TestRelay::start().await;
376 println!(
377 "relay_a started at {} (domain: {})",
378 relay_a.url(),
379 relay_a.domain()
380 );
381
382 let relay_b = TestRelay::start_with_sync(None).await;
383 println!(
384 "relay_b started at {} (domain: {})",
385 relay_b.url(),
386 relay_b.domain()
387 );
388
389 let keys = Keys::generate();
390
391 // 2. Create and send repository announcement to both relays
392 let repo_id = "test-repo-tag-9a";
393 let announcement = create_repo_announcement(
394 &keys,
395 &[&relay_a.domain(), &relay_b.domain()],
396 repo_id,
397 );
398
399 let client_a = TestClient::new(relay_a.url(), keys.clone())
400 .await
401 .expect("Failed to connect to relay_a");
402
403 let client_b = TestClient::new(relay_b.url(), keys.clone())
404 .await
405 .expect("Failed to connect to relay_b");
406
407 client_a
408 .send_event(&announcement)
409 .await
410 .expect("Failed to send announcement to relay_a");
411 println!("Announcement sent to relay_a");
412
413 client_b
414 .send_event(&announcement)
415 .await
416 .expect("Failed to send announcement to relay_b");
417 println!("Announcement sent to relay_b (triggers discovery)");
418
419 // 3. Wait for discovery
420 tokio::time::sleep(Duration::from_secs(1)).await;
421
422 // 4. Create and send Layer 2 issue (parent event)
423 let repo_coordinate = repo_coord(&keys, repo_id);
424 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9a Test")
425 .expect("Failed to create issue");
426 let issue_id = issue.id;
427
428 client_a
429 .send_event(&issue)
430 .await
431 .expect("Failed to send issue");
432 println!("Layer 2 issue {} sent to relay_a", issue_id);
433
434 // 5. Wait for issue to sync to relay_b
435 let issue_filter = Filter::new()
436 .kind(Kind::Custom(KIND_ISSUE))
437 .id(issue_id);
438 let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await;
439 println!("Issue synced to relay_b: {}", issue_synced);
440 assert!(issue_synced, "Layer 2 issue should sync first");
441
442 // 6. Create and send Layer 3 reply with lowercase 'e' tag (kind 1)
443 let reply = build_layer3_reply_with_e_tag(&keys, &issue_id, "Reply with lowercase e tag")
444 .expect("Failed to create reply");
445 let reply_id = reply.id;
446
447 println!("Created reply {} (kind {}) with lowercase 'e' tag", reply_id, reply.kind.as_u16());
448 for tag in reply.tags.iter() {
449 println!(" Tag: {:?}", tag.as_slice());
450 }
451
452 client_a
453 .send_event(&reply)
454 .await
455 .expect("Failed to send reply");
456 println!("Layer 3 reply {} sent to relay_a", reply_id);
457
458 client_a.disconnect().await;
459 client_b.disconnect().await;
460
461 // 7. Wait and verify reply syncs to relay_b
462 let reply_filter = Filter::new()
463 .kind(Kind::TextNote) // Kind 1
464 .author(keys.public_key())
465 .id(reply_id);
466
467 let reply_synced = wait_for_event_on_relay(relay_b.url(), reply_filter, Duration::from_secs(5)).await;
468
469 println!("Reply {} synced to relay_b: {}", reply_id, reply_synced);
470
471 // 8. Cleanup
472 relay_b.stop().await;
473 relay_a.stop().await;
474
475 assert!(
476 reply_synced,
477 "Layer 3 reply with lowercase 'e' tag should have synced to relay_b"
478 );
479}
480
481/// Test 9b: Layer 3 sync with uppercase 'E' tag (NIP-22)
482///
483/// Verifies that Layer 3 events (kind 1111 comments) with uppercase 'E'
484/// tags sync correctly between relays when referencing a Layer 2 event.
485///
486/// The uppercase 'E' tag is used in NIP-22 for comment events.
487///
488/// # Note
489/// This test is currently ignored because Layer 3 sync is not yet implemented.
490/// The test logic is complete and will work when Layer 3 sync is enabled.
491///
492/// TODO: Enable this test when Layer 3 sync is implemented.
493#[tokio::test]
494#[ignore = "Layer 3 sync not yet implemented - comments don't sync via discovery"]
495async fn test_layer3_sync_with_uppercase_e_tag() {
496 // 1. Start relays
497 let relay_a = TestRelay::start().await;
498 println!(
499 "relay_a started at {} (domain: {})",
500 relay_a.url(),
501 relay_a.domain()
502 );
503
504 let relay_b = TestRelay::start_with_sync(None).await;
505 println!(
506 "relay_b started at {} (domain: {})",
507 relay_b.url(),
508 relay_b.domain()
509 );
510
511 let keys = Keys::generate();
512
513 // 2. Create and send repository announcement to both relays
514 let repo_id = "test-repo-tag-9b";
515 let announcement = create_repo_announcement(
516 &keys,
517 &[&relay_a.domain(), &relay_b.domain()],
518 repo_id,
519 );
520
521 let client_a = TestClient::new(relay_a.url(), keys.clone())
522 .await
523 .expect("Failed to connect to relay_a");
524
525 let client_b = TestClient::new(relay_b.url(), keys.clone())
526 .await
527 .expect("Failed to connect to relay_b");
528
529 client_a
530 .send_event(&announcement)
531 .await
532 .expect("Failed to send announcement to relay_a");
533 println!("Announcement sent to relay_a");
534
535 client_b
536 .send_event(&announcement)
537 .await
538 .expect("Failed to send announcement to relay_b");
539 println!("Announcement sent to relay_b (triggers discovery)");
540
541 // 3. Wait for discovery
542 tokio::time::sleep(Duration::from_secs(1)).await;
543
544 // 4. Create and send Layer 2 issue (parent event)
545 let repo_coordinate = repo_coord(&keys, repo_id);
546 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9b Test")
547 .expect("Failed to create issue");
548 let issue_id = issue.id;
549
550 client_a
551 .send_event(&issue)
552 .await
553 .expect("Failed to send issue");
554 println!("Layer 2 issue {} sent to relay_a", issue_id);
555
556 // 5. Wait for issue to sync to relay_b
557 let issue_filter = Filter::new()
558 .kind(Kind::Custom(KIND_ISSUE))
559 .id(issue_id);
560 let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await;
561 println!("Issue synced to relay_b: {}", issue_synced);
562 assert!(issue_synced, "Layer 2 issue should sync first");
563
564 // 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111)
565 let comment = build_layer3_comment_with_uppercase_e_tag(&keys, &issue_id, "Comment with uppercase E tag")
566 .expect("Failed to create comment");
567 let comment_id = comment.id;
568
569 println!("Created comment {} (kind {}) with uppercase 'E' tag", comment_id, comment.kind.as_u16());
570 for tag in comment.tags.iter() {
571 println!(" Tag: {:?}", tag.as_slice());
572 }
573
574 client_a
575 .send_event(&comment)
576 .await
577 .expect("Failed to send comment");
578 println!("Layer 3 comment {} sent to relay_a", comment_id);
579
580 client_a.disconnect().await;
581 client_b.disconnect().await;
582
583 // 7. Wait and verify comment syncs to relay_b
584 let comment_filter = Filter::new()
585 .kind(Kind::Custom(KIND_COMMENT)) // Kind 1111
586 .author(keys.public_key())
587 .id(comment_id);
588
589 let comment_synced = wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await;
590
591 println!("Comment {} synced to relay_b: {}", comment_id, comment_synced);
592
593 // 8. Cleanup
594 relay_b.stop().await;
595 relay_a.stop().await;
596
597 assert!(
598 comment_synced,
599 "Layer 3 comment with uppercase 'E' tag should have synced to relay_b"
600 );
601}
602
603/// Test 9c: Layer 3 sync with 'q' (quote) tag (NIP-18)
604///
605/// Verifies that Layer 3 events (kind 1 quotes) with 'q' (quote)
606/// tags sync correctly between relays when referencing a Layer 2 event.
607///
608/// The 'q' tag is used in NIP-18 for quotes/reposts.
609///
610/// # Note
611/// This test is currently ignored because Layer 3 sync is not yet implemented.
612/// The test logic is complete and will work when Layer 3 sync is enabled.
613///
614/// TODO: Enable this test when Layer 3 sync is implemented.
615#[tokio::test]
616#[ignore = "Layer 3 sync not yet implemented - comments don't sync via discovery"]
617async fn test_layer3_sync_with_q_tag() {
618 // 1. Start relays
619 let relay_a = TestRelay::start().await;
620 println!(
621 "relay_a started at {} (domain: {})",
622 relay_a.url(),
623 relay_a.domain()
624 );
625
626 let relay_b = TestRelay::start_with_sync(None).await;
627 println!(
628 "relay_b started at {} (domain: {})",
629 relay_b.url(),
630 relay_b.domain()
631 );
632
633 let keys = Keys::generate();
634
635 // 2. Create and send repository announcement to both relays
636 let repo_id = "test-repo-tag-9c";
637 let announcement = create_repo_announcement(
638 &keys,
639 &[&relay_a.domain(), &relay_b.domain()],
640 repo_id,
641 );
642
643 let client_a = TestClient::new(relay_a.url(), keys.clone())
644 .await
645 .expect("Failed to connect to relay_a");
646
647 let client_b = TestClient::new(relay_b.url(), keys.clone())
648 .await
649 .expect("Failed to connect to relay_b");
650
651 client_a
652 .send_event(&announcement)
653 .await
654 .expect("Failed to send announcement to relay_a");
655 println!("Announcement sent to relay_a");
656
657 client_b
658 .send_event(&announcement)
659 .await
660 .expect("Failed to send announcement to relay_b");
661 println!("Announcement sent to relay_b (triggers discovery)");
662
663 // 3. Wait for discovery
664 tokio::time::sleep(Duration::from_secs(1)).await;
665
666 // 4. Create and send Layer 2 issue (parent event)
667 let repo_coordinate = repo_coord(&keys, repo_id);
668 let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9c Test")
669 .expect("Failed to create issue");
670 let issue_id = issue.id;
671
672 client_a
673 .send_event(&issue)
674 .await
675 .expect("Failed to send issue");
676 println!("Layer 2 issue {} sent to relay_a", issue_id);
677
678 // 5. Wait for issue to sync to relay_b
679 let issue_filter = Filter::new()
680 .kind(Kind::Custom(KIND_ISSUE))
681 .id(issue_id);
682 let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await;
683 println!("Issue synced to relay_b: {}", issue_synced);
684 assert!(issue_synced, "Layer 2 issue should sync first");
685
686 // 6. Create and send Layer 3 quote with 'q' tag (kind 1)
687 let quote = build_layer3_quote_with_q_tag(&keys, &issue_id, "Quote with q tag")
688 .expect("Failed to create quote");
689 let quote_id = quote.id;
690
691 println!("Created quote {} (kind {}) with 'q' tag", quote_id, quote.kind.as_u16());
692 for tag in quote.tags.iter() {
693 println!(" Tag: {:?}", tag.as_slice());
694 }
695
696 client_a
697 .send_event(&quote)
698 .await
699 .expect("Failed to send quote");
700 println!("Layer 3 quote {} sent to relay_a", quote_id);
701
702 client_a.disconnect().await;
703 client_b.disconnect().await;
704
705 // 7. Wait and verify quote syncs to relay_b
706 let quote_filter = Filter::new()
707 .kind(Kind::TextNote) // Kind 1
708 .author(keys.public_key())
709 .id(quote_id);
710
711 let quote_synced = wait_for_event_on_relay(relay_b.url(), quote_filter, Duration::from_secs(5)).await;
712
713 println!("Quote {} synced to relay_b: {}", quote_id, quote_synced);
714
715 // 8. Cleanup
716 relay_b.stop().await;
717 relay_a.stop().await;
718
719 assert!(
720 quote_synced,
721 "Layer 3 quote with 'q' tag should have synced to relay_b"
722 );
723} \ No newline at end of file