contextvm_sdk/relay/
mock.rs1use std::collections::HashMap;
12use std::sync::Arc;
13
14use async_trait::async_trait;
15use tokio::sync::Mutex;
16
17use nostr_sdk::prelude::*;
18
19use crate::core::error::{Error, Result};
20use crate::relay::RelayPoolTrait;
21
22struct MockRelayInner {
25 events: Vec<Event>,
26 subscriptions: HashMap<u32, Vec<Filter>>,
28 next_sub_id: u32,
29}
30
31impl MockRelayInner {
32 fn new() -> Self {
33 Self {
34 events: Vec::new(),
35 subscriptions: HashMap::new(),
36 next_sub_id: 0,
37 }
38 }
39}
40
41pub struct MockRelayPool {
48 inner: Arc<Mutex<MockRelayInner>>,
49 notification_tx: tokio::sync::broadcast::Sender<RelayPoolNotification>,
52 keys: Keys,
54}
55
56impl MockRelayPool {
57 pub fn new() -> Self {
59 let keys = Keys::generate();
60 let (tx, _rx) = tokio::sync::broadcast::channel(1024);
61 Self {
62 inner: Arc::new(Mutex::new(MockRelayInner::new())),
63 notification_tx: tx,
64 keys,
65 }
66 }
67
68 pub fn mock_public_key(&self) -> PublicKey {
70 self.keys.public_key()
71 }
72
73 pub fn mock_keys(&self) -> Keys {
75 self.keys.clone()
76 }
77
78 pub fn with_keys(keys: Keys) -> Self {
80 let (tx, _rx) = tokio::sync::broadcast::channel(1024);
81 Self {
82 inner: Arc::new(Mutex::new(MockRelayInner::new())),
83 notification_tx: tx,
84 keys,
85 }
86 }
87
88 pub fn create_pair() -> (Self, Self) {
93 let (tx, _rx) = tokio::sync::broadcast::channel(1024);
94 let inner = Arc::new(Mutex::new(MockRelayInner::new()));
95 let a = Self {
96 inner: Arc::clone(&inner),
97 notification_tx: tx.clone(),
98 keys: Keys::generate(),
99 };
100 let b = Self {
101 inner,
102 notification_tx: tx,
103 keys: Keys::generate(),
104 };
105 (a, b)
106 }
107
108 pub fn create_linked_group(n: usize) -> Vec<Self> {
114 assert!(n > 0, "group must have at least one pool");
115 let (tx, _rx) = tokio::sync::broadcast::channel(1024);
116 let inner = Arc::new(Mutex::new(MockRelayInner::new()));
117 (0..n)
118 .map(|_| Self {
119 inner: Arc::clone(&inner),
120 notification_tx: tx.clone(),
121 keys: Keys::generate(),
122 })
123 .collect()
124 }
125
126 pub async fn stored_events(&self) -> Vec<Event> {
128 self.inner.lock().await.events.clone()
129 }
130}
131
132impl Default for MockRelayPool {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138#[async_trait]
141impl RelayPoolTrait for MockRelayPool {
142 async fn connect(&self, _relay_urls: &[String]) -> Result<()> {
144 Ok(())
145 }
146
147 async fn disconnect(&self) -> Result<()> {
149 Ok(())
150 }
151
152 async fn publish_event(&self, event: &Event) -> Result<EventId> {
154 let event_id = event.id;
155
156 {
157 let mut inner = self.inner.lock().await;
158 inner.events.push(event.clone());
159 }
160
161 let notification = make_notification(event.clone());
164 let _ = self.notification_tx.send(notification);
166
167 Ok(event_id)
168 }
169
170 async fn publish(&self, builder: EventBuilder) -> Result<EventId> {
172 let event = sign_with_keys(builder, &self.keys)?;
173 let id = event.id;
174 self.publish_event(&event).await?;
175 Ok(id)
176 }
177
178 async fn sign(&self, builder: EventBuilder) -> Result<Event> {
180 sign_with_keys(builder, &self.keys)
181 }
182
183 async fn signer(&self) -> Result<Arc<dyn NostrSigner>> {
185 Ok(Arc::new(self.keys.clone()) as Arc<dyn NostrSigner>)
186 }
187
188 fn notifications(&self) -> tokio::sync::broadcast::Receiver<RelayPoolNotification> {
192 self.notification_tx.subscribe()
193 }
194
195 async fn public_key(&self) -> Result<PublicKey> {
197 Ok(self.keys.public_key())
198 }
199
200 async fn subscribe(&self, filters: Vec<Filter>) -> Result<()> {
204 let replay = {
205 let mut inner = self.inner.lock().await;
206 let sub_id = inner.next_sub_id;
207 inner.next_sub_id += 1;
208
209 inner.subscriptions.insert(sub_id, filters);
212
213 let events_snapshot = inner.events.clone();
215 let stored = inner.subscriptions.get(&sub_id).expect("just inserted");
216 events_snapshot
217 .into_iter()
218 .filter(|e| {
219 stored
220 .iter()
221 .any(|f| f.match_event(e, MatchEventOptions::default()))
222 })
223 .collect::<Vec<_>>()
224 };
225
226 for event in replay {
227 let _ = self.notification_tx.send(make_notification(event));
228 }
229
230 Ok(())
231 }
232}
233
234fn sign_with_keys(builder: EventBuilder, keys: &Keys) -> Result<Event> {
237 builder
238 .sign_with_keys(keys)
239 .map_err(|e| Error::Transport(e.to_string()))
240}
241
242fn make_notification(event: Event) -> RelayPoolNotification {
243 RelayPoolNotification::Event {
244 relay_url: RelayUrl::parse("wss://mock.relay").expect("hardcoded URL"),
245 subscription_id: SubscriptionId::generate(),
246 event: Box::new(event),
247 }
248}
249
250#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[tokio::test]
257 async fn connect_and_disconnect_are_noops() {
258 let pool = MockRelayPool::new();
259 assert!(pool.connect(&["wss://unused".to_string()]).await.is_ok());
260 assert!(pool.disconnect().await.is_ok());
261 }
262
263 #[tokio::test]
264 async fn publish_event_stores_and_broadcasts() {
265 let pool = MockRelayPool::new();
266 let mut rx = pool.notifications();
267
268 let keys = Keys::generate();
269 let event = EventBuilder::new(Kind::TextNote, "hello")
270 .sign_with_keys(&keys)
271 .unwrap();
272
273 pool.publish_event(&event).await.unwrap();
274
275 assert_eq!(pool.stored_events().await.len(), 1);
276 let notif = rx.try_recv().unwrap();
277 if let RelayPoolNotification::Event { event: e, .. } = notif {
278 assert_eq!(e.id, event.id);
279 } else {
280 panic!("expected Event notification");
281 }
282 }
283
284 #[tokio::test]
285 async fn publish_signs_and_stores() {
286 let pool = MockRelayPool::new();
287 let builder = EventBuilder::new(Kind::TextNote, "signed");
288 pool.publish(builder).await.unwrap();
289 let stored = pool.stored_events().await;
290 assert_eq!(stored.len(), 1);
291 assert_eq!(stored[0].pubkey, pool.mock_public_key());
292 }
293
294 #[tokio::test]
295 async fn sign_does_not_publish() {
296 let pool = MockRelayPool::new();
297 let builder = EventBuilder::new(Kind::TextNote, "unsigned");
298 let event = pool.sign(builder).await.unwrap();
299 assert_eq!(event.pubkey, pool.mock_public_key());
300 assert!(pool.stored_events().await.is_empty());
301 }
302
303 #[tokio::test]
304 async fn signer_uses_same_key_as_publish() {
305 let pool = MockRelayPool::new();
306 let signer = pool.signer().await.unwrap();
307 let expected_pubkey = pool.mock_public_key();
308 assert_eq!(signer.get_public_key().await.unwrap(), expected_pubkey);
309 }
310
311 #[tokio::test]
312 async fn subscribe_replays_matching_stored_events() {
313 let pool = MockRelayPool::new();
314 let mut rx = pool.notifications();
315
316 let keys = Keys::generate();
318 let e1 = EventBuilder::new(Kind::TextNote, "one")
319 .sign_with_keys(&keys)
320 .unwrap();
321 let e2 = EventBuilder::new(Kind::Custom(9999), "two")
322 .sign_with_keys(&keys)
323 .unwrap();
324 pool.publish_event(&e1).await.unwrap();
325 pool.publish_event(&e2).await.unwrap();
326
327 rx.try_recv().unwrap();
329 rx.try_recv().unwrap();
330
331 let filter = Filter::new().kind(Kind::TextNote);
333 pool.subscribe(vec![filter]).await.unwrap();
334
335 let replayed = rx.try_recv().unwrap();
336 if let RelayPoolNotification::Event { event, .. } = replayed {
337 assert_eq!(event.id, e1.id);
338 } else {
339 panic!("expected replayed Event notification");
340 }
341 assert!(rx.try_recv().is_err());
343 }
344
345 #[tokio::test]
346 async fn notifications_receives_future_publishes() {
347 let pool = MockRelayPool::new();
348 let mut rx = pool.notifications();
349
350 let keys = Keys::generate();
351 let event = EventBuilder::new(Kind::TextNote, "future")
352 .sign_with_keys(&keys)
353 .unwrap();
354 pool.publish_event(&event).await.unwrap();
355
356 let notif = rx.try_recv().unwrap();
357 assert!(matches!(notif, RelayPoolNotification::Event { .. }));
358 }
359}