1use crate::models::UpdateRelayerRequest;
4use crate::models::{PaginationQuery, RelayerNetworkPolicy, RelayerRepoModel, RepositoryError};
5use crate::repositories::redis_base::RedisRepository;
6use crate::repositories::{BatchRetrievalResult, PaginatedResult, RelayerRepository, Repository};
7use async_trait::async_trait;
8use log::{debug, error, warn};
9use redis::aio::ConnectionManager;
10use redis::AsyncCommands;
11use std::fmt;
12use std::sync::Arc;
13
14const RELAYER_PREFIX: &str = "relayer";
15const RELAYER_LIST_KEY: &str = "relayer_list";
16
17#[derive(Clone)]
18pub struct RedisRelayerRepository {
19 pub client: Arc<ConnectionManager>,
20 pub key_prefix: String,
21}
22
23impl RedisRepository for RedisRelayerRepository {}
24
25impl RedisRelayerRepository {
26 pub fn new(
27 connection_manager: Arc<ConnectionManager>,
28 key_prefix: String,
29 ) -> Result<Self, RepositoryError> {
30 if key_prefix.is_empty() {
31 return Err(RepositoryError::InvalidData(
32 "Redis key prefix cannot be empty".to_string(),
33 ));
34 }
35
36 Ok(Self {
37 client: connection_manager,
38 key_prefix,
39 })
40 }
41
42 fn relayer_key(&self, relayer_id: &str) -> String {
44 format!("{}:{}:{}", self.key_prefix, RELAYER_PREFIX, relayer_id)
45 }
46
47 fn relayer_list_key(&self) -> String {
49 format!("{}:{}", self.key_prefix, RELAYER_LIST_KEY)
50 }
51
52 async fn get_relayers_by_ids(
54 &self,
55 ids: &[String],
56 ) -> Result<BatchRetrievalResult<RelayerRepoModel>, RepositoryError> {
57 if ids.is_empty() {
58 debug!("No relayer IDs provided for batch fetch");
59 return Ok(BatchRetrievalResult {
60 results: vec![],
61 failed_ids: vec![],
62 });
63 }
64
65 let mut conn = self.client.as_ref().clone();
66 let keys: Vec<String> = ids.iter().map(|id| self.relayer_key(id)).collect();
67
68 debug!("Batch fetching {} relayer data", keys.len());
69
70 let values: Vec<Option<String>> = conn
71 .mget(&keys)
72 .await
73 .map_err(|e| self.map_redis_error(e, "batch_fetch_relayers"))?;
74
75 let mut relayers = Vec::new();
76 let mut failed_count = 0;
77 let mut failed_ids = Vec::new();
78 for (i, value) in values.into_iter().enumerate() {
79 match value {
80 Some(json) => {
81 match self.deserialize_entity(&json, &ids[i], "relayer") {
82 Ok(relayer) => relayers.push(relayer),
83 Err(e) => {
84 failed_count += 1;
85 error!("Failed to deserialize relayer {}: {}", ids[i], e);
86 failed_ids.push(ids[i].clone());
87 }
89 }
90 }
91 None => {
92 warn!("Relayer {} not found in batch fetch", ids[i]);
93 }
94 }
95 }
96
97 if failed_count > 0 {
98 warn!(
99 "Failed to deserialize {} out of {} relayers in batch",
100 failed_count,
101 ids.len()
102 );
103 }
104
105 debug!("Successfully fetched {} relayers", relayers.len());
106 Ok(BatchRetrievalResult {
107 results: relayers,
108 failed_ids,
109 })
110 }
111}
112
113impl fmt::Debug for RedisRelayerRepository {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.debug_struct("RedisRelayerRepository")
116 .field("client", &"<ConnectionManager>")
117 .field("key_prefix", &self.key_prefix)
118 .finish()
119 }
120}
121
122#[async_trait]
123impl Repository<RelayerRepoModel, String> for RedisRelayerRepository {
124 async fn create(&self, entity: RelayerRepoModel) -> Result<RelayerRepoModel, RepositoryError> {
125 if entity.id.is_empty() {
126 return Err(RepositoryError::InvalidData(
127 "Relayer ID cannot be empty".to_string(),
128 ));
129 }
130
131 if entity.name.is_empty() {
132 return Err(RepositoryError::InvalidData(
133 "Relayer name cannot be empty".to_string(),
134 ));
135 }
136
137 let mut conn = self.client.as_ref().clone();
138 let relayer_key = self.relayer_key(&entity.id);
139
140 let exists: bool = conn
142 .exists(&relayer_key)
143 .await
144 .map_err(|e| self.map_redis_error(e, "create_relayer_exists_check"))?;
145
146 if exists {
147 return Err(RepositoryError::ConstraintViolation(format!(
148 "Relayer with ID {} already exists",
149 entity.id
150 )));
151 }
152
153 let serialized = self.serialize_entity(&entity, |r| &r.id, "relayer")?;
154
155 let mut pipe = redis::pipe();
157 pipe.atomic();
158 pipe.set(&relayer_key, &serialized);
159 pipe.sadd(self.relayer_list_key(), &entity.id);
160
161 pipe.exec_async(&mut conn)
162 .await
163 .map_err(|e| self.map_redis_error(e, "create_relayer_pipeline"))?;
164
165 debug!("Created relayer {}", entity.id);
166 Ok(entity)
167 }
168
169 async fn get_by_id(&self, id: String) -> Result<RelayerRepoModel, RepositoryError> {
170 if id.is_empty() {
171 return Err(RepositoryError::InvalidData(
172 "Relayer ID cannot be empty".to_string(),
173 ));
174 }
175
176 let mut conn = self.client.as_ref().clone();
177 let relayer_key = self.relayer_key(&id);
178
179 debug!("Fetching relayer {}", id);
180
181 let json: Option<String> = conn
182 .get(&relayer_key)
183 .await
184 .map_err(|e| self.map_redis_error(e, "get_relayer_by_id"))?;
185
186 match json {
187 Some(json) => {
188 debug!("Found relayer {}", id);
189 self.deserialize_entity(&json, &id, "relayer")
190 }
191 None => {
192 debug!("Relayer {} not found", id);
193 Err(RepositoryError::NotFound(format!(
194 "Relayer with ID {} not found",
195 id
196 )))
197 }
198 }
199 }
200
201 async fn list_all(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
202 let mut conn = self.client.as_ref().clone();
203 let relayer_list_key = self.relayer_list_key();
204
205 debug!("Listing all relayers");
206
207 let relayer_ids: Vec<String> = conn
208 .smembers(&relayer_list_key)
209 .await
210 .map_err(|e| self.map_redis_error(e, "list_all_relayers"))?;
211
212 debug!("Found {} relayers in index", relayer_ids.len());
213
214 let relayers = self.get_relayers_by_ids(&relayer_ids).await?;
215 Ok(relayers.results)
216 }
217
218 async fn list_paginated(
219 &self,
220 query: PaginationQuery,
221 ) -> Result<PaginatedResult<RelayerRepoModel>, RepositoryError> {
222 if query.page == 0 {
223 return Err(RepositoryError::InvalidData(
224 "Page number must be greater than 0".to_string(),
225 ));
226 }
227
228 if query.per_page == 0 {
229 return Err(RepositoryError::InvalidData(
230 "Per page count must be greater than 0".to_string(),
231 ));
232 }
233
234 let mut conn = self.client.as_ref().clone();
235 let relayer_list_key = self.relayer_list_key();
236
237 let total: u64 = conn
239 .scard(&relayer_list_key)
240 .await
241 .map_err(|e| self.map_redis_error(e, "list_paginated_count"))?;
242
243 if total == 0 {
244 return Ok(PaginatedResult {
245 items: vec![],
246 total: 0,
247 page: query.page,
248 per_page: query.per_page,
249 });
250 }
251
252 let all_ids: Vec<String> = conn
254 .smembers(&relayer_list_key)
255 .await
256 .map_err(|e| self.map_redis_error(e, "list_paginated_members"))?;
257
258 let start = ((query.page - 1) * query.per_page) as usize;
259 let end = (start + query.per_page as usize).min(all_ids.len());
260
261 let page_ids = &all_ids[start..end];
262 let items = self.get_relayers_by_ids(page_ids).await?;
263
264 Ok(PaginatedResult {
265 items: items.results.clone(),
266 total,
267 page: query.page,
268 per_page: query.per_page,
269 })
270 }
271
272 async fn update(
273 &self,
274 id: String,
275 entity: RelayerRepoModel,
276 ) -> Result<RelayerRepoModel, RepositoryError> {
277 if id.is_empty() {
278 return Err(RepositoryError::InvalidData(
279 "Relayer ID cannot be empty".to_string(),
280 ));
281 }
282
283 if entity.name.is_empty() {
284 return Err(RepositoryError::InvalidData(
285 "Relayer name cannot be empty".to_string(),
286 ));
287 }
288
289 let mut conn = self.client.as_ref().clone();
290 let relayer_key = self.relayer_key(&id);
291
292 let exists: bool = conn
294 .exists(&relayer_key)
295 .await
296 .map_err(|e| self.map_redis_error(e, "update_relayer_exists_check"))?;
297
298 if !exists {
299 return Err(RepositoryError::NotFound(format!(
300 "Relayer with ID {} not found",
301 id
302 )));
303 }
304
305 let mut updated_entity = entity;
307 updated_entity.id = id.clone();
308
309 let serialized = self.serialize_entity(&updated_entity, |r| &r.id, "relayer")?;
310
311 let mut pipe = redis::pipe();
313 pipe.atomic();
314 pipe.set(&relayer_key, &serialized);
315 pipe.sadd(self.relayer_list_key(), &id);
316
317 pipe.exec_async(&mut conn)
318 .await
319 .map_err(|e| self.map_redis_error(e, "update_relayer_pipeline"))?;
320
321 debug!("Updated relayer {}", id);
322 Ok(updated_entity)
323 }
324
325 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
326 if id.is_empty() {
327 return Err(RepositoryError::InvalidData(
328 "Relayer ID cannot be empty".to_string(),
329 ));
330 }
331
332 let mut conn = self.client.as_ref().clone();
333 let relayer_key = self.relayer_key(&id);
334
335 let exists: bool = conn
337 .exists(&relayer_key)
338 .await
339 .map_err(|e| self.map_redis_error(e, "delete_relayer_exists_check"))?;
340
341 if !exists {
342 return Err(RepositoryError::NotFound(format!(
343 "Relayer with ID {} not found",
344 id
345 )));
346 }
347
348 let mut pipe = redis::pipe();
350 pipe.atomic();
351 pipe.del(&relayer_key);
352 pipe.srem(self.relayer_list_key(), &id);
353
354 pipe.exec_async(&mut conn)
355 .await
356 .map_err(|e| self.map_redis_error(e, "delete_relayer_pipeline"))?;
357
358 debug!("Deleted relayer {}", id);
359 Ok(())
360 }
361
362 async fn count(&self) -> Result<usize, RepositoryError> {
363 let mut conn = self.client.as_ref().clone();
364 let relayer_list_key = self.relayer_list_key();
365
366 let count: u64 = conn
367 .scard(&relayer_list_key)
368 .await
369 .map_err(|e| self.map_redis_error(e, "count_relayers"))?;
370
371 Ok(count as usize)
372 }
373
374 async fn has_entries(&self) -> Result<bool, RepositoryError> {
375 let mut conn = self.client.as_ref().clone();
376 let relayer_list_key = self.relayer_list_key();
377
378 debug!("Checking if relayer entries exist");
379
380 let exists: bool = conn
381 .exists(&relayer_list_key)
382 .await
383 .map_err(|e| self.map_redis_error(e, "has_entries_check"))?;
384
385 debug!("Relayer entries exist: {}", exists);
386 Ok(exists)
387 }
388
389 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
390 let mut conn = self.client.as_ref().clone();
391 let relayer_list_key = self.relayer_list_key();
392
393 debug!("Dropping all relayer entries");
394
395 let relayer_ids: Vec<String> = conn
397 .smembers(&relayer_list_key)
398 .await
399 .map_err(|e| self.map_redis_error(e, "drop_all_entries_get_ids"))?;
400
401 if relayer_ids.is_empty() {
402 debug!("No relayer entries to drop");
403 return Ok(());
404 }
405
406 let mut pipe = redis::pipe();
408 pipe.atomic();
409
410 for relayer_id in &relayer_ids {
412 let relayer_key = self.relayer_key(relayer_id);
413 pipe.del(&relayer_key);
414 }
415
416 pipe.del(&relayer_list_key);
418
419 pipe.exec_async(&mut conn)
420 .await
421 .map_err(|e| self.map_redis_error(e, "drop_all_entries_pipeline"))?;
422
423 debug!("Dropped {} relayer entries", relayer_ids.len());
424 Ok(())
425 }
426}
427
428#[async_trait]
429impl RelayerRepository for RedisRelayerRepository {
430 async fn list_active(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
431 let all_relayers = self.list_all().await?;
432 let active_relayers: Vec<RelayerRepoModel> = all_relayers
433 .into_iter()
434 .filter(|relayer| !relayer.paused)
435 .collect();
436
437 debug!("Found {} active relayers", active_relayers.len());
438 Ok(active_relayers)
439 }
440
441 async fn list_by_signer_id(
442 &self,
443 signer_id: &str,
444 ) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
445 let all_relayers = self.list_all().await?;
446 let relayers_with_signer: Vec<RelayerRepoModel> = all_relayers
447 .into_iter()
448 .filter(|relayer| relayer.signer_id == signer_id)
449 .collect();
450
451 debug!(
452 "Found {} relayers using signer '{}'",
453 relayers_with_signer.len(),
454 signer_id
455 );
456 Ok(relayers_with_signer)
457 }
458
459 async fn list_by_notification_id(
460 &self,
461 notification_id: &str,
462 ) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
463 let all_relayers = self.list_all().await?;
464 let relayers_with_notification: Vec<RelayerRepoModel> = all_relayers
465 .into_iter()
466 .filter(|relayer| {
467 relayer
468 .notification_id
469 .as_ref()
470 .is_some_and(|id| id == notification_id)
471 })
472 .collect();
473
474 debug!(
475 "Found {} relayers using notification '{}'",
476 relayers_with_notification.len(),
477 notification_id
478 );
479 Ok(relayers_with_notification)
480 }
481
482 async fn partial_update(
483 &self,
484 id: String,
485 update: UpdateRelayerRequest,
486 ) -> Result<RelayerRepoModel, RepositoryError> {
487 let mut relayer = self.get_by_id(id.clone()).await?;
489
490 if let Some(paused) = update.paused {
492 relayer.paused = paused;
493 }
494
495 self.update(id, relayer).await
497 }
498
499 async fn enable_relayer(
500 &self,
501 relayer_id: String,
502 ) -> Result<RelayerRepoModel, RepositoryError> {
503 let mut relayer = self.get_by_id(relayer_id.clone()).await?;
505
506 relayer.system_disabled = false;
508
509 self.update(relayer_id, relayer).await
511 }
512
513 async fn disable_relayer(
514 &self,
515 relayer_id: String,
516 ) -> Result<RelayerRepoModel, RepositoryError> {
517 let mut relayer = self.get_by_id(relayer_id.clone()).await?;
519
520 relayer.system_disabled = true;
522
523 self.update(relayer_id, relayer).await
525 }
526
527 async fn update_policy(
528 &self,
529 id: String,
530 policy: RelayerNetworkPolicy,
531 ) -> Result<RelayerRepoModel, RepositoryError> {
532 let mut relayer = self.get_by_id(id.clone()).await?;
534
535 relayer.policies = policy;
537
538 self.update(id, relayer).await
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use crate::models::{NetworkType, RelayerEvmPolicy, RelayerNetworkPolicy};
547 use redis::aio::ConnectionManager;
548 use std::sync::Arc;
549
550 fn create_test_relayer(id: &str) -> RelayerRepoModel {
551 RelayerRepoModel {
552 id: id.to_string(),
553 name: format!("Test Relayer {}", id),
554 network: "ethereum".to_string(),
555 paused: false,
556 network_type: NetworkType::Evm,
557 signer_id: "test-signer".to_string(),
558 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
559 address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
560 notification_id: None,
561 system_disabled: false,
562 custom_rpc_urls: None,
563 }
564 }
565
566 fn create_test_relayer_with_pause(id: &str, paused: bool) -> RelayerRepoModel {
567 let mut relayer = create_test_relayer(id);
568 relayer.paused = paused;
569 relayer
570 }
571
572 async fn setup_test_repo() -> RedisRelayerRepository {
573 let redis_url =
574 std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379/".to_string());
575 let client = redis::Client::open(redis_url).expect("Failed to create Redis client");
576 let connection_manager = ConnectionManager::new(client)
577 .await
578 .expect("Failed to create Redis connection manager");
579
580 RedisRelayerRepository::new(Arc::new(connection_manager), "test".to_string())
581 .expect("Failed to create Redis relayer repository")
582 }
583
584 #[ignore = "Requires active Redis instance"]
585 #[tokio::test]
586 async fn test_new_repository_creation() {
587 let repo = setup_test_repo().await;
588 assert_eq!(repo.key_prefix, "test");
589 }
590
591 #[ignore = "Requires active Redis instance"]
592 #[tokio::test]
593 async fn test_new_repository_empty_prefix_fails() {
594 let redis_url =
595 std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379/".to_string());
596 let client = redis::Client::open(redis_url).expect("Failed to create Redis client");
597 let connection_manager = ConnectionManager::new(client)
598 .await
599 .expect("Failed to create Redis connection manager");
600
601 let result = RedisRelayerRepository::new(Arc::new(connection_manager), "".to_string());
602 assert!(matches!(result, Err(RepositoryError::InvalidData(_))));
603 }
604
605 #[ignore = "Requires active Redis instance"]
606 #[tokio::test]
607 async fn test_key_generation() {
608 let repo = setup_test_repo().await;
609
610 let relayer_key = repo.relayer_key("test-relayer");
611 assert_eq!(relayer_key, "test:relayer:test-relayer");
612
613 let list_key = repo.relayer_list_key();
614 assert_eq!(list_key, "test:relayer_list");
615 }
616
617 #[ignore = "Requires active Redis instance"]
618 #[tokio::test]
619 async fn test_serialize_deserialize_relayer() {
620 let repo = setup_test_repo().await;
621 let relayer = create_test_relayer("test-relayer");
622
623 let serialized = repo
624 .serialize_entity(&relayer, |r| &r.id, "relayer")
625 .unwrap();
626 let deserialized: RelayerRepoModel = repo
627 .deserialize_entity(&serialized, &relayer.id, "relayer")
628 .unwrap();
629
630 assert_eq!(relayer.id, deserialized.id);
631 assert_eq!(relayer.name, deserialized.name);
632 assert_eq!(relayer.network, deserialized.network);
633 assert_eq!(relayer.paused, deserialized.paused);
634 assert_eq!(relayer.network_type, deserialized.network_type);
635 assert_eq!(relayer.signer_id, deserialized.signer_id);
636 assert_eq!(relayer.address, deserialized.address);
637 assert_eq!(relayer.notification_id, deserialized.notification_id);
638 assert_eq!(relayer.system_disabled, deserialized.system_disabled);
639 assert_eq!(relayer.custom_rpc_urls, deserialized.custom_rpc_urls);
640 }
641
642 #[ignore = "Requires active Redis instance"]
643 #[tokio::test]
644 async fn test_create_relayer() {
645 let repo = setup_test_repo().await;
646 let relayer_id = uuid::Uuid::new_v4().to_string();
647 let relayer = create_test_relayer(&relayer_id);
648
649 let result = repo.create(relayer.clone()).await;
650 assert!(result.is_ok());
651
652 let created_relayer = result.unwrap();
653 assert_eq!(created_relayer.id, relayer_id);
654 assert_eq!(created_relayer.name, relayer.name);
655 }
656
657 #[ignore = "Requires active Redis instance"]
658 #[tokio::test]
659 async fn test_get_relayer() {
660 let repo = setup_test_repo().await;
661 let relayer_id = uuid::Uuid::new_v4().to_string();
662 let relayer = create_test_relayer(&relayer_id);
663
664 repo.create(relayer.clone()).await.unwrap();
665
666 let retrieved = repo.get_by_id(relayer_id).await.unwrap();
667 assert_eq!(retrieved.id, relayer.id);
668 assert_eq!(retrieved.name, relayer.name);
669 }
670
671 #[ignore = "Requires active Redis instance"]
672 #[tokio::test]
673 async fn test_list_all_relayers() {
674 let repo = setup_test_repo().await;
675 let relayer1_id = uuid::Uuid::new_v4().to_string();
676 let relayer2_id = uuid::Uuid::new_v4().to_string();
677 let relayer1 = create_test_relayer(&relayer1_id);
678 let relayer2 = create_test_relayer(&relayer2_id);
679
680 repo.create(relayer1).await.unwrap();
681 repo.create(relayer2).await.unwrap();
682
683 let all_relayers = repo.list_all().await.unwrap();
684 assert!(all_relayers.len() >= 2);
685 }
686
687 #[ignore = "Requires active Redis instance"]
688 #[tokio::test]
689 async fn test_list_active_relayers() {
690 let repo = setup_test_repo().await;
691 let relayer1_id = uuid::Uuid::new_v4().to_string();
692 let relayer2_id = uuid::Uuid::new_v4().to_string();
693 let relayer1 = create_test_relayer_with_pause(&relayer1_id, false);
694 let relayer2 = create_test_relayer_with_pause(&relayer2_id, true);
695
696 repo.create(relayer1).await.unwrap();
697 repo.create(relayer2).await.unwrap();
698
699 let active_relayers = repo.list_active().await.unwrap();
700 assert!(!active_relayers.is_empty());
702 assert!(active_relayers.iter().all(|r| !r.paused));
704 }
705
706 #[ignore = "Requires active Redis instance"]
707 #[tokio::test]
708 async fn test_count_relayers() {
709 let repo = setup_test_repo().await;
710 let relayer_id = uuid::Uuid::new_v4().to_string();
711 let relayer = create_test_relayer(&relayer_id);
712
713 repo.create(relayer).await.unwrap();
714
715 let count = repo.count().await.unwrap();
716 assert!(count >= 1);
717 }
718
719 #[ignore = "Requires active Redis instance"]
720 #[tokio::test]
721 async fn test_get_nonexistent_relayer() {
722 let repo = setup_test_repo().await;
723
724 let result = repo.get_by_id("nonexistent-relayer".to_string()).await;
725 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
726 }
727
728 #[ignore = "Requires active Redis instance"]
729 #[tokio::test]
730 async fn test_duplicate_relayer_creation() {
731 let repo = setup_test_repo().await;
732 let relayer_id = uuid::Uuid::new_v4().to_string();
733 let relayer = create_test_relayer(&relayer_id);
734
735 repo.create(relayer.clone()).await.unwrap();
736
737 let duplicate_result = repo.create(relayer).await;
738 assert!(matches!(
739 duplicate_result,
740 Err(RepositoryError::ConstraintViolation(_))
741 ));
742 }
743
744 #[ignore = "Requires active Redis instance"]
745 #[tokio::test]
746 async fn test_update_relayer() {
747 let repo = setup_test_repo().await;
748 let relayer_id = uuid::Uuid::new_v4().to_string();
749 let relayer = create_test_relayer(&relayer_id);
750
751 repo.create(relayer.clone()).await.unwrap();
752
753 let mut updated_relayer = relayer.clone();
754 updated_relayer.name = "Updated Relayer Name".to_string();
755
756 let result = repo.update(relayer.id.clone(), updated_relayer).await;
757 assert!(result.is_ok());
758
759 let updated = result.unwrap();
760 assert_eq!(updated.name, "Updated Relayer Name");
761 assert_eq!(updated.id, relayer.id);
762 }
763
764 #[ignore = "Requires active Redis instance"]
765 #[tokio::test]
766 async fn test_delete_relayer() {
767 let repo = setup_test_repo().await;
768 let relayer_id = uuid::Uuid::new_v4().to_string();
769 let relayer = create_test_relayer(&relayer_id);
770
771 repo.create(relayer.clone()).await.unwrap();
772
773 let delete_result = repo.delete_by_id(relayer.id.clone()).await;
774 assert!(delete_result.is_ok());
775
776 let get_result = repo.get_by_id(relayer.id).await;
777 assert!(matches!(get_result, Err(RepositoryError::NotFound(_))));
778 }
779
780 #[ignore = "Requires active Redis instance"]
781 #[tokio::test]
782 async fn test_list_paginated() {
783 let repo = setup_test_repo().await;
784 let relayer1_id = uuid::Uuid::new_v4().to_string();
785 let relayer2_id = uuid::Uuid::new_v4().to_string();
786 let relayer1 = create_test_relayer(&relayer1_id);
787 let relayer2 = create_test_relayer(&relayer2_id);
788
789 repo.create(relayer1).await.unwrap();
790 repo.create(relayer2).await.unwrap();
791
792 let query = PaginationQuery {
793 page: 1,
794 per_page: 10,
795 };
796
797 let result = repo.list_paginated(query).await.unwrap();
798 assert!(result.total >= 2);
799 assert_eq!(result.page, 1);
800 assert_eq!(result.per_page, 10);
801 }
802
803 #[ignore = "Requires active Redis instance"]
804 #[tokio::test]
805 async fn test_partial_update_relayer() {
806 let repo = setup_test_repo().await;
807 let relayer_id = uuid::Uuid::new_v4().to_string();
808 let relayer = create_test_relayer(&relayer_id);
809
810 repo.create(relayer.clone()).await.unwrap();
811
812 let update = UpdateRelayerRequest {
813 paused: Some(true),
814 ..Default::default()
815 };
816 let result = repo.partial_update(relayer.id.clone(), update).await;
817 assert!(result.is_ok());
818
819 let updated = result.unwrap();
820 assert_eq!(updated.id, relayer.id);
821 assert!(updated.paused);
822 }
823
824 #[ignore = "Requires active Redis instance"]
825 #[tokio::test]
826 async fn test_enable_disable_relayer() {
827 let repo = setup_test_repo().await;
828 let relayer_id = uuid::Uuid::new_v4().to_string();
829 let relayer = create_test_relayer(&relayer_id);
830
831 repo.create(relayer.clone()).await.unwrap();
832
833 let disabled = repo.disable_relayer(relayer.id.clone()).await.unwrap();
835 assert!(disabled.system_disabled);
836
837 let enabled = repo.enable_relayer(relayer.id.clone()).await.unwrap();
839 assert!(!enabled.system_disabled);
840 }
841
842 #[ignore = "Requires active Redis instance"]
843 #[tokio::test]
844 async fn test_update_policy() {
845 let repo = setup_test_repo().await;
846 let relayer_id = uuid::Uuid::new_v4().to_string();
847 let relayer = create_test_relayer(&relayer_id);
848
849 repo.create(relayer.clone()).await.unwrap();
850
851 let new_policy = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
852 gas_price_cap: Some(50_000_000_000),
853 whitelist_receivers: Some(vec!["0x123".to_string()]),
854 eip1559_pricing: Some(true),
855 private_transactions: Some(true),
856 min_balance: Some(1000000000000000000),
857 gas_limit_estimation: Some(true),
858 });
859
860 let result = repo.update_policy(relayer.id.clone(), new_policy).await;
861 assert!(result.is_ok());
862
863 let updated = result.unwrap();
864 if let RelayerNetworkPolicy::Evm(evm_policy) = updated.policies {
865 assert_eq!(evm_policy.gas_price_cap, Some(50_000_000_000));
866 assert_eq!(
867 evm_policy.whitelist_receivers,
868 Some(vec!["0x123".to_string()])
869 );
870 assert_eq!(evm_policy.eip1559_pricing, Some(true));
871 assert!(evm_policy.private_transactions.unwrap_or(false));
872 assert_eq!(evm_policy.min_balance, Some(1000000000000000000));
873 } else {
874 panic!("Expected EVM policy");
875 }
876 }
877
878 #[ignore = "Requires active Redis instance"]
879 #[tokio::test]
880 async fn test_debug_implementation() {
881 let repo = setup_test_repo().await;
882 let debug_str = format!("{:?}", repo);
883 assert!(debug_str.contains("RedisRelayerRepository"));
884 assert!(debug_str.contains("key_prefix"));
885 }
886
887 #[ignore = "Requires active Redis instance"]
888 #[tokio::test]
889 async fn test_error_handling_empty_id() {
890 let repo = setup_test_repo().await;
891
892 let create_result = repo
893 .create(RelayerRepoModel {
894 id: "".to_string(),
895 ..create_test_relayer("test")
896 })
897 .await;
898 assert!(matches!(
899 create_result,
900 Err(RepositoryError::InvalidData(_))
901 ));
902
903 let get_result = repo.get_by_id("".to_string()).await;
904 assert!(matches!(get_result, Err(RepositoryError::InvalidData(_))));
905
906 let update_result = repo
907 .update("".to_string(), create_test_relayer("test"))
908 .await;
909 assert!(matches!(
910 update_result,
911 Err(RepositoryError::InvalidData(_))
912 ));
913
914 let delete_result = repo.delete_by_id("".to_string()).await;
915 assert!(matches!(
916 delete_result,
917 Err(RepositoryError::InvalidData(_))
918 ));
919 }
920
921 #[ignore = "Requires active Redis instance"]
922 #[tokio::test]
923 async fn test_error_handling_empty_name() {
924 let repo = setup_test_repo().await;
925
926 let create_result = repo
927 .create(RelayerRepoModel {
928 name: "".to_string(),
929 ..create_test_relayer("test")
930 })
931 .await;
932 assert!(matches!(
933 create_result,
934 Err(RepositoryError::InvalidData(_))
935 ));
936 }
937
938 #[ignore = "Requires active Redis instance"]
939 #[tokio::test]
940 async fn test_pagination_validation() {
941 let repo = setup_test_repo().await;
942
943 let invalid_page = PaginationQuery {
944 page: 0,
945 per_page: 10,
946 };
947 let result = repo.list_paginated(invalid_page).await;
948 assert!(matches!(result, Err(RepositoryError::InvalidData(_))));
949
950 let invalid_per_page = PaginationQuery {
951 page: 1,
952 per_page: 0,
953 };
954 let result = repo.list_paginated(invalid_per_page).await;
955 assert!(matches!(result, Err(RepositoryError::InvalidData(_))));
956 }
957
958 #[ignore = "Requires active Redis instance"]
959 #[tokio::test]
960 async fn test_update_nonexistent_relayer() {
961 let repo = setup_test_repo().await;
962 let relayer = create_test_relayer("nonexistent-relayer");
963
964 let result = repo
965 .update("nonexistent-relayer".to_string(), relayer)
966 .await;
967 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
968 }
969
970 #[ignore = "Requires active Redis instance"]
971 #[tokio::test]
972 async fn test_delete_nonexistent_relayer() {
973 let repo = setup_test_repo().await;
974
975 let result = repo.delete_by_id("nonexistent-relayer".to_string()).await;
976 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
977 }
978
979 #[tokio::test]
980 #[ignore = "Requires active Redis instance"]
981 async fn test_has_entries() {
982 let repo = setup_test_repo().await;
983 assert!(!repo.has_entries().await.unwrap());
984
985 let relayer_id = uuid::Uuid::new_v4().to_string();
986 let relayer = create_test_relayer(&relayer_id);
987 repo.create(relayer.clone()).await.unwrap();
988 assert!(repo.has_entries().await.unwrap());
989 }
990
991 #[tokio::test]
992 #[ignore = "Requires active Redis instance"]
993 async fn test_drop_all_entries() {
994 let repo = setup_test_repo().await;
995 let relayer_id = uuid::Uuid::new_v4().to_string();
996 let relayer = create_test_relayer(&relayer_id);
997 repo.create(relayer.clone()).await.unwrap();
998 assert!(repo.has_entries().await.unwrap());
999
1000 repo.drop_all_entries().await.unwrap();
1001 assert!(!repo.has_entries().await.unwrap());
1002 }
1003
1004 #[ignore = "Requires active Redis instance"]
1005 #[tokio::test]
1006 async fn test_list_by_signer_id() {
1007 let repo = setup_test_repo().await;
1008
1009 let relayer1_id = uuid::Uuid::new_v4().to_string();
1010 let relayer2_id = uuid::Uuid::new_v4().to_string();
1011 let relayer3_id = uuid::Uuid::new_v4().to_string();
1012 let signer1_id = uuid::Uuid::new_v4().to_string();
1013 let signer2_id = uuid::Uuid::new_v4().to_string();
1014
1015 let mut relayer1 = create_test_relayer(&relayer1_id);
1016 relayer1.signer_id = signer1_id.clone();
1017 repo.create(relayer1).await.unwrap();
1018
1019 let mut relayer2 = create_test_relayer(&relayer2_id);
1020
1021 relayer2.signer_id = signer2_id.clone();
1022 repo.create(relayer2).await.unwrap();
1023
1024 let mut relayer3 = create_test_relayer(&relayer3_id);
1025 relayer3.signer_id = signer1_id.clone();
1026 repo.create(relayer3).await.unwrap();
1027
1028 let result = repo.list_by_signer_id(&signer1_id).await.unwrap();
1029 assert_eq!(result.len(), 2);
1030 let ids: Vec<_> = result.iter().map(|r| r.id.clone()).collect();
1031 assert!(ids.contains(&relayer1_id));
1032 assert!(ids.contains(&relayer3_id));
1033
1034 let result = repo.list_by_signer_id(&signer2_id).await.unwrap();
1035 assert_eq!(result.len(), 1);
1036
1037 let result = repo.list_by_signer_id("nonexistent").await.unwrap();
1038 assert_eq!(result.len(), 0);
1039 }
1040
1041 #[ignore = "Requires active Redis instance"]
1042 #[tokio::test]
1043 async fn test_list_by_notification_id() {
1044 let repo = setup_test_repo().await;
1045
1046 let relayer1_id = uuid::Uuid::new_v4().to_string();
1047 let mut relayer1 = create_test_relayer(&relayer1_id);
1048 relayer1.notification_id = Some("notif1".to_string());
1049 repo.create(relayer1).await.unwrap();
1050
1051 let relayer2_id = uuid::Uuid::new_v4().to_string();
1052 let mut relayer2 = create_test_relayer(&relayer2_id);
1053 relayer2.notification_id = Some("notif2".to_string());
1054 repo.create(relayer2).await.unwrap();
1055
1056 let relayer3_id = uuid::Uuid::new_v4().to_string();
1057 let mut relayer3 = create_test_relayer(&relayer3_id);
1058 relayer3.notification_id = Some("notif1".to_string());
1059 repo.create(relayer3).await.unwrap();
1060
1061 let relayer4_id = uuid::Uuid::new_v4().to_string();
1062 let mut relayer4 = create_test_relayer(&relayer4_id);
1063 relayer4.notification_id = None;
1064 repo.create(relayer4).await.unwrap();
1065
1066 let result = repo.list_by_notification_id("notif1").await.unwrap();
1067 assert_eq!(result.len(), 2);
1068 let ids: Vec<_> = result.iter().map(|r| r.id.clone()).collect();
1069 assert!(ids.contains(&relayer1_id));
1070 assert!(ids.contains(&relayer3_id));
1071
1072 let result = repo.list_by_notification_id("notif2").await.unwrap();
1073 assert_eq!(result.len(), 1);
1074
1075 let result = repo.list_by_notification_id("nonexistent").await.unwrap();
1076 assert_eq!(result.len(), 0);
1077 }
1078}