From bf7f4d5381203d5c27b2811d62c5b1781533aa2b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 19 Nov 2025 17:01:36 +0000 Subject: fix some clippy fmt warnings --- .../src/specs/grasp01/event_acceptance_policy.rs | 729 +++++++++++++-------- grasp-audit/src/specs/grasp01/mod.rs | 2 +- grasp-audit/src/specs/grasp01/nip01_smoke.rs | 112 ++-- grasp-audit/src/specs/grasp01/nip11_document.rs | 71 +- grasp-audit/src/specs/mod.rs | 6 +- 5 files changed, 543 insertions(+), 377 deletions(-) (limited to 'grasp-audit/src/specs') diff --git a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs index 176b19a..c257155 100644 --- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs +++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs @@ -88,8 +88,8 @@ //! - Forward reference tests verify out-of-order event acceptance //! - Transitive tests verify multi-hop acceptance chains +use crate::{AuditClient, AuditResult, FixtureKind, TestContext, TestResult}; use nostr_sdk::{Event, Filter, Kind, Tag, TagKind, Timestamp}; -use crate::{AuditClient, AuditResult, TestResult, TestContext, FixtureKind}; use std::time::Duration; /// Test suite for GRASP-01 event acceptance policy @@ -99,42 +99,42 @@ impl EventAcceptancePolicyTests { /// Run all event acceptance policy tests pub async fn run_all(client: &AuditClient) -> AuditResult { let mut results = AuditResult::new("GRASP-01 Nostr Event Acceptance Policy Tests"); - + // Repository Announcement Acceptance Tests results.add(Self::test_accept_valid_repo_announcement(client).await); results.add(Self::test_reject_repo_announcement_missing_clone_tag(client).await); results.add(Self::test_reject_repo_announcement_missing_relays_tag(client).await); - + // Repository State Announcement Tests results.add(Self::test_accept_valid_repo_state_announcement(client).await); - + // Group 1: Accept Events Tagging Accepted Repositories results.add(Self::test_accept_issue_via_a_tag(client).await); - results.add(Self::test_accept_comment_via_A_tag(client).await); + results.add(Self::test_accept_comment_via_capital_a_tag(client).await); results.add(Self::test_accept_kind1_via_q_tag(client).await); - + // Group 2: Accept Events Tagging Accepted Events (Transitive) results.add(Self::test_accept_issue_quoting_issue_via_q(client).await); - results.add(Self::test_accept_comment_via_E_tag(client).await); + results.add(Self::test_accept_comment_via_capital_e_tag(client).await); results.add(Self::test_accept_kind1_via_e_tag(client).await); - + // Group 3: Accept Events Tagged BY Accepted Events (Forward Refs) results.add(Self::test_accept_kind1_referenced_in_issue(client).await); results.add(Self::test_accept_comment_referenced_in_comment(client).await); results.add(Self::test_accept_kind1_referenced_in_kind1(client).await); - + // Group 4: Reject Unrelated Events results.add(Self::test_reject_orphan_issue(client).await); results.add(Self::test_reject_orphan_kind1(client).await); results.add(Self::test_reject_comment_quoting_other_repo(client).await); - + results } - + // ============================================================ // Repository Announcement Acceptance Tests // ============================================================ - + /// Test: Accept valid repository announcements /// /// Spec: Lines 3-5 of ../grasp/01.md @@ -152,41 +152,52 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext for mode-aware fixture management let ctx = TestContext::new(client); - + // Request repository fixture - behavior depends on mode - let event = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let event = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Get relay URL for validation - let relay_url = client.client().relays().await + let relay_url = client + .client() + .relays() + .await .keys() .next() .ok_or("No relay connected")? .to_string(); - + // Convert WebSocket URL to HTTP URL for validation let http_url = relay_url .replace("ws://", "http://") .replace("wss://", "https://"); - + // Extract repo_id from the event's d tag - let repo_id = event.tags.iter() + let repo_id = event + .tags + .iter() .find(|t| t.kind() == TagKind::d()) .and_then(|t| t.content()) .ok_or("Missing d tag in announcement")? .to_string(); - + let event_id = event.id; - + // Query back to verify it was accepted and stored let filter = Filter::new() .kind(Kind::GitRepoAnnouncement) .author(client.public_key()) .identifier(&repo_id); - - let events = client.query(filter).await + + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query events from relay: {}", e))?; - + // Verify we got the event back if events.is_empty() { return Err(format!( @@ -194,41 +205,43 @@ impl EventAcceptancePolicyTests { event_id, repo_id )); } - + // Verify it's the same event - let stored_event = events.iter() - .find(|e| e.id == event_id) - .ok_or(format!( - "Stored event ID doesn't match sent event. Expected: {}, Got {} events", - event_id, events.len() - ))?; - + let stored_event = events.iter().find(|e| e.id == event_id).ok_or(format!( + "Stored event ID doesn't match sent event. Expected: {}, Got {} events", + event_id, + events.len() + ))?; + // Verify key tags are present - let has_clone_tag = stored_event.tags.iter() - .any(|t| { - t.kind() == TagKind::Custom("clone".into()) && - t.content().map(|c| c.contains(&http_url)).unwrap_or(false) - }); - - let has_relays_tag = stored_event.tags.iter() - .any(|t| { - t.kind() == TagKind::Custom("relays".into()) && - t.content() == Some(&relay_url) - }); - + let has_clone_tag = stored_event.tags.iter().any(|t| { + t.kind() == TagKind::Custom("clone".into()) + && t.content().map(|c| c.contains(&http_url)).unwrap_or(false) + }); + + let has_relays_tag = stored_event.tags.iter().any(|t| { + t.kind() == TagKind::Custom("relays".into()) && t.content() == Some(&relay_url) + }); + if !has_clone_tag { - return Err(format!("Stored event missing clone tag with service URL ({})", http_url)); + return Err(format!( + "Stored event missing clone tag with service URL ({})", + http_url + )); } - + if !has_relays_tag { - return Err(format!("Stored event missing relays tag with service URL ({})", relay_url)); + return Err(format!( + "Stored event missing relays tag with service URL ({})", + relay_url + )); } - + Ok(()) }) .await } - + /// Test: Reject repo announcements not listing service in clone tag /// /// Spec: Line 5 of ../grasp/01.md @@ -241,39 +254,54 @@ impl EventAcceptancePolicyTests { ) .run(|| async { // Get relay URL from client - let relay_url = client.client().relays().await + let relay_url = client + .client() + .relays() + .await .keys() .next() .ok_or("No relay connected - client has no active relay connections")? .to_string(); - + // Create unique repository identifier let timestamp = Timestamp::now().as_u64(); let repo_id = format!("test-repo-no-clone-{}", timestamp); - + // Create repo announcement WITHOUT service in clone tag - let event = client.event_builder(Kind::GitRepoAnnouncement, "") + let event = client + .event_builder(Kind::GitRepoAnnouncement, "") .tag(Tag::identifier(&repo_id)) - .tag(Tag::custom(TagKind::Custom("name".into()), vec!["Test Repo No Clone"])) - .tag(Tag::custom(TagKind::Custom("clone".into()), vec!["https://github.com/user/repo.git"])) // NOT this service - .tag(Tag::custom(TagKind::Custom("relays".into()), vec![relay_url.clone()])) // Correct relay + .tag(Tag::custom( + TagKind::Custom("name".into()), + vec!["Test Repo No Clone"], + )) + .tag(Tag::custom( + TagKind::Custom("clone".into()), + vec!["https://github.com/user/repo.git"], + )) // NOT this service + .tag(Tag::custom( + TagKind::Custom("relays".into()), + vec![relay_url.clone()], + )) // Correct relay .build(client.keys()) .map_err(|e| format!("Failed to build event: {}", e))?; - + let event_id = event.id; - + // Send event - expect rejection - let send_result = client.send_event(event.clone()).await; - + let _send_result = client.send_event(event.clone()).await; + // Query to verify event is NOT stored let filter = Filter::new() .kind(Kind::GitRepoAnnouncement) .author(client.public_key()) .identifier(&repo_id); - - let events = client.query(filter).await + + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query events from relay: {}", e))?; - + // Verify event was rejected (not stored) if events.iter().any(|e| e.id == event_id) { return Err(format!( @@ -282,12 +310,12 @@ impl EventAcceptancePolicyTests { event_id, relay_url )); } - + Ok(()) }) .await } - + /// Test: Reject repo announcements not listing service in relays tag /// /// Spec: Line 5 of ../grasp/01.md @@ -300,44 +328,63 @@ impl EventAcceptancePolicyTests { ) .run(|| async { // Get relay URL from client - let relay_url = client.client().relays().await + let relay_url = client + .client() + .relays() + .await .keys() .next() .ok_or("No relay connected - client has no active relay connections")? .to_string(); - + // Convert WebSocket URL to HTTP URL for clone tag let http_url = relay_url .replace("ws://", "http://") .replace("wss://", "https://"); - + // Create unique repository identifier let timestamp = Timestamp::now().as_u64(); let repo_id = format!("test-repo-no-relays-{}", timestamp); - + // Create repo announcement WITHOUT service in relays tag - let event = client.event_builder(Kind::GitRepoAnnouncement, "") + let event = client + .event_builder(Kind::GitRepoAnnouncement, "") .tag(Tag::identifier(&repo_id)) - .tag(Tag::custom(TagKind::custom("name"), vec!["Test Repo No Relays"])) - .tag(Tag::custom(TagKind::custom("clone"), vec![format!("{}/{}/test-repo.git", http_url, client.public_key())])) // Correct clone - .tag(Tag::custom(TagKind::custom("relays"), vec!["wss://relay.damus.io"])) // NOT this service + .tag(Tag::custom( + TagKind::custom("name"), + vec!["Test Repo No Relays"], + )) + .tag(Tag::custom( + TagKind::custom("clone"), + vec![format!( + "{}/{}/test-repo.git", + http_url, + client.public_key() + )], + )) // Correct clone + .tag(Tag::custom( + TagKind::custom("relays"), + vec!["wss://relay.damus.io"], + )) // NOT this service .build(client.keys()) .map_err(|e| format!("Failed to build event: {}", e))?; - + let event_id = event.id; - + // Send event - expect rejection let _send_result = client.send_event(event.clone()).await; - + // Query to verify event is NOT stored let filter = Filter::new() .kind(Kind::GitRepoAnnouncement) .author(client.public_key()) .identifier(&repo_id); - - let events = client.query(filter).await + + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query events from relay: {}", e))?; - + // Verify event was rejected (not stored) if events.iter().any(|e| e.id == event_id) { return Err(format!( @@ -346,16 +393,16 @@ impl EventAcceptancePolicyTests { event_id, relay_url )); } - + Ok(()) }) .await } - + // ============================================================ // Repository State Announcement Tests // ============================================================ - + /// Test: Accept valid repository state announcements /// /// Spec: Lines 6-7 of ../grasp/01.md @@ -374,31 +421,39 @@ impl EventAcceptancePolicyTests { .run(|| async { // NEW: Create TestContext for mode-aware fixture management let ctx = TestContext::new(client); - + // NEW: Request repository fixture - behavior depends on mode // CI mode: Creates fresh repo for this test // Production mode: Returns cached repo if available - let repo_event = ctx.get_fixture(FixtureKind::RepoState).await - .map_err(|e| format!("Test setup failed: could not get repository state fixture: {}", e))?; - + let repo_event = ctx.get_fixture(FixtureKind::RepoState).await.map_err(|e| { + format!( + "Test setup failed: could not get repository state fixture: {}", + e + ) + })?; + // Extract repo_id from the repository announcement - let repo_id = repo_event.tags.iter() + let repo_id = repo_event + .tags + .iter() .find(|t| t.kind() == TagKind::d()) .and_then(|t| t.content()) .ok_or("Missing d tag in repository announcement")? .to_string(); - + let event_id = repo_event.id; - + // Query back to verify it was accepted and stored let filter = Filter::new() .kind(Kind::Custom(30618)) .author(client.public_key()) .identifier(&repo_id); - - let events = client.query(filter).await + + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query events from relay: {}", e))?; - + // Verify we got the event back if events.is_empty() { return Err(format!( @@ -406,16 +461,16 @@ impl EventAcceptancePolicyTests { event_id, repo_id )); } - + Ok(()) }) .await } - + // ============================================================ // Helper Functions (6 total) // ============================================================ - + /// Extract the `d` tag value from an event fn extract_d_tag(event: &Event) -> Option { for tag in event.tags.iter() { @@ -426,15 +481,16 @@ impl EventAcceptancePolicyTests { } None } - + /// Create a basic repository announcement (kind 30617) /// Uses the client's create_repo_announcement helper which includes required clone and relays tags async fn create_test_repo(client: &AuditClient, repo_id: &str) -> Result { - client.create_repo_announcement(repo_id) + client + .create_repo_announcement(repo_id) .await .map_err(|e| format!("Test setup failed: could not create test repository: {}", e)) } - + /// Create an issue (kind 1621) that references a repository /// Uses AuditClient::create_issue helper method fn create_issue_for_repo( @@ -442,10 +498,11 @@ impl EventAcceptancePolicyTests { repo_event: &Event, issue_title: &str, ) -> Result { - client.create_issue(repo_event, issue_title, "issue content", vec![]) + client + .create_issue(repo_event, issue_title, "issue content", vec![]) .map_err(|e| format!("Test setup failed: could not create test issue: {}", e)) } - + /// Create a NIP-22 comment (kind 1111) for an event /// Uses AuditClient::create_comment helper method fn create_comment_for_event( @@ -453,10 +510,11 @@ impl EventAcceptancePolicyTests { event: &Event, content: &str, ) -> Result { - client.create_comment(event, content, vec![]) + client + .create_comment(event, content, vec![]) .map_err(|e| format!("Test setup failed: could not create test comment: {}", e)) } - + /// Send event and verify it was accepted (stored by relay) async fn send_and_verify_accepted( client: &AuditClient, @@ -464,23 +522,27 @@ impl EventAcceptancePolicyTests { description: &str, ) -> Result<(), String> { let event_id = event.id; - - client.send_event(event).await + + client + .send_event(event) + .await .map_err(|e| format!("Failed to send event to relay: {}", e))?; - + tokio::time::sleep(Duration::from_millis(100)).await; - + let filter = Filter::new().id(event_id); - let events = client.query(filter).await + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query relay for verification: {}", e))?; - + if events.is_empty() { return Err(format!("Event should be accepted: {}", description)); } - + Ok(()) } - + /// Send event and verify it was rejected (NOT stored by relay) async fn send_and_verify_rejected( client: &AuditClient, @@ -488,27 +550,31 @@ impl EventAcceptancePolicyTests { description: &str, ) -> Result<(), String> { let event_id = event.id; - - client.send_event(event).await + + client + .send_event(event) + .await .map_err(|e| format!("Failed to send event to relay: {}", e))?; - + tokio::time::sleep(Duration::from_millis(100)).await; - + let filter = Filter::new().id(event_id); - let events = client.query(filter).await + let events = client + .query(filter) + .await .map_err(|e| format!("Failed to query relay for verification: {}", e))?; - + if !events.is_empty() { return Err(format!("Event should be rejected: {}", description)); } - + Ok(()) } - + // ============================================================ // Group 1: Accept Events Tagging Accepted Repositories (3 tests) // ============================================================ - + /// Test 1.1: Issue referencing repo via `a` tag should be accepted /// /// **EXAMPLE: Using TestContext for prerequisite events** @@ -522,28 +588,33 @@ impl EventAcceptancePolicyTests { .run(|| async { // NEW: Create TestContext let ctx = TestContext::new(client); - + // NEW: Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // 2. Create issue that references the repo let issue = Self::create_issue_for_repo(client, &repo, "Test Issue 1")?; - + // 3. Send issue and verify it's accepted - Self::send_and_verify_accepted(client, issue, "issue referencing repo via 'a' tag").await?; - + Self::send_and_verify_accepted(client, issue, "issue referencing repo via 'a' tag") + .await?; + Ok(()) }) .await } - + /// Test 1.2: NIP-22 comment with root `A` tag referencing repo should be accepted /// /// **Using TestContext pattern:** /// - In CI mode: Creates fresh repo for full isolation /// - In Production mode: Reuses cached repo to minimize events - async fn test_accept_comment_via_A_tag(client: &AuditClient) -> TestResult { + async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult { TestResult::new( "accept_comment_via_A_tag", "GRASP-01:event-acceptance:1.2", @@ -552,37 +623,44 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Extract repo_id and create `A` tag manually - let repo_id = Self::extract_d_tag(&repo) - .ok_or("Failed to extract repo_id from repo event")?; + let repo_id = + Self::extract_d_tag(&repo).ok_or("Failed to extract repo_id from repo event")?; let a_tag_value = format!("30617:{}:{}", repo.pubkey, repo_id); - + // Create comment with `A` tag (root reference to repo) let tags = vec![ - Tag::custom(TagKind::custom("A"), vec![a_tag_value.clone(), "".to_string(), "root".to_string()]), + Tag::custom( + TagKind::custom("A"), + vec![a_tag_value.clone(), "".to_string(), "root".to_string()], + ), Tag::custom(TagKind::custom("K"), vec!["30617".to_string()]), Tag::public_key(repo.pubkey), ]; - + let comment = client .event_builder(Kind::Custom(1111), "Comment on repo") .tags(tags) .build(client.keys()) .map_err(|e| format!("Failed to build comment: {}", e))?; - + // Send comment and verify it's accepted Self::send_and_verify_accepted(client, comment, "comment with 'A' tag to repo").await?; - + Ok(()) }) .await } - + /// Test 1.3: Kind 1 text note quoting repo via `q` tag should be accepted /// /// **Using TestContext pattern:** @@ -597,39 +675,41 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Extract repo_id and create `q` tag - let repo_id = Self::extract_d_tag(&repo) - .ok_or("Failed to extract repo_id from repo event")?; + let repo_id = + Self::extract_d_tag(&repo).ok_or("Failed to extract repo_id from repo event")?; let a_tag_value = format!("30617:{}:{}", repo.pubkey, repo_id); - + // Create kind 1 note with `q` tag (quote reference to repo) - let tags = vec![ - Tag::custom(TagKind::custom("q"), vec![a_tag_value]), - ]; - + let tags = vec![Tag::custom(TagKind::custom("q"), vec![a_tag_value])]; + let note = client .event_builder(Kind::TextNote, "Mentioning this repo") .tags(tags) .build(client.keys()) .map_err(|e| format!("Failed to build note: {}", e))?; - + // Send note and verify it's accepted Self::send_and_verify_accepted(client, note, "kind 1 with 'q' tag to repo").await?; - + Ok(()) }) .await } - + // ============================================================ // Group 2: Accept Events Tagging Accepted Events (3 tests) // ============================================================ - + /// Test 2.1: Issue quoting another accepted issue should be accepted (transitive) /// /// **Using TestContext pattern:** @@ -644,37 +724,44 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repo with issue fixture (mode-aware) - returns the issue event - let issue_a = ctx.get_fixture(FixtureKind::RepoWithIssue).await - .map_err(|e| format!("Test setup failed: could not get repo with issue fixture: {}", e))?; - + let issue_a = ctx + .get_fixture(FixtureKind::RepoWithIssue) + .await + .map_err(|e| { + format!( + "Test setup failed: could not get repo with issue fixture: {}", + e + ) + })?; + // Create Repo B but DON'T send it (unaccepted) - just for creating Issue B let repo_b = Self::create_test_repo(client, "repo-b").await?; - + // Create Issue B that quotes accepted Issue A via 'q' tag (should make it accepted) - let additional_tags = vec![ - Tag::custom(TagKind::custom("q"), vec![issue_a.id.to_hex()]), - ]; - + let additional_tags = + vec![Tag::custom(TagKind::custom("q"), vec![issue_a.id.to_hex()])]; + let issue_b = client .create_issue(&repo_b, "Issue B", "issue content", additional_tags) .map_err(|e| format!("Failed to build issue B: {}", e))?; - + // Send Issue B and verify it's ACCEPTED (via transitive quote to Issue A) - Self::send_and_verify_accepted(client, issue_b, "issue B quoting accepted issue A").await?; - + Self::send_and_verify_accepted(client, issue_b, "issue B quoting accepted issue A") + .await?; + Ok(()) }) .await } - + /// Test 2.2: NIP-22 comment with root 'E' tag to accepted issue should be accepted /// /// **Using TestContext pattern:** /// - In CI mode: Creates fresh repo+issue for full isolation /// - In Production mode: Reuses cached repo+issue to minimize events - async fn test_accept_comment_via_E_tag(client: &AuditClient) -> TestResult { + async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult { TestResult::new( "accept_comment_via_E_tag", "GRASP-01:event-acceptance:2.2", @@ -683,22 +770,30 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repo with issue fixture (mode-aware) - returns the issue event - let issue = ctx.get_fixture(FixtureKind::RepoWithIssue).await - .map_err(|e| format!("Test setup failed: could not get repo with issue fixture: {}", e))?; - + let issue = ctx + .get_fixture(FixtureKind::RepoWithIssue) + .await + .map_err(|e| { + format!( + "Test setup failed: could not get repo with issue fixture: {}", + e + ) + })?; + // Create comment using the helper (which adds NIP-22 tags including 'E') let comment = Self::create_comment_for_event(client, &issue, "Comment content")?; - + // Send comment and verify it's accepted (via E tag to accepted issue) - Self::send_and_verify_accepted(client, comment, "comment with E tag to accepted issue").await?; - + Self::send_and_verify_accepted(client, comment, "comment with E tag to accepted issue") + .await?; + Ok(()) }) .await } - + /// Test 2.3: Kind 1 note with 'e' tag reply to accepted kind 1 should be accepted /// /// **Using TestContext pattern:** @@ -713,43 +808,52 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Create Kind 1 A that quotes the repo (makes it accepted) - let repo_id = Self::extract_d_tag(&repo) - .ok_or("Failed to extract repo_id")?; + let repo_id = Self::extract_d_tag(&repo).ok_or("Failed to extract repo_id")?; let a_tag_value = format!("30617:{}:{}", repo.pubkey, repo_id); - + let kind1_a = client .event_builder(Kind::TextNote, "Note A about repo") .tags(vec![Tag::custom(TagKind::custom("q"), vec![a_tag_value])]) .build(client.keys()) .map_err(|e| format!("Failed to build kind1 A: {}", e))?; - - Self::send_and_verify_accepted(client, kind1_a.clone(), "kind 1 A quoting repo").await?; - + + Self::send_and_verify_accepted(client, kind1_a.clone(), "kind 1 A quoting repo") + .await?; + // Create Kind 1 B that replies to Kind 1 A via 'e' tag let kind1_b = client .event_builder(Kind::TextNote, "Reply to Note A") .tags(vec![Tag::event(kind1_a.id)]) .build(client.keys()) .map_err(|e| format!("Failed to build kind1 B: {}", e))?; - + // Send Kind 1 B and verify it's accepted (via 'e' tag to accepted kind 1 A) - Self::send_and_verify_accepted(client, kind1_b, "kind 1 B replying to accepted kind 1 A").await?; - + Self::send_and_verify_accepted( + client, + kind1_b, + "kind 1 B replying to accepted kind 1 A", + ) + .await?; + Ok(()) }) .await } - + // ============================================================ // Group 3: Accept Events Tagged BY Accepted Events (3 tests) // ============================================================ - + /// Test 3.1: Kind 1 note should be accepted when referenced by an accepted issue (forward ref) /// /// **Using TestContext pattern:** @@ -764,44 +868,61 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Create Kind 1 note locally but DON'T send it yet let kind1_note = client .event_builder(Kind::TextNote, "Note to be referenced") .build(client.keys()) .map_err(|e| format!("Failed to build kind1: {}", e))?; - + // Create and send issue that QUOTES the unsent Kind 1 note let issue_tags = vec![ // Reference to accepted repo - Tag::custom(TagKind::custom("a"), vec![ - format!("30617:{}:{}", repo.pubkey, Self::extract_d_tag(&repo).unwrap()) - ]), - Tag::custom(TagKind::custom("subject"), vec!["Issue referencing kind1".to_string()]), + Tag::custom( + TagKind::custom("a"), + vec![format!( + "30617:{}:{}", + repo.pubkey, + Self::extract_d_tag(&repo).unwrap() + )], + ), + Tag::custom( + TagKind::custom("subject"), + vec!["Issue referencing kind1".to_string()], + ), // Quote the Kind 1 that hasn't been sent yet Tag::custom(TagKind::custom("q"), vec![kind1_note.id.to_hex()]), ]; - + let issue = client .event_builder(Kind::Custom(1621), "issue content") .tags(issue_tags) .build(client.keys()) .map_err(|e| format!("Failed to build issue: {}", e))?; - + Self::send_and_verify_accepted(client, issue, "issue quoting unsent kind1").await?; - + // NOW send the Kind 1 note - should be accepted because accepted issue quotes it - Self::send_and_verify_accepted(client, kind1_note, "kind1 note referenced by accepted issue").await?; - + Self::send_and_verify_accepted( + client, + kind1_note, + "kind1 note referenced by accepted issue", + ) + .await?; + Ok(()) }) .await } - + /// Test 3.2: Comment should be accepted when referenced by another accepted comment (forward ref) /// /// **Using TestContext pattern:** @@ -816,58 +937,74 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repo with issue fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::RepoWithIssue).await - .map_err(|e| format!("Test setup failed: could not get repo with issue fixture: {}", e))?; - + let repo = ctx + .get_fixture(FixtureKind::RepoWithIssue) + .await + .map_err(|e| { + format!( + "Test setup failed: could not get repo with issue fixture: {}", + e + ) + })?; + // Extract the issue from the repo event (it's stored as the first 'e' tag) - let issue_id = repo.tags.iter() + let issue_id = repo + .tags + .iter() .find(|t| t.kind() == TagKind::e()) .and_then(|t| t.content()) .ok_or("Missing issue reference in RepoWithIssue fixture")?; - + // Query to get the actual issue event - let filter = Filter::new().id( - nostr_sdk::EventId::from_hex(issue_id) - .map_err(|e| format!("Invalid issue ID: {}", e))? - ); - let issues = client.query(filter).await + let filter = Filter::new().id(nostr_sdk::EventId::from_hex(issue_id) + .map_err(|e| format!("Invalid issue ID: {}", e))?); + let issues = client + .query(filter) + .await .map_err(|e| format!("Failed to query issue: {}", e))?; - let issue = issues.first() - .ok_or("Issue not found")? - .clone(); - + let issue = issues.first().ok_or("Issue not found")?.clone(); + // Create Comment A locally but DON'T send it yet let comment_a = Self::create_comment_for_event(client, &issue, "Comment A")?; - + // Create and send Comment B that quotes Comment A (which hasn't been sent) let comment_b_tags = vec![ // NIP-22 tags for the original issue - Tag::custom(TagKind::custom("E"), vec![issue.id.to_hex(), "".to_string(), "root".to_string()]), + Tag::custom( + TagKind::custom("E"), + vec![issue.id.to_hex(), "".to_string(), "root".to_string()], + ), Tag::event(issue.id), Tag::custom(TagKind::custom("K"), vec![issue.kind.as_u16().to_string()]), Tag::public_key(issue.pubkey), // Quote Comment A which hasn't been sent yet Tag::custom(TagKind::custom("q"), vec![comment_a.id.to_hex()]), ]; - + let comment_b = client .event_builder(Kind::Custom(1111), "Comment B quoting Comment A") .tags(comment_b_tags) .build(client.keys()) .map_err(|e| format!("Failed to build comment B: {}", e))?; - - Self::send_and_verify_accepted(client, comment_b, "comment B quoting unsent comment A").await?; - + + Self::send_and_verify_accepted(client, comment_b, "comment B quoting unsent comment A") + .await?; + // NOW send Comment A - should be accepted because accepted Comment B quotes it - Self::send_and_verify_accepted(client, comment_a, "comment A referenced by accepted comment B").await?; - + Self::send_and_verify_accepted( + client, + comment_a, + "comment A referenced by accepted comment B", + ) + .await?; + Ok(()) }) .await } - + /// Test 3.3: Kind 1 note should be accepted when referenced by another accepted kind 1 (forward ref) /// /// **Using TestContext pattern:** @@ -882,47 +1019,56 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get repository fixture (mode-aware) - let repo = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let repo = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Create Kind 1 A locally but DON'T send it yet let kind1_a = client .event_builder(Kind::TextNote, "Note A to be referenced") .build(client.keys()) .map_err(|e| format!("Failed to build kind1 A: {}", e))?; - + // Create and send Kind 1 B that: // - Quotes the repo (makes it accepted) // - Mentions Kind 1 A via 'e' tag (which hasn't been sent yet) - let repo_id = Self::extract_d_tag(&repo) - .ok_or("Failed to extract repo_id")?; + let repo_id = Self::extract_d_tag(&repo).ok_or("Failed to extract repo_id")?; let a_tag_value = format!("30617:{}:{}", repo.pubkey, repo_id); - + let kind1_b = client .event_builder(Kind::TextNote, "Note B mentioning Note A") .tags(vec![ - Tag::custom(TagKind::custom("q"), vec![a_tag_value]), // Quote repo (accepted) - Tag::event(kind1_a.id), // Mention unsent Kind 1 A + Tag::custom(TagKind::custom("q"), vec![a_tag_value]), // Quote repo (accepted) + Tag::event(kind1_a.id), // Mention unsent Kind 1 A ]) .build(client.keys()) .map_err(|e| format!("Failed to build kind1 B: {}", e))?; - - Self::send_and_verify_accepted(client, kind1_b, "kind1 B mentioning unsent kind1 A").await?; - + + Self::send_and_verify_accepted(client, kind1_b, "kind1 B mentioning unsent kind1 A") + .await?; + // NOW send Kind 1 A - should be accepted because accepted Kind 1 B mentions it - Self::send_and_verify_accepted(client, kind1_a, "kind1 A referenced by accepted kind1 B").await?; - + Self::send_and_verify_accepted( + client, + kind1_a, + "kind1 A referenced by accepted kind1 B", + ) + .await?; + Ok(()) }) .await } - + // ============================================================ // Group 4: Reject Unrelated Events (3 tests) // ============================================================ - + /// Test 4.1: Issue referencing unaccepted repo should be rejected async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult { TestResult::new( @@ -933,18 +1079,24 @@ impl EventAcceptancePolicyTests { .run(|| async { // 1. Create a repo but DON'T send it (so it's unaccepted) let unaccepted_repo = Self::create_test_repo(client, "unaccepted-repo-1").await?; - + // 2. Create issue that references the unaccepted repo - let orphan_issue = Self::create_issue_for_repo(client, &unaccepted_repo, "Orphan Issue")?; - + let orphan_issue = + Self::create_issue_for_repo(client, &unaccepted_repo, "Orphan Issue")?; + // 3. Send issue and verify it's REJECTED - Self::send_and_verify_rejected(client, orphan_issue, "issue referencing unaccepted repo").await?; - + Self::send_and_verify_rejected( + client, + orphan_issue, + "issue referencing unaccepted repo", + ) + .await?; + Ok(()) }) .await } - + /// Test 4.2: Generic kind 1 note with no repo references should be rejected async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult { TestResult::new( @@ -958,15 +1110,16 @@ impl EventAcceptancePolicyTests { .event_builder(Kind::TextNote, "Just a random note") .build(client.keys()) .map_err(|e| format!("Failed to build note: {}", e))?; - + // 2. Send note and verify it's REJECTED - Self::send_and_verify_rejected(client, orphan_note, "kind 1 with no repo references").await?; - + Self::send_and_verify_rejected(client, orphan_note, "kind 1 with no repo references") + .await?; + Ok(()) }) .await } - + /// Test 4.3: Comment quoting unaccepted repo should be rejected /// /// **Using TestContext pattern:** @@ -982,35 +1135,42 @@ impl EventAcceptancePolicyTests { .run(|| async { // Create TestContext let ctx = TestContext::new(client); - + // Get accepted repo A fixture (mode-aware) - let _repo_a = ctx.get_fixture(FixtureKind::ValidRepo).await - .map_err(|e| format!("Test setup failed: could not get valid repository fixture: {}", e))?; - + let _repo_a = ctx.get_fixture(FixtureKind::ValidRepo).await.map_err(|e| { + format!( + "Test setup failed: could not get valid repository fixture: {}", + e + ) + })?; + // Create Repo B but DON'T send it (unaccepted) let repo_b = Self::create_test_repo(client, "unaccepted-repo-b").await?; - + // Extract repo_b info and create comment that quotes repo B (not repo A) - let repo_b_id = Self::extract_d_tag(&repo_b) - .ok_or("Failed to extract repo_b id")?; + let repo_b_id = Self::extract_d_tag(&repo_b).ok_or("Failed to extract repo_b id")?; let repo_b_a_tag = format!("30617:{}:{}", repo_b.pubkey, repo_b_id); - + // Create comment that references ONLY repo B (unaccepted) let tags = vec![ - Tag::custom(TagKind::custom("A"), vec![repo_b_a_tag, "".to_string(), "root".to_string()]), + Tag::custom( + TagKind::custom("A"), + vec![repo_b_a_tag, "".to_string(), "root".to_string()], + ), Tag::custom(TagKind::custom("K"), vec!["30617".to_string()]), Tag::public_key(repo_b.pubkey), ]; - + let comment = client .event_builder(Kind::Custom(1111), "Comment on unaccepted repo") .tags(tags) .build(client.keys()) .map_err(|e| format!("Failed to build comment: {}", e))?; - + // Send comment and verify it's REJECTED (only references unaccepted repo B) - Self::send_and_verify_rejected(client, comment, "comment quoting only unaccepted repo").await?; - + Self::send_and_verify_rejected(client, comment, "comment quoting only unaccepted repo") + .await?; + Ok(()) }) .await @@ -1021,27 +1181,30 @@ impl EventAcceptancePolicyTests { mod tests { use super::*; use crate::AuditConfig; - + #[tokio::test] #[ignore] // Requires running relay async fn test_grasp01_event_acceptance_policy_against_relay() { // Read relay URL from environment variable - must be supplied - let relay_url = std::env::var("RELAY_URL") - .expect("RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081"); - + let relay_url = std::env::var("RELAY_URL").expect( + "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", + ); + let config = AuditConfig::ci(); let client = AuditClient::new(&relay_url, config) .await - .expect(&format!( - "Failed to connect to relay at {}. Ensure relay is running and accessible. \ + .unwrap_or_else(|_| { + panic!( + "Failed to connect to relay at {}. Ensure relay is running and accessible. \ Try: docker run --rm -p 18081:8081 ghcr.io/danconwaydev/ngit-relay:latest", - relay_url - )); - + relay_url + ) + }); + let results = EventAcceptancePolicyTests::run_all(&client).await; results.print_report(); - + // Don't assert all passed yet - some tests may be failing // Future: assert!(results.all_passed(), "Some GRASP-01 event acceptance tests failed"); } -} \ No newline at end of file +} diff --git a/grasp-audit/src/specs/grasp01/mod.rs b/grasp-audit/src/specs/grasp01/mod.rs index 4f4583e..6fd6960 100644 --- a/grasp-audit/src/specs/grasp01/mod.rs +++ b/grasp-audit/src/specs/grasp01/mod.rs @@ -6,4 +6,4 @@ pub mod nip11_document; pub use event_acceptance_policy::EventAcceptancePolicyTests; pub use nip01_smoke::Nip01SmokeTests; -pub use nip11_document::Nip11DocumentTests; \ No newline at end of file +pub use nip11_document::Nip11DocumentTests; diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs index 9ed0f56..204ee60 100644 --- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs +++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs @@ -13,7 +13,7 @@ impl Nip01SmokeTests { /// Run all NIP-01 smoke tests pub async fn run_all(client: &AuditClient) -> AuditResult { let mut results = AuditResult::new("NIP-01 Smoke Tests"); - + // Run tests sequentially to avoid future type issues results.add(Self::test_websocket_connection(client).await); results.add(Self::test_send_receive_event(client).await); @@ -21,10 +21,10 @@ impl Nip01SmokeTests { results.add(Self::test_close_subscription(client).await); results.add(Self::test_reject_invalid_signature(client).await); results.add(Self::test_reject_invalid_event_id(client).await); - + results } - + /// Test 1: Can establish WebSocket connection /// /// Spec: NIP-01 basic requirement @@ -39,17 +39,17 @@ impl Nip01SmokeTests { if !client.is_connected().await { return Err("Failed to connect to relay".to_string()); } - + Ok(()) }) .await } - + /// Test 2: Can send EVENT and receive OK response /// /// Spec: NIP-01 EVENT message /// Requirement: Relay MUST accept valid EVENT messages - /// + /// /// For GRASP servers, we send a NIP-34 repository announcement that lists /// the GRASP server in clone and relays tags (required for acceptance). async fn test_send_receive_event(client: &AuditClient) -> TestResult { @@ -60,15 +60,17 @@ impl Nip01SmokeTests { ) .run(|| async { // Create a NIP-34 announcement event - let event = client.create_repo_announcement("send_receive_event").await + let event = client + .create_repo_announcement("send_receive_event") + .await .map_err(|e| format!("Failed to create announcement: {}", e))?; - + // Send event let event_id = client .send_event(event.clone()) .await .map_err(|e| format!("Failed to send event: {}", e))?; - + // Verify we got an event ID back if event_id != event.id { return Err(format!( @@ -76,43 +78,47 @@ impl Nip01SmokeTests { event.id, event_id )); } - + // Wait a bit for event to be indexed tokio::time::sleep(std::time::Duration::from_millis(100)).await; - + // Try to query it back - let filter = Filter::new() - .kind(Kind::Custom(30617)) - .id(event_id); - + let filter = Filter::new().kind(Kind::Custom(30617)).id(event_id); + let events = client .query(filter) .await .map_err(|e| format!("Failed to query event: {}", e))?; - + if events.is_empty() { // Debug: try querying without audit client filtering eprintln!("Event not found with audit client query, trying direct client query..."); let direct_filter = Filter::new().kind(Kind::Custom(30617)).id(event_id); - let direct_events = client.client().fetch_events(direct_filter, std::time::Duration::from_secs(5)).await + let direct_events = client + .client() + .fetch_events(direct_filter, std::time::Duration::from_secs(5)) + .await .map_err(|e| format!("Direct query failed: {}", e))?; let direct_vec: Vec = direct_events.into_iter().collect(); eprintln!("Direct query found {} events", direct_vec.len()); if !direct_vec.is_empty() { eprintln!("Event tags: {:?}", direct_vec[0].tags); } - return Err(format!("Event not found after sending (direct query found {})", direct_vec.len())); + return Err(format!( + "Event not found after sending (direct query found {})", + direct_vec.len() + )); } - + if events[0].id != event_id { return Err("Retrieved event has different ID".to_string()); } - + Ok(()) }) .await } - + /// Test 3: Can create subscription with REQ /// /// Spec: NIP-01 REQ message @@ -125,34 +131,36 @@ impl Nip01SmokeTests { ) .run(|| async { // Create a NIP-34 announcement event (accepted by GRASP relays) - let event = client.create_repo_announcement("create_subscription").await + let event = client + .create_repo_announcement("create_subscription") + .await .map_err(|e| format!("Failed to create announcement: {}", e))?; - - let event_id = client + + let _event_id = client .send_event(event.clone()) .await .map_err(|e| format!("Failed to send event: {}", e))?; - + // Subscribe to NIP-34 announcements from this author let filter = Filter::new() .kind(Kind::Custom(30617)) .author(client.public_key()); - + let events = client .subscribe(vec![filter], Some(std::time::Duration::from_secs(5))) .await .map_err(|e| format!("Failed to subscribe: {}", e))?; - + // Should have at least our event if events.is_empty() { return Err("No events received from subscription".to_string()); } - + Ok(()) }) .await } - + /// Test 4: Can close subscription with CLOSE /// /// Spec: NIP-01 CLOSE message @@ -167,22 +175,20 @@ impl Nip01SmokeTests { // For now, we just verify we can query events // Full subscription management with CLOSE would require // lower-level WebSocket access - - let filter = Filter::new() - .kind(Kind::TextNote) - .limit(1); - + + let filter = Filter::new().kind(Kind::TextNote).limit(1); + let _events = client .subscribe(vec![filter], Some(std::time::Duration::from_secs(2))) .await .map_err(|e| format!("Failed to subscribe: {}", e))?; - + // If we got here, subscription worked Ok(()) }) .await } - + /// Test 5: Rejects events with invalid signatures /// /// Spec: NIP-01 event validation @@ -199,7 +205,7 @@ impl Nip01SmokeTests { .event_builder(Kind::TextNote, "Invalid signature test") .build(client.keys()) .map_err(|e| format!("Failed to build event: {}", e))?; - + // Corrupt the signature by creating a new event with wrong sig // We'll use a different key to sign, creating an invalid signature let wrong_keys = Keys::generate(); @@ -207,7 +213,7 @@ impl Nip01SmokeTests { .tags(event.tags.clone()) .sign_with_keys(&wrong_keys) .map_err(|e| format!("Failed to build wrong event: {}", e))?; - + // Create event JSON with mismatched pubkey and signature // This should be rejected by the relay let invalid_event_json = serde_json::json!({ @@ -219,24 +225,24 @@ impl Nip01SmokeTests { "content": event.content, "sig": wrong_event.sig.to_string(), // Wrong signature! }); - + // Parse it back to an Event let invalid_event: Event = serde_json::from_value(invalid_event_json) .map_err(|e| format!("Failed to create invalid event: {}", e))?; - + // Try to send the invalid event let result = client.send_event(invalid_event).await; - + // We expect this to fail if result.is_ok() { return Err("Relay accepted event with invalid signature".to_string()); } - + Ok(()) }) .await } - + /// Test 6: Rejects events with invalid event IDs /// /// Spec: NIP-01 event ID validation @@ -253,7 +259,7 @@ impl Nip01SmokeTests { .event_builder(Kind::TextNote, "Invalid ID test") .build(client.keys()) .map_err(|e| format!("Failed to build event: {}", e))?; - + // Create event JSON with corrupted ID let invalid_event_json = serde_json::json!({ "id": EventId::all_zeros().to_hex(), // Wrong ID! @@ -264,19 +270,19 @@ impl Nip01SmokeTests { "content": event.content, "sig": event.sig.to_string(), }); - + // Parse it back to an Event let invalid_event: Event = serde_json::from_value(invalid_event_json) .map_err(|e| format!("Failed to create invalid event: {}", e))?; - + // Try to send the invalid event let result = client.send_event(invalid_event).await; - + // We expect this to fail if result.is_ok() { return Err("Relay accepted event with invalid ID".to_string()); } - + Ok(()) }) .await @@ -287,25 +293,25 @@ impl Nip01SmokeTests { mod tests { use super::*; use crate::AuditConfig; - + // Note: These tests require a running relay // They are integration tests, not unit tests - + #[tokio::test] #[ignore] // Ignore by default since it needs a running relay async fn test_smoke_tests_against_relay() { // RELAY_URL env var must be set - no default fallback let relay_url = std::env::var("RELAY_URL") .expect("RELAY_URL environment variable must be set for integration tests"); - + let config = AuditConfig::ci(); let client = AuditClient::new(&relay_url, config) .await .expect("Failed to connect to relay"); - + let results = Nip01SmokeTests::run_all(&client).await; results.print_report(); - + assert!(results.all_passed(), "Some smoke tests failed"); } } diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs index 3f9c04a..be04777 100644 --- a/grasp-audit/src/specs/grasp01/nip11_document.rs +++ b/grasp-audit/src/specs/grasp01/nip11_document.rs @@ -9,7 +9,6 @@ //! - Handles curation field correctly (present if curated, absent otherwise) use crate::{AuditClient, AuditResult, TestResult}; -use nostr_sdk::prelude::*; pub struct Nip11DocumentTests; @@ -17,25 +16,25 @@ impl Nip11DocumentTests { /// Run all NIP-11 document tests pub async fn run_all(client: &AuditClient) -> AuditResult { let mut results = AuditResult::new("GRASP-01 NIP-11 Document Tests"); - + // NIP-11 relay information tests results.add(Self::test_nip11_document_exists(client).await); results.add(Self::test_nip11_supported_grasps_field(client).await); results.add(Self::test_nip11_repo_acceptance_criteria_field(client).await); results.add(Self::test_nip11_curation_field(client).await); - + results } - + // ========================================================================= // NIP-11 Relay Information Tests // ========================================================================= - + /// Test: Serve NIP-11 document /// /// Spec: Line 11 of ../grasp/01.md /// Requirement: MUST serve NIP-11 document - async fn test_nip11_document_exists(client: &AuditClient) -> TestResult { + async fn test_nip11_document_exists(_client: &AuditClient) -> TestResult { TestResult::new( "nip11_document_exists", "GRASP-01:nostr-relay:11", @@ -52,17 +51,17 @@ impl Nip11DocumentTests { // 4. Verify response is valid JSON // 5. Parse as NIP-11 document // 6. Verify has required fields (name, description, etc.) - + Err("Not implemented yet".to_string()) }) .await } - + /// Test: NIP-11 includes supported_grasps field /// /// Spec: Line 12 of ../grasp/01.md /// Requirement: MUST list supported GRASPs as string array - async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult { + async fn test_nip11_supported_grasps_field(_client: &AuditClient) -> TestResult { TestResult::new( "nip11_supported_grasps_field", "GRASP-01:nostr-relay:12", @@ -76,17 +75,17 @@ impl Nip11DocumentTests { // 4. Verify array includes "GRASP-01" // 5. Verify format: each entry matches pattern "GRASP-\d{2}" // 6. Document other GRASPs found (for info) - + Err("Not implemented yet".to_string()) }) .await } - + /// Test: NIP-11 includes repo_acceptance_criteria field /// /// Spec: Line 13 of ../grasp/01.md /// Requirement: MUST list repository acceptance criteria - async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult { + async fn test_nip11_repo_acceptance_criteria_field(_client: &AuditClient) -> TestResult { TestResult::new( "nip11_repo_acceptance_criteria_field", "GRASP-01:nostr-relay:13", @@ -101,17 +100,17 @@ impl Nip11DocumentTests { // 5. Document the criteria (for info) // Examples: "Must list this relay in clone and relays tags" // "Pre-payment required via Lightning invoice" - + Err("Not implemented yet".to_string()) }) .await } - + /// Test: NIP-11 curation field handling /// /// Spec: Line 14 of ../grasp/01.md /// Requirement: MUST include curation if curated, omit otherwise - async fn test_nip11_curation_field(client: &AuditClient) -> TestResult { + async fn test_nip11_curation_field(_client: &AuditClient) -> TestResult { TestResult::new( "nip11_curation_field", "GRASP-01:nostr-relay:14", @@ -127,39 +126,41 @@ impl Nip11DocumentTests { // 4. If absent: // - Document that no curation beyond SPAM prevention // 5. Both cases are valid per spec - + Err("Not implemented yet".to_string()) }) .await } - } #[cfg(test)] mod tests { use super::*; use crate::AuditConfig; - -#[tokio::test] -#[ignore] // Requires running relay -async fn test_grasp01_nip11_document_against_relay() { - // Read relay URL from environment variable - must be supplied - let relay_url = std::env::var("RELAY_URL") - .expect("RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081"); - - let config = AuditConfig::ci(); - let client = AuditClient::new(&relay_url, config) - .await - .expect(&format!( - "Failed to connect to relay at {}. Ensure relay is running and accessible. \ + + #[tokio::test] + #[ignore] // Requires running relay + async fn test_grasp01_nip11_document_against_relay() { + // Read relay URL from environment variable - must be supplied + let relay_url = std::env::var("RELAY_URL").expect( + "RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081", + ); + + let config = AuditConfig::ci(); + let client = AuditClient::new(&relay_url, config) + .await + .unwrap_or_else(|_| { + panic!( + "Failed to connect to relay at {}. Ensure relay is running and accessible. \ Try: docker run --rm -p 18081:8081 ghcr.io/danconwaydev/ngit-relay:latest", - relay_url - )); - + relay_url + ) + }); + let results = Nip11DocumentTests::run_all(&client).await; results.print_report(); - + // Don't assert all passed yet - tests not implemented // assert!(results.all_passed(), "Some GRASP-01 NIP-11 document tests failed"); } -} \ No newline at end of file +} diff --git a/grasp-audit/src/specs/mod.rs b/grasp-audit/src/specs/mod.rs index c1c277c..a502866 100644 --- a/grasp-audit/src/specs/mod.rs +++ b/grasp-audit/src/specs/mod.rs @@ -3,8 +3,4 @@ pub mod grasp01; // Re-export all test structs from grasp01 module -pub use grasp01::{ - EventAcceptancePolicyTests, - Nip01SmokeTests, - Nip11DocumentTests, -}; +pub use grasp01::{EventAcceptancePolicyTests, Nip01SmokeTests, Nip11DocumentTests}; -- cgit v1.2.3