upleb.uk

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

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/client.rs
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/client.rs
parentd428baf30feec295870fadda2d335d1e7f89507b (diff)
created POC grasp-auditor
Diffstat (limited to 'grasp-audit/src/client.rs')
-rw-r--r--grasp-audit/src/client.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs
new file mode 100644
index 0000000..934aef2
--- /dev/null
+++ b/grasp-audit/src/client.rs
@@ -0,0 +1,146 @@
1//! Audit client for testing GRASP implementations
2
3use crate::audit::{AuditConfig, AuditEventBuilder, AuditMode};
4use anyhow::{anyhow, Result};
5use nostr_sdk::prelude::*;
6use std::time::Duration;
7
8/// Client for auditing GRASP implementations
9pub struct AuditClient {
10 client: Client,
11 pub config: AuditConfig,
12 keys: Keys,
13}
14
15impl AuditClient {
16 /// Create a new audit client
17 pub async fn new(relay_url: &str, config: AuditConfig) -> Result<Self> {
18 let keys = Keys::generate();
19 let client = Client::new(&keys);
20
21 client.add_relay(relay_url).await?;
22 client.connect().await;
23
24 // Wait a bit for connection to establish
25 tokio::time::sleep(Duration::from_millis(500)).await;
26
27 Ok(Self {
28 client,
29 config,
30 keys,
31 })
32 }
33
34 /// Get the public key for this audit client
35 pub fn public_key(&self) -> PublicKey {
36 self.keys.public_key()
37 }
38
39 /// Check if connected to relay
40 pub async fn is_connected(&self) -> bool {
41 // Check if we have any connected relays
42 let relays = self.client.relays().await;
43 relays.values().any(|r| r.is_connected())
44 }
45
46 /// Send an event (with audit tags automatically added)
47 pub async fn send_event(&self, event: Event) -> Result<EventId> {
48 if self.config.read_only {
49 return Err(anyhow!("Client is in read-only mode"));
50 }
51
52 let event_id = self.client.send_event(event).await?;
53
54 // Wait a bit for event to propagate
55 tokio::time::sleep(Duration::from_millis(100)).await;
56
57 Ok(event_id)
58 }
59
60 /// Create an event builder with audit tags
61 pub fn event_builder(&self, kind: Kind, content: impl Into<String>) -> AuditEventBuilder {
62 AuditEventBuilder::new(kind, content, self.config.clone())
63 }
64
65 /// Query events, optionally filtered to this audit run
66 pub async fn query(&self, mut filter: Filter) -> Result<Vec<Event>> {
67 if self.config.mode == AuditMode::CI {
68 // In CI mode, only see our own audit events
69 filter = filter
70 .custom_tag(
71 SingleLetterTag::lowercase(Alphabet::G),
72 ["true"] // grasp-audit tag
73 )
74 .custom_tag(
75 SingleLetterTag::lowercase(Alphabet::R),
76 [&self.config.run_id] // audit-run-id tag
77 );
78 }
79 // In Production mode, see all events (no filter modification)
80
81 let events = self.client
82 .get_events_of(vec![filter], Some(Duration::from_secs(5)))
83 .await?;
84
85 Ok(events)
86 }
87
88 /// Subscribe to events with a callback
89 pub async fn subscribe(
90 &self,
91 filters: Vec<Filter>,
92 timeout: Option<Duration>,
93 ) -> Result<Vec<Event>> {
94 let events = self.client
95 .get_events_of(filters, timeout)
96 .await?;
97
98 Ok(events)
99 }
100
101 /// Get the underlying nostr client (for advanced usage)
102 pub fn client(&self) -> &Client {
103 &self.client
104 }
105
106 /// Get the keys (for signing custom events)
107 pub fn keys(&self) -> &Keys {
108 &self.keys
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[tokio::test]
117 async fn test_client_creation() {
118 let config = AuditConfig::ci();
119
120 // This will fail if no relay is running, which is expected in tests
121 // In real usage, there should be a relay at the URL
122 let result = AuditClient::new("ws://localhost:7000", config).await;
123
124 // We can't test connection without a running relay
125 // But we can test that the client is created
126 if let Ok(client) = result {
127 assert_eq!(client.config.mode, AuditMode::CI);
128 }
129 }
130
131 #[test]
132 fn test_event_builder() {
133 let config = AuditConfig::ci();
134 let keys = Keys::generate();
135 let client = AuditClient {
136 client: Client::new(&keys),
137 config: config.clone(),
138 keys: keys.clone(),
139 };
140
141 let builder = client.event_builder(Kind::TextNote, "test content");
142
143 // Builder should have the config
144 assert_eq!(builder.config.run_id, config.run_id);
145 }
146}