upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-20 22:15:03 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-20 22:20:28 +0000
commit519fdc66930280cd1772417dca327ed858333d64 (patch)
tree4b20e18ccbc7406106bc72316dc3e26f2b58495f
parentca50f5b98f30d0933a510c05db86b608afee73a0 (diff)
refactor: isolate each grasp-audit lib test with minimal boilerplate
- Add isolated_test! macro pattern to nip34_announcements.rs and nip01_compliance.rs - Each test runs with its own fresh relay instance for complete isolation - Make all individual test functions public in grasp-audit library (nip01_smoke.rs, event_acceptance_policy.rs) - Eliminates 122 lines of boilerplate across integration tests - Tests: 15 GRASP-01 event acceptance policy tests + 6 NIP-01 smoke tests - Ensures tests don't interfere with each other, preventing flakiness
-rw-r--r--grasp-audit/src/specs/grasp01/event_acceptance_policy.rs32
-rw-r--r--grasp-audit/src/specs/grasp01/nip01_smoke.rs12
-rw-r--r--tests/nip01_compliance.rs120
-rw-r--r--tests/nip34_announcements.rs168
4 files changed, 105 insertions, 227 deletions
diff --git a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
index c257155..638ae5f 100644
--- a/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
+++ b/grasp-audit/src/specs/grasp01/event_acceptance_policy.rs
@@ -143,7 +143,7 @@ impl EventAcceptancePolicyTests {
143 /// **Using TestContext pattern:** 143 /// **Using TestContext pattern:**
144 /// - In CI mode: Creates fresh repo for full isolation 144 /// - In CI mode: Creates fresh repo for full isolation
145 /// - In Production mode: Reuses cached repo to minimize events 145 /// - In Production mode: Reuses cached repo to minimize events
146 async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult { 146 pub async fn test_accept_valid_repo_announcement(client: &AuditClient) -> TestResult {
147 TestResult::new( 147 TestResult::new(
148 "accept_valid_repo_announcement", 148 "accept_valid_repo_announcement",
149 "GRASP-01:nostr-relay:3-5", 149 "GRASP-01:nostr-relay:3-5",
@@ -246,7 +246,7 @@ impl EventAcceptancePolicyTests {
246 /// 246 ///
247 /// Spec: Line 5 of ../grasp/01.md 247 /// Spec: Line 5 of ../grasp/01.md
248 /// Requirement: MUST reject announcements not listing service (unless GRASP-05) 248 /// Requirement: MUST reject announcements not listing service (unless GRASP-05)
249 async fn test_reject_repo_announcement_missing_clone_tag(client: &AuditClient) -> TestResult { 249 pub async fn test_reject_repo_announcement_missing_clone_tag(client: &AuditClient) -> TestResult {
250 TestResult::new( 250 TestResult::new(
251 "reject_repo_announcement_missing_clone_tag", 251 "reject_repo_announcement_missing_clone_tag",
252 "GRASP-01:nostr-relay:5", 252 "GRASP-01:nostr-relay:5",
@@ -320,7 +320,7 @@ impl EventAcceptancePolicyTests {
320 /// 320 ///
321 /// Spec: Line 5 of ../grasp/01.md 321 /// Spec: Line 5 of ../grasp/01.md
322 /// Requirement: MUST reject announcements not listing service in relays 322 /// Requirement: MUST reject announcements not listing service in relays
323 async fn test_reject_repo_announcement_missing_relays_tag(client: &AuditClient) -> TestResult { 323 pub async fn test_reject_repo_announcement_missing_relays_tag(client: &AuditClient) -> TestResult {
324 TestResult::new( 324 TestResult::new(
325 "reject_repo_announcement_missing_relays_tag", 325 "reject_repo_announcement_missing_relays_tag",
326 "GRASP-01:nostr-relay:5", 326 "GRASP-01:nostr-relay:5",
@@ -412,7 +412,7 @@ impl EventAcceptancePolicyTests {
412 /// This test demonstrates the new TestContext pattern: 412 /// This test demonstrates the new TestContext pattern:
413 /// - In CI mode: Creates fresh repo for full isolation 413 /// - In CI mode: Creates fresh repo for full isolation
414 /// - In Production mode: Reuses cached repo to minimize events 414 /// - In Production mode: Reuses cached repo to minimize events
415 async fn test_accept_valid_repo_state_announcement(client: &AuditClient) -> TestResult { 415 pub async fn test_accept_valid_repo_state_announcement(client: &AuditClient) -> TestResult {
416 TestResult::new( 416 TestResult::new(
417 "accept_valid_repo_state_announcement", 417 "accept_valid_repo_state_announcement",
418 "GRASP-01:nostr-relay:6-7", 418 "GRASP-01:nostr-relay:6-7",
@@ -579,7 +579,7 @@ impl EventAcceptancePolicyTests {
579 /// 579 ///
580 /// **EXAMPLE: Using TestContext for prerequisite events** 580 /// **EXAMPLE: Using TestContext for prerequisite events**
581 /// Demonstrates how TestContext simplifies test setup while supporting dual modes 581 /// Demonstrates how TestContext simplifies test setup while supporting dual modes
582 async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult { 582 pub async fn test_accept_issue_via_a_tag(client: &AuditClient) -> TestResult {
583 TestResult::new( 583 TestResult::new(
584 "accept_issue_via_a_tag", 584 "accept_issue_via_a_tag",
585 "GRASP-01:event-acceptance:1.1", 585 "GRASP-01:event-acceptance:1.1",
@@ -614,7 +614,7 @@ impl EventAcceptancePolicyTests {
614 /// **Using TestContext pattern:** 614 /// **Using TestContext pattern:**
615 /// - In CI mode: Creates fresh repo for full isolation 615 /// - In CI mode: Creates fresh repo for full isolation
616 /// - In Production mode: Reuses cached repo to minimize events 616 /// - In Production mode: Reuses cached repo to minimize events
617 async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult { 617 pub async fn test_accept_comment_via_capital_a_tag(client: &AuditClient) -> TestResult {
618 TestResult::new( 618 TestResult::new(
619 "accept_comment_via_A_tag", 619 "accept_comment_via_A_tag",
620 "GRASP-01:event-acceptance:1.2", 620 "GRASP-01:event-acceptance:1.2",
@@ -666,7 +666,7 @@ impl EventAcceptancePolicyTests {
666 /// **Using TestContext pattern:** 666 /// **Using TestContext pattern:**
667 /// - In CI mode: Creates fresh repo for full isolation 667 /// - In CI mode: Creates fresh repo for full isolation
668 /// - In Production mode: Reuses cached repo to minimize events 668 /// - In Production mode: Reuses cached repo to minimize events
669 async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult { 669 pub async fn test_accept_kind1_via_q_tag(client: &AuditClient) -> TestResult {
670 TestResult::new( 670 TestResult::new(
671 "accept_kind1_via_q_tag", 671 "accept_kind1_via_q_tag",
672 "GRASP-01:event-acceptance:1.3", 672 "GRASP-01:event-acceptance:1.3",
@@ -715,7 +715,7 @@ impl EventAcceptancePolicyTests {
715 /// **Using TestContext pattern:** 715 /// **Using TestContext pattern:**
716 /// - In CI mode: Creates fresh repo+issue for full isolation 716 /// - In CI mode: Creates fresh repo+issue for full isolation
717 /// - In Production mode: Reuses cached repo+issue to minimize events 717 /// - In Production mode: Reuses cached repo+issue to minimize events
718 async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult { 718 pub async fn test_accept_issue_quoting_issue_via_q(client: &AuditClient) -> TestResult {
719 TestResult::new( 719 TestResult::new(
720 "accept_issue_quoting_issue_via_q", 720 "accept_issue_quoting_issue_via_q",
721 "GRASP-01:event-acceptance:2.1", 721 "GRASP-01:event-acceptance:2.1",
@@ -761,7 +761,7 @@ impl EventAcceptancePolicyTests {
761 /// **Using TestContext pattern:** 761 /// **Using TestContext pattern:**
762 /// - In CI mode: Creates fresh repo+issue for full isolation 762 /// - In CI mode: Creates fresh repo+issue for full isolation
763 /// - In Production mode: Reuses cached repo+issue to minimize events 763 /// - In Production mode: Reuses cached repo+issue to minimize events
764 async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult { 764 pub async fn test_accept_comment_via_capital_e_tag(client: &AuditClient) -> TestResult {
765 TestResult::new( 765 TestResult::new(
766 "accept_comment_via_E_tag", 766 "accept_comment_via_E_tag",
767 "GRASP-01:event-acceptance:2.2", 767 "GRASP-01:event-acceptance:2.2",
@@ -799,7 +799,7 @@ impl EventAcceptancePolicyTests {
799 /// **Using TestContext pattern:** 799 /// **Using TestContext pattern:**
800 /// - In CI mode: Creates fresh repo for full isolation 800 /// - In CI mode: Creates fresh repo for full isolation
801 /// - In Production mode: Reuses cached repo to minimize events 801 /// - In Production mode: Reuses cached repo to minimize events
802 async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult { 802 pub async fn test_accept_kind1_via_e_tag(client: &AuditClient) -> TestResult {
803 TestResult::new( 803 TestResult::new(
804 "accept_kind1_via_e_tag", 804 "accept_kind1_via_e_tag",
805 "GRASP-01:event-acceptance:2.3", 805 "GRASP-01:event-acceptance:2.3",
@@ -859,7 +859,7 @@ impl EventAcceptancePolicyTests {
859 /// **Using TestContext pattern:** 859 /// **Using TestContext pattern:**
860 /// - In CI mode: Creates fresh repo for full isolation 860 /// - In CI mode: Creates fresh repo for full isolation
861 /// - In Production mode: Reuses cached repo to minimize events 861 /// - In Production mode: Reuses cached repo to minimize events
862 async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult { 862 pub async fn test_accept_kind1_referenced_in_issue(client: &AuditClient) -> TestResult {
863 TestResult::new( 863 TestResult::new(
864 "accept_kind1_referenced_in_issue", 864 "accept_kind1_referenced_in_issue",
865 "GRASP-01:event-acceptance:3.1", 865 "GRASP-01:event-acceptance:3.1",
@@ -928,7 +928,7 @@ impl EventAcceptancePolicyTests {
928 /// **Using TestContext pattern:** 928 /// **Using TestContext pattern:**
929 /// - In CI mode: Creates fresh repo+issue for full isolation 929 /// - In CI mode: Creates fresh repo+issue for full isolation
930 /// - In Production mode: Reuses cached repo+issue to minimize events 930 /// - In Production mode: Reuses cached repo+issue to minimize events
931 async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult { 931 pub async fn test_accept_comment_referenced_in_comment(client: &AuditClient) -> TestResult {
932 TestResult::new( 932 TestResult::new(
933 "accept_comment_referenced_in_comment", 933 "accept_comment_referenced_in_comment",
934 "GRASP-01:event-acceptance:3.2", 934 "GRASP-01:event-acceptance:3.2",
@@ -1010,7 +1010,7 @@ impl EventAcceptancePolicyTests {
1010 /// **Using TestContext pattern:** 1010 /// **Using TestContext pattern:**
1011 /// - In CI mode: Creates fresh repo for full isolation 1011 /// - In CI mode: Creates fresh repo for full isolation
1012 /// - In Production mode: Reuses cached repo to minimize events 1012 /// - In Production mode: Reuses cached repo to minimize events
1013 async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult { 1013 pub async fn test_accept_kind1_referenced_in_kind1(client: &AuditClient) -> TestResult {
1014 TestResult::new( 1014 TestResult::new(
1015 "accept_kind1_referenced_in_kind1", 1015 "accept_kind1_referenced_in_kind1",
1016 "GRASP-01:event-acceptance:3.3", 1016 "GRASP-01:event-acceptance:3.3",
@@ -1070,7 +1070,7 @@ impl EventAcceptancePolicyTests {
1070 // ============================================================ 1070 // ============================================================
1071 1071
1072 /// Test 4.1: Issue referencing unaccepted repo should be rejected 1072 /// Test 4.1: Issue referencing unaccepted repo should be rejected
1073 async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult { 1073 pub async fn test_reject_orphan_issue(client: &AuditClient) -> TestResult {
1074 TestResult::new( 1074 TestResult::new(
1075 "reject_orphan_issue", 1075 "reject_orphan_issue",
1076 "GRASP-01:event-acceptance:4.1", 1076 "GRASP-01:event-acceptance:4.1",
@@ -1098,7 +1098,7 @@ impl EventAcceptancePolicyTests {
1098 } 1098 }
1099 1099
1100 /// Test 4.2: Generic kind 1 note with no repo references should be rejected 1100 /// Test 4.2: Generic kind 1 note with no repo references should be rejected
1101 async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult { 1101 pub async fn test_reject_orphan_kind1(client: &AuditClient) -> TestResult {
1102 TestResult::new( 1102 TestResult::new(
1103 "reject_orphan_kind1", 1103 "reject_orphan_kind1",
1104 "GRASP-01:event-acceptance:4.2", 1104 "GRASP-01:event-acceptance:4.2",
@@ -1126,7 +1126,7 @@ impl EventAcceptancePolicyTests {
1126 /// - In CI mode: Creates fresh accepted repo for full isolation 1126 /// - In CI mode: Creates fresh accepted repo for full isolation
1127 /// - In Production mode: Reuses cached accepted repo to minimize events 1127 /// - In Production mode: Reuses cached accepted repo to minimize events
1128 /// - Note: Unaccepted repo B is always created fresh (not cached) since it must remain unaccepted 1128 /// - Note: Unaccepted repo B is always created fresh (not cached) since it must remain unaccepted
1129 async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult { 1129 pub async fn test_reject_comment_quoting_other_repo(client: &AuditClient) -> TestResult {
1130 TestResult::new( 1130 TestResult::new(
1131 "reject_comment_quoting_other_repo", 1131 "reject_comment_quoting_other_repo",
1132 "GRASP-01:event-acceptance:4.3", 1132 "GRASP-01:event-acceptance:4.3",
diff --git a/grasp-audit/src/specs/grasp01/nip01_smoke.rs b/grasp-audit/src/specs/grasp01/nip01_smoke.rs
index 204ee60..79220e5 100644
--- a/grasp-audit/src/specs/grasp01/nip01_smoke.rs
+++ b/grasp-audit/src/specs/grasp01/nip01_smoke.rs
@@ -29,7 +29,7 @@ impl Nip01SmokeTests {
29 /// 29 ///
30 /// Spec: NIP-01 basic requirement 30 /// Spec: NIP-01 basic requirement
31 /// Requirement: MUST serve a relay at / via WebSocket 31 /// Requirement: MUST serve a relay at / via WebSocket
32 async fn test_websocket_connection(client: &AuditClient) -> TestResult { 32 pub async fn test_websocket_connection(client: &AuditClient) -> TestResult {
33 TestResult::new( 33 TestResult::new(
34 "websocket_connection", 34 "websocket_connection",
35 "NIP-01:basic", 35 "NIP-01:basic",
@@ -52,7 +52,7 @@ impl Nip01SmokeTests {
52 /// 52 ///
53 /// For GRASP servers, we send a NIP-34 repository announcement that lists 53 /// For GRASP servers, we send a NIP-34 repository announcement that lists
54 /// the GRASP server in clone and relays tags (required for acceptance). 54 /// the GRASP server in clone and relays tags (required for acceptance).
55 async fn test_send_receive_event(client: &AuditClient) -> TestResult { 55 pub async fn test_send_receive_event(client: &AuditClient) -> TestResult {
56 TestResult::new( 56 TestResult::new(
57 "send_receive_event", 57 "send_receive_event",
58 "NIP-01:event-message", 58 "NIP-01:event-message",
@@ -123,7 +123,7 @@ impl Nip01SmokeTests {
123 /// 123 ///
124 /// Spec: NIP-01 REQ message 124 /// Spec: NIP-01 REQ message
125 /// Requirement: Relay MUST support REQ subscriptions 125 /// Requirement: Relay MUST support REQ subscriptions
126 async fn test_create_subscription(client: &AuditClient) -> TestResult { 126 pub async fn test_create_subscription(client: &AuditClient) -> TestResult {
127 TestResult::new( 127 TestResult::new(
128 "create_subscription", 128 "create_subscription",
129 "NIP-01:req-message", 129 "NIP-01:req-message",
@@ -165,7 +165,7 @@ impl Nip01SmokeTests {
165 /// 165 ///
166 /// Spec: NIP-01 CLOSE message 166 /// Spec: NIP-01 CLOSE message
167 /// Requirement: Relay MUST support CLOSE to end subscriptions 167 /// Requirement: Relay MUST support CLOSE to end subscriptions
168 async fn test_close_subscription(client: &AuditClient) -> TestResult { 168 pub async fn test_close_subscription(client: &AuditClient) -> TestResult {
169 TestResult::new( 169 TestResult::new(
170 "close_subscription", 170 "close_subscription",
171 "NIP-01:close-message", 171 "NIP-01:close-message",
@@ -193,7 +193,7 @@ impl Nip01SmokeTests {
193 /// 193 ///
194 /// Spec: NIP-01 event validation 194 /// Spec: NIP-01 event validation
195 /// Requirement: Relay MUST reject events with invalid signatures 195 /// Requirement: Relay MUST reject events with invalid signatures
196 async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult { 196 pub async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult {
197 TestResult::new( 197 TestResult::new(
198 "reject_invalid_signature", 198 "reject_invalid_signature",
199 "NIP-01:validation", 199 "NIP-01:validation",
@@ -247,7 +247,7 @@ impl Nip01SmokeTests {
247 /// 247 ///
248 /// Spec: NIP-01 event ID validation 248 /// Spec: NIP-01 event ID validation
249 /// Requirement: Relay MUST reject events where ID doesn't match hash 249 /// Requirement: Relay MUST reject events where ID doesn't match hash
250 async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult { 250 pub async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult {
251 TestResult::new( 251 TestResult::new(
252 "reject_invalid_event_id", 252 "reject_invalid_event_id",
253 "NIP-01:validation", 253 "NIP-01:validation",
diff --git a/tests/nip01_compliance.rs b/tests/nip01_compliance.rs
index 4cb2af4..6fb721a 100644
--- a/tests/nip01_compliance.rs
+++ b/tests/nip01_compliance.rs
@@ -1,13 +1,13 @@
1//! NIP-01 Compliance Integration Tests 1//! NIP-01 Compliance Integration Tests
2//! 2//!
3//! Tests ngit-grasp relay's NIP-01 compliance using grasp-audit library. 3//! Tests ngit-grasp relay's NIP-01 compliance using grasp-audit library.
4//! Avoids code duplication by delegating to grasp-audit's test suite. 4//! Uses isolated test pattern for complete test independence.
5//! 5//!
6//! # Test Strategy 6//! # Test Strategy
7//! 7//!
8//! - Uses TestRelay fixture for ngit-grasp relay lifecycle management 8//! - Each test runs in complete isolation with its own fresh relay instance
9//! - Uses grasp-audit's Nip01SmokeTests for actual test logic 9//! - Uses macro to eliminate boilerplate while maintaining test isolation
10//! - Minimal duplication - single source of truth in grasp-audit 10//! - Calls individual test methods from grasp-audit for minimal duplication
11//! 11//!
12//! # Running Tests 12//! # Running Tests
13//! 13//!
@@ -16,7 +16,7 @@
16//! cargo test --test nip01_compliance 16//! cargo test --test nip01_compliance
17//! 17//!
18//! # Run specific test 18//! # Run specific test
19//! cargo test --test nip01_compliance test_nip01_smoke 19//! cargo test --test nip01_compliance test_websocket_connection
20//! 20//!
21//! # With output 21//! # With output
22//! cargo test --test nip01_compliance -- --nocapture 22//! cargo test --test nip01_compliance -- --nocapture
@@ -27,87 +27,41 @@ mod common;
27use common::TestRelay; 27use common::TestRelay;
28use grasp_audit::*; 28use grasp_audit::*;
29 29
30/// Test NIP-01 smoke tests against ngit-grasp relay 30/// Macro to generate isolated integration tests
31/// 31///
32/// This test runs all NIP-01 smoke tests from grasp-audit against 32/// Each test runs with its own fresh relay instance to ensure complete isolation.
33/// the ngit-grasp relay implementation. 33/// This eliminates flakiness and ensures tests don't interfere with each other.
34/// 34macro_rules! isolated_test {
35/// Tests cover: 35 ($test_name:ident) => {
36/// - WebSocket connection 36 #[tokio::test]
37/// - Event send/receive 37 async fn $test_name() {
38/// - Subscriptions (REQ/CLOSE) 38 let relay = TestRelay::start().await;
39/// - Event validation (signature, ID) 39 let config = AuditConfig::ci();
40#[tokio::test] 40 let client = AuditClient::new(relay.url(), config)
41async fn test_nip01_smoke() { 41 .await
42 // Start test relay 42 .expect("Failed to create audit client");
43 let relay = TestRelay::start().await; 43
44 44 let result = specs::Nip01SmokeTests::$test_name(&client).await;
45 // Create audit client in CI mode (isolated testing) 45
46 let config = AuditConfig::ci(); 46 relay.stop().await;
47 let client = AuditClient::new(relay.url(), config) 47
48 .await 48 assert!(
49 .expect("Failed to create audit client"); 49 result.passed,
50 50 "{} failed: {}",
51 // Run all NIP-01 smoke tests 51 stringify!($test_name),
52 let results = specs::Nip01SmokeTests::run_all(&client).await; 52 result.error.as_deref().unwrap_or("unknown error")
53 53 );
54 // Print detailed report 54 }
55 results.print_report(); 55 };
56
57 // Stop relay
58 relay.stop().await;
59
60 // Assert all tests passed
61 assert!(
62 results.all_passed(),
63 "NIP-01 smoke tests failed: {}/{} passed",
64 results.passed_count(),
65 results.total_count()
66 );
67} 56}
68 57
69/// Test that relay properly validates events 58// Generate isolated tests for all NIP-01 smoke tests
70/// 59isolated_test!(test_websocket_connection);
71/// Critical security test - ensures relay validates: 60isolated_test!(test_send_receive_event);
72/// - Event signatures 61isolated_test!(test_create_subscription);
73/// - Event IDs 62isolated_test!(test_close_subscription);
74/// - Other NIP-01 requirements 63isolated_test!(test_reject_invalid_signature);
75#[tokio::test] 64isolated_test!(test_reject_invalid_event_id);
76async fn test_relay_validates_events() {
77 let relay = TestRelay::start().await;
78 let config = AuditConfig::ci();
79 let client = AuditClient::new(relay.url(), config)
80 .await
81 .expect("Failed to create audit client");
82
83 // Run smoke tests which include validation tests
84 let results = specs::Nip01SmokeTests::run_all(&client).await;
85
86 relay.stop().await;
87
88 // Filter to validation tests
89 let validation_tests: Vec<_> = results
90 .results
91 .iter()
92 .filter(|t| t.name.contains("reject") || t.name.contains("invalid"))
93 .collect();
94
95 // Should have validation tests
96 assert!(
97 !validation_tests.is_empty(),
98 "No validation tests found (these are critical for security)"
99 );
100
101 // All validation tests should pass
102 for test in validation_tests {
103 assert!(
104 test.passed,
105 "Validation test failed: {} - {}\nThis is a security issue!",
106 test.name,
107 test.error.as_deref().unwrap_or("unknown error")
108 );
109 }
110}
111 65
112/// Test relay lifecycle management 66/// Test relay lifecycle management
113/// 67///
diff --git a/tests/nip34_announcements.rs b/tests/nip34_announcements.rs
index f1cbd05..09d9c8f 100644
--- a/tests/nip34_announcements.rs
+++ b/tests/nip34_announcements.rs
@@ -5,9 +5,9 @@
5//! 5//!
6//! # Test Strategy 6//! # Test Strategy
7//! 7//!
8//! - Uses TestRelay fixture for ngit-grasp relay lifecycle management 8//! - Each test runs in complete isolation with its own fresh relay instance
9//! - Uses grasp-audit's EventAcceptancePolicyTests for actual test logic 9//! - Uses macro to eliminate boilerplate while maintaining test isolation
10//! - Minimal duplication - single source of truth in grasp-audit 10//! - Calls individual test methods from grasp-audit for minimal duplication
11//! 11//!
12//! # Running Tests 12//! # Running Tests
13//! 13//!
@@ -16,7 +16,7 @@
16//! cargo test --test nip34_announcements 16//! cargo test --test nip34_announcements
17//! 17//!
18//! # Run specific test 18//! # Run specific test
19//! cargo test --test nip34_announcements test_grasp01_event_acceptance 19//! cargo test --test nip34_announcements test_reject_orphan_kind1
20//! 20//!
21//! # With output 21//! # With output
22//! cargo test --test nip34_announcements -- --nocapture 22//! cargo test --test nip34_announcements -- --nocapture
@@ -27,124 +27,48 @@ mod common;
27use common::TestRelay; 27use common::TestRelay;
28use grasp_audit::*; 28use grasp_audit::*;
29 29
30/// Test GRASP-01 event acceptance policy against ngit-grasp relay 30/// Macro to generate isolated integration tests
31/// 31///
32/// This test runs all GRASP-01 event acceptance policy tests from grasp-audit 32/// Each test runs with its own fresh relay instance to ensure complete isolation.
33/// against the ngit-grasp relay implementation. 33/// This eliminates rate-limiting issues and ensures tests don't interfere with each other.
34/// 34macro_rules! isolated_test {
35/// Tests cover: 35 ($test_name:ident) => {
36/// - Repository announcement acceptance/rejection 36 #[tokio::test]
37/// - Repository state announcement acceptance 37 async fn $test_name() {
38/// - Events tagging accepted repositories 38 let relay = TestRelay::start().await;
39/// - Transitive event acceptance (events tagging accepted events) 39 let config = AuditConfig::ci();
40/// - Forward reference acceptance (events tagged by accepted events) 40 let client = AuditClient::new(relay.url(), config)
41/// - Rejection of unrelated events 41 .await
42#[tokio::test] 42 .expect("Failed to create audit client");
43async fn test_grasp01_event_acceptance() { 43
44 // Start test relay 44 let result = specs::EventAcceptancePolicyTests::$test_name(&client).await;
45 let relay = TestRelay::start().await; 45
46 46 relay.stop().await;
47 // Create audit client in CI mode (isolated testing) 47
48 let config = AuditConfig::ci(); 48 assert!(
49 let client = AuditClient::new(relay.url(), config) 49 result.passed,
50 .await 50 "{} failed: {}",
51 .expect("Failed to create audit client"); 51 stringify!($test_name),
52 52 result.error.as_deref().unwrap_or("unknown error")
53 // Run all GRASP-01 event acceptance policy tests 53 );
54 let results = specs::EventAcceptancePolicyTests::run_all(&client).await; 54 }
55 55 };
56 // Print detailed report
57 results.print_report();
58
59 // Stop relay
60 relay.stop().await;
61
62 // Assert all tests passed
63 assert!(
64 results.all_passed(),
65 "GRASP-01 event acceptance tests failed: {}/{} passed",
66 results.passed_count(),
67 results.total_count()
68 );
69} 56}
70 57
71/// Test that relay accepts valid repository announcements 58// Generate isolated tests for all GRASP-01 event acceptance policy tests
72/// 59isolated_test!(test_accept_valid_repo_announcement);
73/// Demonstrates running individual test categories from the suite 60isolated_test!(test_reject_repo_announcement_missing_clone_tag);
74#[tokio::test] 61isolated_test!(test_reject_repo_announcement_missing_relays_tag);
75async fn test_accepts_repository_announcements() { 62isolated_test!(test_accept_valid_repo_state_announcement);
76 let relay = TestRelay::start().await; 63isolated_test!(test_accept_issue_via_a_tag);
77 let config = AuditConfig::ci(); 64isolated_test!(test_accept_comment_via_capital_a_tag);
78 let client = AuditClient::new(relay.url(), config) 65isolated_test!(test_accept_kind1_via_q_tag);
79 .await 66isolated_test!(test_accept_issue_quoting_issue_via_q);
80 .expect("Failed to create audit client"); 67isolated_test!(test_accept_comment_via_capital_e_tag);
81 68isolated_test!(test_accept_kind1_via_e_tag);
82 // Run all tests 69isolated_test!(test_accept_kind1_referenced_in_issue);
83 let results = specs::EventAcceptancePolicyTests::run_all(&client).await; 70isolated_test!(test_accept_comment_referenced_in_comment);
84 71isolated_test!(test_accept_kind1_referenced_in_kind1);
85 relay.stop().await; 72isolated_test!(test_reject_orphan_issue);
86 73isolated_test!(test_reject_orphan_kind1);
87 // Filter to only repository announcement tests 74isolated_test!(test_reject_comment_quoting_other_repo); \ No newline at end of file
88 let announcement_tests: Vec<_> = results
89 .results
90 .iter()
91 .filter(|t| {
92 t.spec_ref.contains("repo") || t.name.contains("announcement") || t.name.contains("state")
93 })
94 .collect();
95
96 // Verify we have announcement tests
97 assert!(
98 !announcement_tests.is_empty(),
99 "No repository announcement tests found"
100 );
101
102 // All should pass
103 for test in announcement_tests {
104 assert!(
105 test.passed,
106 "Repository test failed: {} - {}",
107 test.name,
108 test.error.as_deref().unwrap_or("unknown error")
109 );
110 }
111}
112
113/// Test that relay properly validates clone and relays tags
114///
115/// This is a critical security requirement for GRASP-01
116#[tokio::test]
117async fn test_validates_service_tags() {
118 let relay = TestRelay::start().await;
119 let config = AuditConfig::ci();
120 let client = AuditClient::new(relay.url(), config)
121 .await
122 .expect("Failed to create audit client");
123
124 let results = specs::EventAcceptancePolicyTests::run_all(&client).await;
125
126 relay.stop().await;
127
128 // Filter to rejection tests (these verify tag validation)
129 let rejection_tests: Vec<_> = results
130 .results
131 .iter()
132 .filter(|t| t.name.contains("reject"))
133 .collect();
134
135 // Should have rejection tests
136 assert!(
137 !rejection_tests.is_empty(),
138 "No rejection tests found (these are critical for security)"
139 );
140
141 // All rejection tests should pass
142 for test in rejection_tests {
143 assert!(
144 test.passed,
145 "Rejection test failed: {} - {}\nThis is a security issue!",
146 test.name,
147 test.error.as_deref().unwrap_or("unknown error")
148 );
149 }
150}