diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-20 22:15:03 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-20 22:20:28 +0000 |
| commit | 519fdc66930280cd1772417dca327ed858333d64 (patch) | |
| tree | 4b20e18ccbc7406106bc72316dc3e26f2b58495f | |
| parent | ca50f5b98f30d0933a510c05db86b608afee73a0 (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.rs | 32 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01/nip01_smoke.rs | 12 | ||||
| -rw-r--r-- | tests/nip01_compliance.rs | 120 | ||||
| -rw-r--r-- | tests/nip34_announcements.rs | 168 |
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; | |||
| 27 | use common::TestRelay; | 27 | use common::TestRelay; |
| 28 | use grasp_audit::*; | 28 | use 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 | /// | 34 | macro_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) |
| 41 | async 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 | /// | 59 | isolated_test!(test_websocket_connection); |
| 71 | /// Critical security test - ensures relay validates: | 60 | isolated_test!(test_send_receive_event); |
| 72 | /// - Event signatures | 61 | isolated_test!(test_create_subscription); |
| 73 | /// - Event IDs | 62 | isolated_test!(test_close_subscription); |
| 74 | /// - Other NIP-01 requirements | 63 | isolated_test!(test_reject_invalid_signature); |
| 75 | #[tokio::test] | 64 | isolated_test!(test_reject_invalid_event_id); |
| 76 | async 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; | |||
| 27 | use common::TestRelay; | 27 | use common::TestRelay; |
| 28 | use grasp_audit::*; | 28 | use 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 | /// | 34 | macro_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"); |
| 43 | async 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 | /// | 59 | isolated_test!(test_accept_valid_repo_announcement); |
| 73 | /// Demonstrates running individual test categories from the suite | 60 | isolated_test!(test_reject_repo_announcement_missing_clone_tag); |
| 74 | #[tokio::test] | 61 | isolated_test!(test_reject_repo_announcement_missing_relays_tag); |
| 75 | async fn test_accepts_repository_announcements() { | 62 | isolated_test!(test_accept_valid_repo_state_announcement); |
| 76 | let relay = TestRelay::start().await; | 63 | isolated_test!(test_accept_issue_via_a_tag); |
| 77 | let config = AuditConfig::ci(); | 64 | isolated_test!(test_accept_comment_via_capital_a_tag); |
| 78 | let client = AuditClient::new(relay.url(), config) | 65 | isolated_test!(test_accept_kind1_via_q_tag); |
| 79 | .await | 66 | isolated_test!(test_accept_issue_quoting_issue_via_q); |
| 80 | .expect("Failed to create audit client"); | 67 | isolated_test!(test_accept_comment_via_capital_e_tag); |
| 81 | 68 | isolated_test!(test_accept_kind1_via_e_tag); | |
| 82 | // Run all tests | 69 | isolated_test!(test_accept_kind1_referenced_in_issue); |
| 83 | let results = specs::EventAcceptancePolicyTests::run_all(&client).await; | 70 | isolated_test!(test_accept_comment_referenced_in_comment); |
| 84 | 71 | isolated_test!(test_accept_kind1_referenced_in_kind1); | |
| 85 | relay.stop().await; | 72 | isolated_test!(test_reject_orphan_issue); |
| 86 | 73 | isolated_test!(test_reject_orphan_kind1); | |
| 87 | // Filter to only repository announcement tests | 74 | isolated_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] | ||
| 117 | async 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 | } | ||