//! Tag Variation Tests //! //! Tests for different tag types used in Layer 2 and Layer 3 events. //! Ensures the relay correctly handles all valid NIP tag patterns. //! //! # Layer 2 Tag Variations (Tests 8a-c) //! - Test 8a: Lowercase 'a' tag (standard NIP-01 addressable reference) //! - Test 8b: Uppercase 'A' tag (NIP-33 parameterized replaceable) //! - Test 8c: Quote 'q' tag (NIP-18 reposts/quotes) //! //! # Layer 3 Tag Variations (Tests 9a-c) //! - Test 9a: Lowercase 'e' tag (standard NIP-01 event reference) //! - Test 9b: Uppercase 'E' tag (NIP-22 comments) //! - Test 9c: Quote 'q' tag (NIP-18 quotes) //! //! # Sync Mechanism //! All tests use discovery-based sync: //! 1. Send announcement to both relays (triggers discovery) //! 2. Publish test event to relay_a //! 3. Verify event syncs to relay_b use std::time::Duration; use nostr_sdk::prelude::*; use crate::common::{sync_helpers::*, TestRelay}; // ============================================================================ // Layer 2 Tag Variation Tests (Tests 8a-c) // ============================================================================ /// Test 8a: Layer 2 sync with lowercase 'a' tag (standard NIP-01) /// /// Verifies that Layer 2 events (kind 1618 issues) with standard lowercase 'a' /// tags sync correctly between relays. /// /// The lowercase 'a' tag is the standard NIP-01 way to reference addressable /// events (those with a d-tag, like repository announcements). #[tokio::test] async fn test_layer2_sync_with_lowercase_a_tag() { // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data // (purgatory requires git data before announcements are accepted) let repo_id = "test-repo-tag-8a"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue with lowercase 'a' tag let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Test Issue with lowercase a tag") .expect("Failed to create issue event"); let issue_id = issue.id; println!( "Created issue {} (kind {}) with lowercase 'a' tag", issue_id, issue.kind.as_u16() ); let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue to relay_a"); println!("Issue sent to relay_a"); client_a.disconnect().await; // 5. Wait and verify event syncs to relay_b let filter = Filter::new() .kind(Kind::GitIssue) .author(keys.public_key()) .id(issue_id); let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; println!("Issue {} synced to relay_b: {}", issue_id, synced); // 6. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( synced, "Layer 2 issue with lowercase 'a' tag should have synced to relay_b" ); } /// Test 8b: Layer 2 sync with uppercase 'A' tag (NIP-33) /// /// Verifies that Layer 2 events (kind 1618 issues) with uppercase 'A' /// tags sync correctly between relays. /// /// The uppercase 'A' tag is used in NIP-33 for parameterized replaceable /// events references. #[tokio::test] async fn test_layer2_sync_with_uppercase_a_tag() { // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data let repo_id = "test-repo-tag-8b"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue with uppercase 'A' tag let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_with_uppercase_a_tag( &keys, &repo_coordinate, "Test Issue with uppercase A tag", ) .expect("Failed to create issue event"); let issue_id = issue.id; println!( "Created issue {} (kind {}) with uppercase 'A' tag", issue_id, issue.kind.as_u16() ); let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue to relay_a"); println!("Issue sent to relay_a"); client_a.disconnect().await; // 5. Wait and verify event syncs to relay_b let filter = Filter::new() .kind(Kind::GitIssue) .author(keys.public_key()) .id(issue_id); let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; println!("Issue {} synced to relay_b: {}", issue_id, synced); // 6. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( synced, "Layer 2 issue with uppercase 'A' tag should have synced to relay_b" ); } /// Test 8c: Layer 2 sync with 'q' (quote) tag (NIP-18) /// /// Verifies that Layer 2 events (kind 1618 issues) with 'q' (quote) /// tags sync correctly between relays. /// /// The 'q' tag is used in NIP-18 for reposts and quotes. #[tokio::test] async fn test_layer2_sync_with_q_tag() { // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data let repo_id = "test-repo-tag-8c"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue with 'q' tag let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_with_q_tag(&keys, &repo_coordinate, "Test Issue with q tag") .expect("Failed to create issue event"); let issue_id = issue.id; println!( "Created issue {} (kind {}) with 'q' tag", issue_id, issue.kind.as_u16() ); let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue to relay_a"); println!("Issue sent to relay_a"); client_a.disconnect().await; // 5. Wait and verify event syncs to relay_b let filter = Filter::new() .kind(Kind::GitIssue) .author(keys.public_key()) .id(issue_id); let synced = wait_for_event_on_relay(relay_b.url(), filter, Duration::from_secs(5)).await; println!("Issue {} synced to relay_b: {}", issue_id, synced); // 6. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( synced, "Layer 2 issue with 'q' tag should have synced to relay_b" ); } // ============================================================================ // Layer 3 Tag Variation Tests (Tests 9a-c) // ============================================================================ /// Test 9a: Layer 3 sync with lowercase 'e' tag (standard NIP-01) /// /// Verifies that Layer 3 events (kind 1 replies) with standard lowercase 'e' /// tags sync correctly between relays when referencing a Layer 2 event. /// /// The lowercase 'e' tag is the standard NIP-01 way to reference events by ID. #[tokio::test] async fn test_layer3_sync_with_lowercase_e_tag() { // Initialize tracing for debug output let _ = tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .try_init(); // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data let repo_id = "test-repo-tag-9a"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue (parent event) let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9a Test") .expect("Failed to create issue"); let issue_id = issue.id; let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue"); println!("Layer 2 issue {} sent to relay_a", issue_id); // 5. Wait for issue to sync to relay_b let issue_filter = Filter::new().kind(Kind::GitIssue).id(issue_id); let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; println!("Issue synced to relay_b: {}", issue_synced); assert!(issue_synced, "Layer 2 issue should sync first"); // Wait for Layer 3 subscriptions to be established tokio::time::sleep(Duration::from_millis(500)).await; // 6. Create and send Layer 3 reply with lowercase 'e' tag (kind 1) let reply = build_layer3_reply_with_e_tag(&keys, &issue_id, "Reply with lowercase e tag") .expect("Failed to create reply"); let reply_id = reply.id; println!( "Created reply {} (kind {}) with lowercase 'e' tag", reply_id, reply.kind.as_u16() ); client_a .send_event(&reply) .await .expect("Failed to send reply"); println!("Layer 3 reply {} sent to relay_a", reply_id); client_a.disconnect().await; // 7. Wait and verify reply syncs to relay_b let reply_filter = Filter::new() .kind(Kind::TextNote) // Kind 1 .author(keys.public_key()) .id(reply_id); let reply_synced = wait_for_event_on_relay(relay_b.url(), reply_filter, Duration::from_secs(5)).await; println!("Reply {} synced to relay_b: {}", reply_id, reply_synced); // 8. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( reply_synced, "Layer 3 reply with lowercase 'e' tag should have synced to relay_b" ); } /// Test 9b: Layer 3 sync with uppercase 'E' tag (NIP-22) /// /// Verifies that Layer 3 events (kind 1111 comments) with uppercase 'E' /// tags sync correctly between relays when referencing a Layer 2 event. /// /// The uppercase 'E' tag is used in NIP-22 for comment events. #[tokio::test] async fn test_layer3_sync_with_uppercase_e_tag() { // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data let repo_id = "test-repo-tag-9b"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue (parent event) let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9b Test") .expect("Failed to create issue"); let issue_id = issue.id; let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue"); println!("Layer 2 issue {} sent to relay_a", issue_id); // 5. Wait for issue to sync to relay_b let issue_filter = Filter::new().kind(Kind::GitIssue).id(issue_id); let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; println!("Issue synced to relay_b: {}", issue_synced); assert!(issue_synced, "Layer 2 issue should sync first"); // Wait for Layer 3 subscriptions to be established tokio::time::sleep(Duration::from_millis(500)).await; // 6. Create and send Layer 3 comment with uppercase 'E' tag (kind 1111) let comment = build_layer3_comment_with_uppercase_e_tag(&keys, &issue_id, "Comment with uppercase E tag") .expect("Failed to create comment"); let comment_id = comment.id; println!( "Created comment {} (kind {}) with uppercase 'E' tag", comment_id, comment.kind.as_u16() ); client_a .send_event(&comment) .await .expect("Failed to send comment"); println!("Layer 3 comment {} sent to relay_a", comment_id); client_a.disconnect().await; // 7. Wait and verify comment syncs to relay_b let comment_filter = Filter::new() .kind(Kind::Comment) // Kind 1111 .author(keys.public_key()) .id(comment_id); let comment_synced = wait_for_event_on_relay(relay_b.url(), comment_filter, Duration::from_secs(5)).await; println!( "Comment {} synced to relay_b: {}", comment_id, comment_synced ); // 8. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( comment_synced, "Layer 3 comment with uppercase 'E' tag should have synced to relay_b" ); } /// Test 9c: Layer 3 sync with 'q' (quote) tag (NIP-18) /// /// Verifies that Layer 3 events (kind 1 quotes) with 'q' (quote) /// tags sync correctly between relays when referencing a Layer 2 event. /// /// The 'q' tag is used in NIP-18 for quotes/reposts. #[tokio::test] async fn test_layer3_sync_with_q_tag() { // 1. Start relays let relay_a = TestRelay::start().await; println!( "relay_a started at {} (domain: {})", relay_a.url(), relay_a.domain() ); let relay_b = TestRelay::start_with_sync(None).await; println!( "relay_b started at {} (domain: {})", relay_b.url(), relay_b.domain() ); let keys = Keys::generate(); // 2. Create and send repository announcement to both relays with git data let repo_id = "test-repo-tag-9c"; let domains = vec![relay_a.domain(), relay_b.domain()]; let domain_refs: Vec<&str> = domains.iter().map(|s| s.as_str()).collect(); let (_announcement, _git_dir_a) = setup_announcement_on_relay(&relay_a, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_a with git data"); let (_announcement_b, _git_dir_b) = setup_announcement_on_relay(&relay_b, &keys, &domain_refs, repo_id).await; println!("Announcement set up on relay_b with git data (triggers discovery)"); // 3. Wait for discovery tokio::time::sleep(Duration::from_secs(1)).await; // 4. Create and send Layer 2 issue (parent event) let repo_coordinate = repo_coord(&keys, repo_id); let issue = build_layer2_issue_event(&keys, &repo_coordinate, "Parent Issue for Tag 9c Test") .expect("Failed to create issue"); let issue_id = issue.id; let client_a = TestClient::new(relay_a.url(), keys.clone()) .await .expect("Failed to connect to relay_a"); client_a .send_event(&issue) .await .expect("Failed to send issue"); println!("Layer 2 issue {} sent to relay_a", issue_id); // 5. Wait for issue to sync to relay_b let issue_filter = Filter::new().kind(Kind::GitIssue).id(issue_id); let issue_synced = wait_for_event_on_relay(relay_b.url(), issue_filter, Duration::from_secs(5)).await; println!("Issue synced to relay_b: {}", issue_synced); assert!(issue_synced, "Layer 2 issue should sync first"); // Wait for Layer 3 subscriptions to be established tokio::time::sleep(Duration::from_millis(500)).await; // 6. Create and send Layer 3 quote with 'q' tag (kind 1) let quote = build_layer3_quote_with_q_tag(&keys, &issue_id, "Quote with q tag") .expect("Failed to create quote"); let quote_id = quote.id; println!( "Created quote {} (kind {}) with 'q' tag", quote_id, quote.kind.as_u16() ); client_a .send_event("e) .await .expect("Failed to send quote"); println!("Layer 3 quote {} sent to relay_a", quote_id); client_a.disconnect().await; // 7. Wait and verify quote syncs to relay_b let quote_filter = Filter::new() .kind(Kind::TextNote) // Kind 1 .author(keys.public_key()) .id(quote_id); let quote_synced = wait_for_event_on_relay(relay_b.url(), quote_filter, Duration::from_secs(5)).await; println!("Quote {} synced to relay_b: {}", quote_id, quote_synced); // 8. Cleanup relay_b.stop().await; relay_a.stop().await; assert!( quote_synced, "Layer 3 quote with 'q' tag should have synced to relay_b" ); }