diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-05 11:48:35 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-05 11:48:35 +0000 |
| commit | d9c9ef2ff92b687f5ff5585b08b2eead8f139a02 (patch) | |
| tree | b2b125899d6724e2f21e0b785b4844703e16722a /grasp-audit | |
| parent | ff00d106da0abd734a233e6445cdd81ffb5f1d9f (diff) | |
feat(grasp-audit): improve test infrastructure and error handling
- Fix compilation error in test setup (use .expect() instead of ?)
- Add comprehensive error messages with troubleshooting guidance
- Implement connection verification in AuditClient with retry logic
- Update AGENTS.md with testing troubleshooting section
- Verify all changes: 4/18 tests passing as expected
Error messages now include:
- Specific context about failures (event IDs, repo IDs, URLs)
- Example commands for resolution (docker, nak verification)
- References to helper scripts (test-ngit-relay.sh)
Tests compile cleanly and run successfully against ngit-relay.
Diffstat (limited to 'grasp-audit')
| -rw-r--r-- | grasp-audit/src/client.rs | 22 | ||||
| -rw-r--r-- | grasp-audit/src/specs/grasp01_nostr_relay.rs | 53 |
2 files changed, 55 insertions, 20 deletions
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs index 4831d3f..b80b59f 100644 --- a/grasp-audit/src/client.rs +++ b/grasp-audit/src/client.rs | |||
| @@ -24,11 +24,12 @@ impl AuditClient { | |||
| 24 | 24 | ||
| 25 | // Wait for connection to establish (with retries) | 25 | // Wait for connection to establish (with retries) |
| 26 | let mut attempts = 0; | 26 | let mut attempts = 0; |
| 27 | let mut connected = false; | ||
| 27 | while attempts < 20 { | 28 | while attempts < 20 { |
| 28 | tokio::time::sleep(Duration::from_millis(100)).await; | 29 | tokio::time::sleep(Duration::from_millis(100)).await; |
| 29 | 30 | ||
| 30 | let relays = client.relays().await; | 31 | let relays = client.relays().await; |
| 31 | let connected = relays.values().any(|r| r.is_connected()); | 32 | connected = relays.values().any(|r| r.is_connected()); |
| 32 | 33 | ||
| 33 | if connected { | 34 | if connected { |
| 34 | break; | 35 | break; |
| @@ -37,6 +38,25 @@ impl AuditClient { | |||
| 37 | attempts += 1; | 38 | attempts += 1; |
| 38 | } | 39 | } |
| 39 | 40 | ||
| 41 | // Verify we actually connected | ||
| 42 | if !connected { | ||
| 43 | return Err(anyhow!( | ||
| 44 | "Failed to connect to relay at '{}'\n\ | ||
| 45 | \n\ | ||
| 46 | Possible causes:\n\ | ||
| 47 | • Relay is not running at this address\n\ | ||
| 48 | • Network connectivity issues\n\ | ||
| 49 | • Incorrect URL or port\n\ | ||
| 50 | \n\ | ||
| 51 | To start ngit-relay for testing:\n\ | ||
| 52 | docker run --rm -p 18081:8081 ghcr.io/danconwaydev/ngit-relay:latest\n\ | ||
| 53 | \n\ | ||
| 54 | Or use the test script:\n\ | ||
| 55 | cd grasp-audit && ./test-ngit-relay.sh", | ||
| 56 | relay_url | ||
| 57 | )); | ||
| 58 | } | ||
| 59 | |||
| 40 | // Give it a bit more time to stabilize | 60 | // Give it a bit more time to stabilize |
| 41 | tokio::time::sleep(Duration::from_millis(200)).await; | 61 | tokio::time::sleep(Duration::from_millis(200)).await; |
| 42 | 62 | ||
diff --git a/grasp-audit/src/specs/grasp01_nostr_relay.rs b/grasp-audit/src/specs/grasp01_nostr_relay.rs index 7c4ef1e..5a93672 100644 --- a/grasp-audit/src/specs/grasp01_nostr_relay.rs +++ b/grasp-audit/src/specs/grasp01_nostr_relay.rs | |||
| @@ -82,7 +82,7 @@ impl Grasp01NostrRelayTests { | |||
| 82 | 82 | ||
| 83 | // Get npub for clone URL | 83 | // Get npub for clone URL |
| 84 | let npub = client.public_key().to_bech32() | 84 | let npub = client.public_key().to_bech32() |
| 85 | .map_err(|e| format!("Failed to convert pubkey to npub: {}", e))?; | 85 | .map_err(|e| format!("Failed to convert public key to bech32 npub format: {}", e))?; |
| 86 | 86 | ||
| 87 | // Build kind 30617 repository announcement | 87 | // Build kind 30617 repository announcement |
| 88 | let event = client.event_builder(Kind::GitRepoAnnouncement, "") | 88 | let event = client.event_builder(Kind::GitRepoAnnouncement, "") |
| @@ -92,11 +92,11 @@ impl Grasp01NostrRelayTests { | |||
| 92 | .tag(Tag::custom(TagKind::Custom("clone".into()), vec![format!("{}/{}/{}.git", http_url, npub, repo_id)])) | 92 | .tag(Tag::custom(TagKind::Custom("clone".into()), vec![format!("{}/{}/{}.git", http_url, npub, repo_id)])) |
| 93 | .tag(Tag::custom(TagKind::Custom("relays".into()), vec![relay_url.clone()])) | 93 | .tag(Tag::custom(TagKind::Custom("relays".into()), vec![relay_url.clone()])) |
| 94 | .build(client.keys()) | 94 | .build(client.keys()) |
| 95 | .map_err(|e| format!("Failed to build event: {}", e))?; | 95 | .map_err(|e| format!("Failed to build repository announcement event (kind 30617): {}", e))?; |
| 96 | 96 | ||
| 97 | // Send the event | 97 | // Send the event |
| 98 | let event_id = client.send_event(event.clone()).await | 98 | let event_id = client.send_event(event.clone()).await |
| 99 | .map_err(|e| format!("Failed to send event: {}", e))?; | 99 | .map_err(|e| format!("Failed to send repository announcement to relay: {}", e))?; |
| 100 | 100 | ||
| 101 | // Query back to verify it was accepted and stored | 101 | // Query back to verify it was accepted and stored |
| 102 | let filter = Filter::new() | 102 | let filter = Filter::new() |
| @@ -105,17 +105,23 @@ impl Grasp01NostrRelayTests { | |||
| 105 | .identifier(&repo_id); | 105 | .identifier(&repo_id); |
| 106 | 106 | ||
| 107 | let events = client.query(filter).await | 107 | let events = client.query(filter).await |
| 108 | .map_err(|e| format!("Failed to query events: {}", e))?; | 108 | .map_err(|e| format!("Failed to query events from relay: {}", e))?; |
| 109 | 109 | ||
| 110 | // Verify we got the event back | 110 | // Verify we got the event back |
| 111 | if events.is_empty() { | 111 | if events.is_empty() { |
| 112 | return Err("Event was not stored in relay (possibly rejected)".to_string()); | 112 | return Err(format!( |
| 113 | "Event was not stored in relay (possibly rejected). Event ID: {}, Repo ID: {}", | ||
| 114 | event_id, repo_id | ||
| 115 | )); | ||
| 113 | } | 116 | } |
| 114 | 117 | ||
| 115 | // Verify it's the same event | 118 | // Verify it's the same event |
| 116 | let stored_event = events.iter() | 119 | let stored_event = events.iter() |
| 117 | .find(|e| e.id == event_id) | 120 | .find(|e| e.id == event_id) |
| 118 | .ok_or("Stored event ID doesn't match sent event")?; | 121 | .ok_or(format!( |
| 122 | "Stored event ID doesn't match sent event. Expected: {}, Got {} events", | ||
| 123 | event_id, events.len() | ||
| 124 | ))?; | ||
| 119 | 125 | ||
| 120 | // Verify key tags are present | 126 | // Verify key tags are present |
| 121 | let has_clone_tag = stored_event.tags.iter() | 127 | let has_clone_tag = stored_event.tags.iter() |
| @@ -158,7 +164,7 @@ impl Grasp01NostrRelayTests { | |||
| 158 | let relay_url = client.client().relays().await | 164 | let relay_url = client.client().relays().await |
| 159 | .keys() | 165 | .keys() |
| 160 | .next() | 166 | .next() |
| 161 | .ok_or("No relay connected")? | 167 | .ok_or("No relay connected - client has no active relay connections")? |
| 162 | .to_string(); | 168 | .to_string(); |
| 163 | 169 | ||
| 164 | // Create unique repository identifier | 170 | // Create unique repository identifier |
| @@ -186,11 +192,15 @@ impl Grasp01NostrRelayTests { | |||
| 186 | .identifier(&repo_id); | 192 | .identifier(&repo_id); |
| 187 | 193 | ||
| 188 | let events = client.query(filter).await | 194 | let events = client.query(filter).await |
| 189 | .map_err(|e| format!("Failed to query events: {}", e))?; | 195 | .map_err(|e| format!("Failed to query events from relay: {}", e))?; |
| 190 | 196 | ||
| 191 | // Verify event was rejected (not stored) | 197 | // Verify event was rejected (not stored) |
| 192 | if events.iter().any(|e| e.id == event_id) { | 198 | if events.iter().any(|e| e.id == event_id) { |
| 193 | return Err("Relay accepted announcement without service in clone tag - should reject".to_string()); | 199 | return Err(format!( |
| 200 | "Relay incorrectly accepted announcement without service in clone tag. \ | ||
| 201 | Event ID: {}, Clone URL: https://github.com/user/repo.git (should require {})", | ||
| 202 | event_id, relay_url | ||
| 203 | )); | ||
| 194 | } | 204 | } |
| 195 | 205 | ||
| 196 | Ok(()) | 206 | Ok(()) |
| @@ -650,16 +660,21 @@ mod tests { | |||
| 650 | use super::*; | 660 | use super::*; |
| 651 | use crate::AuditConfig; | 661 | use crate::AuditConfig; |
| 652 | 662 | ||
| 653 | #[tokio::test] | 663 | #[tokio::test] |
| 654 | #[ignore] // Requires running relay | 664 | #[ignore] // Requires running relay |
| 655 | async fn test_grasp01_nostr_relay_against_relay() { | 665 | async fn test_grasp01_nostr_relay_against_relay() { |
| 656 | // Read relay URL from environment variable - must be supplied | 666 | // Read relay URL from environment variable - must be supplied |
| 657 | let relay_url = std::env::var("RELAY_URL")?; | 667 | let relay_url = std::env::var("RELAY_URL") |
| 658 | 668 | .expect("RELAY_URL environment variable must be set. Example: RELAY_URL=ws://localhost:18081"); | |
| 659 | let config = AuditConfig::ci(); | 669 | |
| 660 | let client = AuditClient::new(&relay_url, config) | 670 | let config = AuditConfig::ci(); |
| 661 | .await | 671 | let client = AuditClient::new(&relay_url, config) |
| 662 | .expect("Failed to connect to relay"); | 672 | .await |
| 673 | .expect(&format!( | ||
| 674 | "Failed to connect to relay at {}. Ensure relay is running and accessible. \ | ||
| 675 | Try: docker run --rm -p 18081:8081 ghcr.io/danconwaydev/ngit-relay:latest", | ||
| 676 | relay_url | ||
| 677 | )); | ||
| 663 | 678 | ||
| 664 | let results = Grasp01NostrRelayTests::run_all(&client).await; | 679 | let results = Grasp01NostrRelayTests::run_all(&client).await; |
| 665 | results.print_report(); | 680 | results.print_report(); |