upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/specs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 06:17:55 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-04 06:17:55 +0000
commit001ca45e385c05b0eaa36d9879e051853aaff107 (patch)
tree603fb85d2563db5b7c418e9fd143d479bd09676e /grasp-audit/src/specs
parentd428baf30feec295870fadda2d335d1e7f89507b (diff)
created POC grasp-auditor
Diffstat (limited to 'grasp-audit/src/specs')
-rw-r--r--grasp-audit/src/specs/mod.rs5
-rw-r--r--grasp-audit/src/specs/nip01_smoke.rs303
2 files changed, 308 insertions, 0 deletions
diff --git a/grasp-audit/src/specs/mod.rs b/grasp-audit/src/specs/mod.rs
new file mode 100644
index 0000000..451ea1f
--- /dev/null
+++ b/grasp-audit/src/specs/mod.rs
@@ -0,0 +1,5 @@
1//! Test specifications
2
3pub mod nip01_smoke;
4
5pub use nip01_smoke::Nip01SmokeTests;
diff --git a/grasp-audit/src/specs/nip01_smoke.rs b/grasp-audit/src/specs/nip01_smoke.rs
new file mode 100644
index 0000000..fc3ec29
--- /dev/null
+++ b/grasp-audit/src/specs/nip01_smoke.rs
@@ -0,0 +1,303 @@
1//! NIP-01 Smoke Tests
2//!
3//! These tests verify basic Nostr relay functionality.
4//! We don't comprehensively test NIP-01 because rust-nostr already has 1000+ tests.
5//! These are just smoke tests to ensure the relay is working at all.
6
7use crate::{AuditClient, AuditResult, TestResult};
8use nostr_sdk::prelude::*;
9
10pub struct Nip01SmokeTests;
11
12impl Nip01SmokeTests {
13 /// Run all NIP-01 smoke tests
14 pub async fn run_all(client: &AuditClient) -> AuditResult {
15 let mut results = AuditResult::new("NIP-01 Smoke Tests");
16
17 // Run tests in parallel
18 let tests = vec![
19 Self::test_websocket_connection(client),
20 Self::test_send_receive_event(client),
21 Self::test_create_subscription(client),
22 Self::test_close_subscription(client),
23 Self::test_reject_invalid_signature(client),
24 Self::test_reject_invalid_event_id(client),
25 ];
26
27 let test_results = futures::future::join_all(tests).await;
28
29 for result in test_results {
30 results.add(result);
31 }
32
33 results
34 }
35
36 /// Test 1: Can establish WebSocket connection
37 ///
38 /// Spec: NIP-01 basic requirement
39 /// Requirement: MUST serve a relay at / via WebSocket
40 async fn test_websocket_connection(client: &AuditClient) -> TestResult {
41 TestResult::new(
42 "websocket_connection",
43 "NIP-01:basic",
44 "Can establish WebSocket connection to /",
45 )
46 .run(|| async {
47 if !client.is_connected().await {
48 return Err("Failed to connect to relay".to_string());
49 }
50
51 Ok(())
52 })
53 .await
54 }
55
56 /// Test 2: Can send EVENT and receive OK response
57 ///
58 /// Spec: NIP-01 EVENT message
59 /// Requirement: Relay MUST accept valid EVENT messages
60 async fn test_send_receive_event(client: &AuditClient) -> TestResult {
61 TestResult::new(
62 "send_receive_event",
63 "NIP-01:event-message",
64 "Can send EVENT and receive OK response",
65 )
66 .run(|| async {
67 // Create audit event
68 let event = client
69 .event_builder(Kind::TextNote, "NIP-01 smoke test event")
70 .build(client.keys())
71 .await
72 .map_err(|e| format!("Failed to build event: {}", e))?;
73
74 // Send event
75 let event_id = client
76 .send_event(event.clone())
77 .await
78 .map_err(|e| format!("Failed to send event: {}", e))?;
79
80 // Verify we got an event ID back
81 if event_id != event.id {
82 return Err(format!(
83 "Event ID mismatch: sent {}, got {}",
84 event.id, event_id
85 ));
86 }
87
88 // Try to query it back
89 let filter = Filter::new()
90 .kind(Kind::TextNote)
91 .id(event_id);
92
93 let events = client
94 .query(filter)
95 .await
96 .map_err(|e| format!("Failed to query event: {}", e))?;
97
98 if events.is_empty() {
99 return Err("Event not found after sending".to_string());
100 }
101
102 if events[0].id != event_id {
103 return Err("Retrieved event has different ID".to_string());
104 }
105
106 Ok(())
107 })
108 .await
109 }
110
111 /// Test 3: Can create subscription with REQ
112 ///
113 /// Spec: NIP-01 REQ message
114 /// Requirement: Relay MUST support REQ subscriptions
115 async fn test_create_subscription(client: &AuditClient) -> TestResult {
116 TestResult::new(
117 "create_subscription",
118 "NIP-01:req-message",
119 "Can create subscription with REQ and receive EOSE",
120 )
121 .run(|| async {
122 // Create a test event first
123 let event = client
124 .event_builder(Kind::TextNote, "Subscription test event")
125 .build(client.keys())
126 .await
127 .map_err(|e| format!("Failed to build event: {}", e))?;
128
129 client
130 .send_event(event.clone())
131 .await
132 .map_err(|e| format!("Failed to send event: {}", e))?;
133
134 // Subscribe to events
135 let filter = Filter::new()
136 .kind(Kind::TextNote)
137 .author(client.public_key());
138
139 let events = client
140 .subscribe(vec![filter], Some(std::time::Duration::from_secs(5)))
141 .await
142 .map_err(|e| format!("Failed to subscribe: {}", e))?;
143
144 // Should have at least our event
145 if events.is_empty() {
146 return Err("No events received from subscription".to_string());
147 }
148
149 Ok(())
150 })
151 .await
152 }
153
154 /// Test 4: Can close subscription with CLOSE
155 ///
156 /// Spec: NIP-01 CLOSE message
157 /// Requirement: Relay MUST support CLOSE to end subscriptions
158 async fn test_close_subscription(client: &AuditClient) -> TestResult {
159 TestResult::new(
160 "close_subscription",
161 "NIP-01:close-message",
162 "Can close subscriptions",
163 )
164 .run(|| async {
165 // For now, we just verify we can query events
166 // Full subscription management with CLOSE would require
167 // lower-level WebSocket access
168
169 let filter = Filter::new()
170 .kind(Kind::TextNote)
171 .limit(1);
172
173 let _events = client
174 .subscribe(vec![filter], Some(std::time::Duration::from_secs(2)))
175 .await
176 .map_err(|e| format!("Failed to subscribe: {}", e))?;
177
178 // If we got here, subscription worked
179 Ok(())
180 })
181 .await
182 }
183
184 /// Test 5: Rejects events with invalid signatures
185 ///
186 /// Spec: NIP-01 event validation
187 /// Requirement: Relay MUST reject events with invalid signatures
188 async fn test_reject_invalid_signature(client: &AuditClient) -> TestResult {
189 TestResult::new(
190 "reject_invalid_signature",
191 "NIP-01:validation",
192 "Rejects events with invalid signatures",
193 )
194 .run(|| async {
195 // Create a valid event
196 let mut event = client
197 .event_builder(Kind::TextNote, "Invalid signature test")
198 .build(client.keys())
199 .await
200 .map_err(|e| format!("Failed to build event: {}", e))?;
201
202 // Corrupt the signature by creating a new event with wrong sig
203 // We'll use a different key to sign, creating an invalid signature
204 let wrong_keys = Keys::generate();
205 let wrong_event = EventBuilder::new(
206 event.kind,
207 event.content.clone(),
208 event.tags.clone(),
209 )
210 .to_event(&wrong_keys)
211 .await
212 .map_err(|e| format!("Failed to build wrong event: {}", e))?;
213
214 // Create event with mismatched pubkey and signature
215 // This should be rejected by the relay
216 event = Event {
217 id: event.id,
218 pubkey: event.pubkey,
219 created_at: event.created_at,
220 kind: event.kind,
221 tags: event.tags,
222 content: event.content,
223 sig: wrong_event.sig, // Wrong signature!
224 };
225
226 // Try to send the invalid event
227 let result = client.send_event(event).await;
228
229 // We expect this to fail
230 if result.is_ok() {
231 return Err("Relay accepted event with invalid signature".to_string());
232 }
233
234 Ok(())
235 })
236 .await
237 }
238
239 /// Test 6: Rejects events with invalid event IDs
240 ///
241 /// Spec: NIP-01 event ID validation
242 /// Requirement: Relay MUST reject events where ID doesn't match hash
243 async fn test_reject_invalid_event_id(client: &AuditClient) -> TestResult {
244 TestResult::new(
245 "reject_invalid_event_id",
246 "NIP-01:validation",
247 "Rejects events with invalid event IDs",
248 )
249 .run(|| async {
250 // Create a valid event
251 let mut event = client
252 .event_builder(Kind::TextNote, "Invalid ID test")
253 .build(client.keys())
254 .await
255 .map_err(|e| format!("Failed to build event: {}", e))?;
256
257 // Corrupt the ID
258 event = Event {
259 id: EventId::all_zeros(), // Wrong ID!
260 pubkey: event.pubkey,
261 created_at: event.created_at,
262 kind: event.kind,
263 tags: event.tags,
264 content: event.content,
265 sig: event.sig,
266 };
267
268 // Try to send the invalid event
269 let result = client.send_event(event).await;
270
271 // We expect this to fail
272 if result.is_ok() {
273 return Err("Relay accepted event with invalid ID".to_string());
274 }
275
276 Ok(())
277 })
278 .await
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use crate::AuditConfig;
286
287 // Note: These tests require a running relay
288 // They are integration tests, not unit tests
289
290 #[tokio::test]
291 #[ignore] // Ignore by default since it needs a running relay
292 async fn test_smoke_tests_against_relay() {
293 let config = AuditConfig::ci();
294 let client = AuditClient::new("ws://localhost:7000", config)
295 .await
296 .expect("Failed to connect to relay");
297
298 let results = Nip01SmokeTests::run_all(&client).await;
299 results.print_report();
300
301 assert!(results.all_passed(), "Some smoke tests failed");
302 }
303}