openzeppelin_relayer/repositories/signer/
signer_redis.rs

1//! Redis-backed implementation of the signer repository.
2
3use crate::models::{RepositoryError, SignerRepoModel};
4use crate::repositories::redis_base::RedisRepository;
5use crate::repositories::*;
6use async_trait::async_trait;
7use log::{debug, error, warn};
8use redis::aio::ConnectionManager;
9use redis::{AsyncCommands, RedisError};
10use std::fmt;
11use std::sync::Arc;
12
13const SIGNER_PREFIX: &str = "signer";
14const SIGNER_LIST_KEY: &str = "signer_list";
15
16#[derive(Clone)]
17pub struct RedisSignerRepository {
18    pub client: Arc<ConnectionManager>,
19    pub key_prefix: String,
20}
21
22impl RedisRepository for RedisSignerRepository {}
23
24impl RedisSignerRepository {
25    pub fn new(
26        connection_manager: Arc<ConnectionManager>,
27        key_prefix: String,
28    ) -> Result<Self, RepositoryError> {
29        if key_prefix.is_empty() {
30            return Err(RepositoryError::InvalidData(
31                "Redis key prefix cannot be empty".to_string(),
32            ));
33        }
34
35        Ok(Self {
36            client: connection_manager,
37            key_prefix,
38        })
39    }
40
41    fn signer_key(&self, id: &str) -> String {
42        format!("{}:{}:{}", self.key_prefix, SIGNER_PREFIX, id)
43    }
44
45    fn signer_list_key(&self) -> String {
46        format!("{}:{}", self.key_prefix, SIGNER_LIST_KEY)
47    }
48
49    async fn add_to_list(&self, id: &str) -> Result<(), RepositoryError> {
50        let key = self.signer_list_key();
51        let mut conn = self.client.as_ref().clone();
52
53        let result: Result<i64, RedisError> = conn.sadd(&key, id).await;
54        result.map_err(|e| {
55            error!("Failed to add signer {} to list: {}", id, e);
56            RepositoryError::Other(format!("Failed to add signer to list: {}", e))
57        })?;
58
59        debug!("Added signer {} to list", id);
60        Ok(())
61    }
62
63    async fn remove_from_list(&self, id: &str) -> Result<(), RepositoryError> {
64        let key = self.signer_list_key();
65        let mut conn = self.client.as_ref().clone();
66
67        let result: Result<i64, RedisError> = conn.srem(&key, id).await;
68        result.map_err(|e| {
69            error!("Failed to remove signer {} from list: {}", id, e);
70            RepositoryError::Other(format!("Failed to remove signer from list: {}", e))
71        })?;
72
73        debug!("Removed signer {} from list", id);
74        Ok(())
75    }
76
77    async fn get_all_ids(&self) -> Result<Vec<String>, RepositoryError> {
78        let key = self.signer_list_key();
79        let mut conn = self.client.as_ref().clone();
80
81        let result: Result<Vec<String>, RedisError> = conn.smembers(&key).await;
82        result.map_err(|e| {
83            error!("Failed to get signer IDs: {}", e);
84            RepositoryError::Other(format!("Failed to get signer IDs: {}", e))
85        })
86    }
87
88    /// Batch fetch signers by IDs
89    async fn get_signers_by_ids(
90        &self,
91        ids: &[String],
92    ) -> Result<BatchRetrievalResult<SignerRepoModel>, RepositoryError> {
93        if ids.is_empty() {
94            debug!("No signer IDs provided for batch fetch");
95            return Ok(BatchRetrievalResult {
96                results: vec![],
97                failed_ids: vec![],
98            });
99        }
100
101        let mut conn = self.client.as_ref().clone();
102        let keys: Vec<String> = ids.iter().map(|id| self.signer_key(id)).collect();
103
104        debug!("Batch fetching {} signers", ids.len());
105
106        let values: Vec<Option<String>> = conn
107            .mget(&keys)
108            .await
109            .map_err(|e| self.map_redis_error(e, "batch_fetch_signers"))?;
110
111        let mut signers = Vec::new();
112        let mut failed_count = 0;
113        let mut failed_ids = Vec::new();
114
115        for (i, value) in values.into_iter().enumerate() {
116            match value {
117                Some(json) => {
118                    match self.deserialize_entity::<SignerRepoModel>(&json, &ids[i], "signer") {
119                        Ok(signer) => signers.push(signer),
120                        Err(e) => {
121                            failed_count += 1;
122                            error!("Failed to deserialize signer {}: {}", ids[i], e);
123                            failed_ids.push(ids[i].clone());
124                        }
125                    }
126                }
127                None => {
128                    warn!("Signer {} not found in batch fetch", ids[i]);
129                }
130            }
131        }
132
133        if failed_count > 0 {
134            warn!(
135                "Failed to deserialize {} out of {} signers in batch",
136                failed_count,
137                ids.len()
138            );
139            warn!("Failed to deserialize signers: {:?}", failed_ids);
140        }
141
142        debug!("Successfully fetched {} signers", signers.len());
143        Ok(BatchRetrievalResult {
144            results: signers,
145            failed_ids,
146        })
147    }
148}
149
150impl fmt::Debug for RedisSignerRepository {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.debug_struct("RedisSignerRepository")
153            .field("key_prefix", &self.key_prefix)
154            .finish()
155    }
156}
157
158#[async_trait]
159impl Repository<SignerRepoModel, String> for RedisSignerRepository {
160    async fn create(&self, signer: SignerRepoModel) -> Result<SignerRepoModel, RepositoryError> {
161        if signer.id.is_empty() {
162            return Err(RepositoryError::InvalidData(
163                "Signer ID cannot be empty".to_string(),
164            ));
165        }
166
167        let key = self.signer_key(&signer.id);
168        let mut conn = self.client.as_ref().clone();
169
170        // Check if signer already exists
171        let exists: Result<bool, RedisError> = conn.exists(&key).await;
172        match exists {
173            Ok(true) => {
174                return Err(RepositoryError::ConstraintViolation(format!(
175                    "Signer with ID {} already exists",
176                    signer.id
177                )));
178            }
179            Ok(false) => {
180                // Continue with creation
181            }
182            Err(e) => {
183                error!("Failed to check if signer exists: {}", e);
184                return Err(RepositoryError::Other(format!(
185                    "Failed to check signer existence: {}",
186                    e
187                )));
188            }
189        }
190
191        // Serialize signer (encryption happens automatically for human-readable formats)
192        let serialized = self.serialize_entity(&signer, |s| &s.id, "signer")?;
193
194        // Store signer
195        let result: Result<(), RedisError> = conn.set(&key, &serialized).await;
196        result.map_err(|e| {
197            error!("Failed to store signer {}: {}", signer.id, e);
198            RepositoryError::Other(format!("Failed to store signer: {}", e))
199        })?;
200
201        // Add to list
202        self.add_to_list(&signer.id).await?;
203
204        debug!("Created signer with ID: {}", signer.id);
205        Ok(signer)
206    }
207
208    async fn get_by_id(&self, id: String) -> Result<SignerRepoModel, RepositoryError> {
209        if id.is_empty() {
210            return Err(RepositoryError::InvalidData(
211                "Signer ID cannot be empty".to_string(),
212            ));
213        }
214
215        let key = self.signer_key(&id);
216        let mut conn = self.client.as_ref().clone();
217
218        let result: Result<Option<String>, RedisError> = conn.get(&key).await;
219        match result {
220            Ok(Some(data)) => {
221                // Deserialize signer (decryption happens automatically)
222                let signer = self.deserialize_entity::<SignerRepoModel>(&data, &id, "signer")?;
223                debug!("Retrieved signer with ID: {}", id);
224                Ok(signer)
225            }
226            Ok(None) => {
227                debug!("Signer with ID {} not found", id);
228                Err(RepositoryError::NotFound(format!(
229                    "Signer with ID {} not found",
230                    id
231                )))
232            }
233            Err(e) => {
234                error!("Failed to retrieve signer {}: {}", id, e);
235                Err(RepositoryError::Other(format!(
236                    "Failed to retrieve signer: {}",
237                    e
238                )))
239            }
240        }
241    }
242
243    async fn update(
244        &self,
245        id: String,
246        signer: SignerRepoModel,
247    ) -> Result<SignerRepoModel, RepositoryError> {
248        if id.is_empty() {
249            return Err(RepositoryError::InvalidData(
250                "Signer ID cannot be empty".to_string(),
251            ));
252        }
253
254        if signer.id != id {
255            return Err(RepositoryError::InvalidData(
256                "Signer ID in data does not match provided ID".to_string(),
257            ));
258        }
259
260        let key = self.signer_key(&id);
261        let mut conn = self.client.as_ref().clone();
262
263        // Check if signer exists
264        let exists: Result<bool, RedisError> = conn.exists(&key).await;
265        match exists {
266            Ok(false) => {
267                return Err(RepositoryError::NotFound(format!(
268                    "Signer with ID {} not found",
269                    id
270                )));
271            }
272            Ok(true) => {
273                // Continue with update
274            }
275            Err(e) => {
276                error!("Failed to check if signer exists: {}", e);
277                return Err(RepositoryError::Other(format!(
278                    "Failed to check signer existence: {}",
279                    e
280                )));
281            }
282        }
283
284        // Serialize signer (encryption happens automatically for human-readable formats)
285        let serialized = self.serialize_entity(&signer, |s| &s.id, "signer")?;
286
287        // Update signer
288        let result: Result<(), RedisError> = conn.set(&key, &serialized).await;
289        result.map_err(|e| {
290            error!("Failed to update signer {}: {}", id, e);
291            RepositoryError::Other(format!("Failed to update signer: {}", e))
292        })?;
293
294        debug!("Updated signer with ID: {}", id);
295        Ok(signer)
296    }
297
298    async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
299        if id.is_empty() {
300            return Err(RepositoryError::InvalidData(
301                "Signer ID cannot be empty".to_string(),
302            ));
303        }
304
305        let key = self.signer_key(&id);
306        let mut conn = self.client.as_ref().clone();
307
308        // Check if signer exists
309        let exists: Result<bool, RedisError> = conn.exists(&key).await;
310        match exists {
311            Ok(false) => {
312                return Err(RepositoryError::NotFound(format!(
313                    "Signer with ID {} not found",
314                    id
315                )));
316            }
317            Ok(true) => {
318                // Continue with deletion
319            }
320            Err(e) => {
321                error!("Failed to check if signer exists: {}", e);
322                return Err(RepositoryError::Other(format!(
323                    "Failed to check signer existence: {}",
324                    e
325                )));
326            }
327        }
328
329        // Delete signer
330        let result: Result<i64, RedisError> = conn.del(&key).await;
331        result.map_err(|e| {
332            error!("Failed to delete signer {}: {}", id, e);
333            RepositoryError::Other(format!("Failed to delete signer: {}", e))
334        })?;
335
336        // Remove from list
337        self.remove_from_list(&id).await?;
338
339        debug!("Deleted signer with ID: {}", id);
340        Ok(())
341    }
342
343    async fn list_all(&self) -> Result<Vec<SignerRepoModel>, RepositoryError> {
344        let ids = self.get_all_ids().await?;
345
346        if ids.is_empty() {
347            debug!("No signers found");
348            return Ok(Vec::new());
349        }
350
351        let signers = self.get_signers_by_ids(&ids).await?;
352        debug!("Successfully fetched {} signers", signers.results.len());
353        Ok(signers.results)
354    }
355
356    async fn list_paginated(
357        &self,
358        query: PaginationQuery,
359    ) -> Result<PaginatedResult<SignerRepoModel>, RepositoryError> {
360        if query.per_page == 0 {
361            return Err(RepositoryError::InvalidData(
362                "per_page must be greater than 0".to_string(),
363            ));
364        }
365
366        debug!(
367            "Listing paginated signers: page {}, per_page {}",
368            query.page, query.per_page
369        );
370
371        let all_ids: Vec<String> = self.get_all_ids().await?;
372        let total = all_ids.len() as u64;
373        let per_page = query.per_page as usize;
374        let page = query.page as usize;
375        let total_pages = all_ids.len().div_ceil(per_page);
376
377        if page > total_pages && !all_ids.is_empty() {
378            debug!(
379                "Requested page {} exceeds total pages {}",
380                page, total_pages
381            );
382            return Ok(PaginatedResult {
383                items: Vec::new(),
384                total,
385                page: query.page,
386                per_page: query.per_page,
387            });
388        }
389
390        let start_idx = (page - 1) * per_page;
391        let end_idx = std::cmp::min(start_idx + per_page, all_ids.len());
392
393        let page_ids = all_ids[start_idx..end_idx].to_vec();
394        let signers = self.get_signers_by_ids(&page_ids).await?;
395
396        debug!(
397            "Successfully retrieved {} signers for page {}",
398            signers.results.len(),
399            query.page
400        );
401        Ok(PaginatedResult {
402            items: signers.results.clone(),
403            total,
404            page: query.page,
405            per_page: query.per_page,
406        })
407    }
408
409    async fn count(&self) -> Result<usize, RepositoryError> {
410        let ids = self.get_all_ids().await?;
411        Ok(ids.len())
412    }
413
414    async fn has_entries(&self) -> Result<bool, RepositoryError> {
415        let mut conn = self.client.as_ref().clone();
416        let signer_list_key = self.signer_list_key();
417
418        debug!("Checking if signer entries exist");
419
420        let exists: bool = conn
421            .exists(&signer_list_key)
422            .await
423            .map_err(|e| self.map_redis_error(e, "has_entries_check"))?;
424
425        debug!("Signer entries exist: {}", exists);
426        Ok(exists)
427    }
428
429    async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
430        let mut conn = self.client.as_ref().clone();
431        let signer_list_key = self.signer_list_key();
432
433        debug!("Dropping all signer entries");
434
435        // Get all signer IDs first
436        let signer_ids: Vec<String> = conn
437            .smembers(&signer_list_key)
438            .await
439            .map_err(|e| self.map_redis_error(e, "drop_all_entries_get_ids"))?;
440
441        if signer_ids.is_empty() {
442            debug!("No signer entries to drop");
443            return Ok(());
444        }
445
446        // Use pipeline for atomic operations
447        let mut pipe = redis::pipe();
448        pipe.atomic();
449
450        // Delete all individual signer entries
451        for signer_id in &signer_ids {
452            let signer_key = self.signer_key(signer_id);
453            pipe.del(&signer_key);
454        }
455
456        // Delete the signer list key
457        pipe.del(&signer_list_key);
458
459        pipe.exec_async(&mut conn)
460            .await
461            .map_err(|e| self.map_redis_error(e, "drop_all_entries_pipeline"))?;
462
463        debug!("Dropped {} signer entries", signer_ids.len());
464        Ok(())
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471    use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
472    use secrets::SecretVec;
473    use std::sync::Arc;
474
475    fn create_local_signer(id: &str) -> SignerRepoModel {
476        SignerRepoModel {
477            id: id.to_string(),
478            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
479                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
480            }),
481        }
482    }
483
484    async fn setup_test_repo() -> RedisSignerRepository {
485        let client =
486            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
487        let connection_manager = redis::aio::ConnectionManager::new(client)
488            .await
489            .expect("Failed to create connection manager");
490
491        RedisSignerRepository::new(Arc::new(connection_manager), "test".to_string())
492            .expect("Failed to create repository")
493    }
494
495    #[tokio::test]
496    #[ignore = "Requires active Redis instance"]
497    async fn test_new_repository_creation() {
498        let repo = setup_test_repo().await;
499        assert_eq!(repo.key_prefix, "test");
500    }
501
502    #[tokio::test]
503    #[ignore = "Requires active Redis instance"]
504    async fn test_new_repository_empty_prefix_fails() {
505        let client =
506            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
507        let connection_manager = redis::aio::ConnectionManager::new(client)
508            .await
509            .expect("Failed to create connection manager");
510
511        let result = RedisSignerRepository::new(Arc::new(connection_manager), "".to_string());
512        assert!(result.is_err());
513        assert!(result
514            .unwrap_err()
515            .to_string()
516            .contains("key prefix cannot be empty"));
517    }
518
519    #[tokio::test]
520    #[ignore = "Requires active Redis instance"]
521    async fn test_key_generation() {
522        let repo = setup_test_repo().await;
523        let signer_key = repo.signer_key("test-id");
524        let list_key = repo.signer_list_key();
525
526        assert_eq!(signer_key, "test:signer:test-id");
527        assert_eq!(list_key, "test:signer_list");
528    }
529
530    #[tokio::test]
531    #[ignore = "Requires active Redis instance"]
532    async fn test_serialize_deserialize_signer() {
533        let repo = setup_test_repo().await;
534        let signer = create_local_signer("test-signer");
535
536        let serialized = repo.serialize_entity(&signer, |s| &s.id, "signer").unwrap();
537        let deserialized: SignerRepoModel = repo
538            .deserialize_entity(&serialized, &signer.id, "signer")
539            .unwrap();
540
541        assert_eq!(signer.id, deserialized.id);
542        assert!(matches!(signer.config, SignerConfigStorage::Local(_)));
543        assert!(matches!(deserialized.config, SignerConfigStorage::Local(_)));
544    }
545
546    #[tokio::test]
547    #[ignore = "Requires active Redis instance"]
548    async fn test_create_signer() {
549        let repo = setup_test_repo().await;
550        let signer_name = uuid::Uuid::new_v4().to_string();
551        let signer = create_local_signer(&signer_name);
552
553        let result = repo.create(signer).await;
554        assert!(result.is_ok());
555
556        let created_signer = result.unwrap();
557        assert_eq!(created_signer.id, signer_name);
558    }
559
560    #[tokio::test]
561    #[ignore = "Requires active Redis instance"]
562    async fn test_get_signer() {
563        let repo = setup_test_repo().await;
564        let signer_name = uuid::Uuid::new_v4().to_string();
565        let signer = create_local_signer(&signer_name);
566
567        // Create the signer first
568        repo.create(signer.clone()).await.unwrap();
569
570        // Get the signer
571        let retrieved = repo.get_by_id(signer_name.clone()).await.unwrap();
572        assert_eq!(retrieved.id, signer.id);
573        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
574    }
575
576    #[tokio::test]
577    #[ignore = "Requires active Redis instance"]
578    async fn test_get_nonexistent_signer() {
579        let repo = setup_test_repo().await;
580        let result = repo.get_by_id("nonexistent-id".to_string()).await;
581
582        assert!(result.is_err());
583        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
584    }
585
586    #[tokio::test]
587    #[ignore = "Requires active Redis instance"]
588    async fn test_update_signer() {
589        let repo = setup_test_repo().await;
590        let signer_name = uuid::Uuid::new_v4().to_string();
591        let signer = create_local_signer(&signer_name);
592
593        // Create the signer first
594        repo.create(signer.clone()).await.unwrap();
595
596        // Update the signer
597        let updated_signer = SignerRepoModel {
598            id: signer_name.clone(),
599            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
600                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[2; 32])),
601            }),
602        };
603
604        let result = repo.update(signer_name.clone(), updated_signer).await;
605        assert!(result.is_ok());
606
607        // Verify the update
608        let retrieved = repo.get_by_id(signer_name).await.unwrap();
609        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
610    }
611
612    #[tokio::test]
613    #[ignore = "Requires active Redis instance"]
614    async fn test_delete_signer() {
615        let repo = setup_test_repo().await;
616        let signer_name = uuid::Uuid::new_v4().to_string();
617        let signer = create_local_signer(&signer_name);
618
619        // Create the signer first
620        repo.create(signer).await.unwrap();
621
622        // Delete the signer
623        let result = repo.delete_by_id(signer_name.clone()).await;
624        assert!(result.is_ok());
625
626        // Verify deletion
627        let get_result = repo.get_by_id(signer_name).await;
628        assert!(get_result.is_err());
629        assert!(matches!(
630            get_result.unwrap_err(),
631            RepositoryError::NotFound(_)
632        ));
633    }
634
635    #[tokio::test]
636    #[ignore = "Requires active Redis instance"]
637    async fn test_list_all_signers() {
638        let repo = setup_test_repo().await;
639        let signer1_name = uuid::Uuid::new_v4().to_string();
640        let signer2_name = uuid::Uuid::new_v4().to_string();
641        let signer1 = create_local_signer(&signer1_name);
642        let signer2 = create_local_signer(&signer2_name);
643
644        // Create signers
645        repo.create(signer1).await.unwrap();
646        repo.create(signer2).await.unwrap();
647
648        // List all signers
649        let signers = repo.list_all().await.unwrap();
650        assert!(signers.len() >= 2);
651
652        let ids: Vec<String> = signers.iter().map(|s| s.id.clone()).collect();
653        assert!(ids.contains(&signer1_name));
654        assert!(ids.contains(&signer2_name));
655    }
656
657    #[tokio::test]
658    #[ignore = "Requires active Redis instance"]
659    async fn test_count_signers() {
660        let repo = setup_test_repo().await;
661        let initial_count = repo.count().await.unwrap();
662
663        let signer_name = uuid::Uuid::new_v4().to_string();
664        let signer = create_local_signer(&signer_name);
665
666        // Create a signer
667        repo.create(signer).await.unwrap();
668
669        // Check count increased
670        let new_count = repo.count().await.unwrap();
671        assert!(new_count > initial_count);
672    }
673
674    #[tokio::test]
675    #[ignore = "Requires active Redis instance"]
676    async fn test_list_paginated_signers() {
677        let repo = setup_test_repo().await;
678        let signer1_name = uuid::Uuid::new_v4().to_string();
679        let signer2_name = uuid::Uuid::new_v4().to_string();
680        let signer1 = create_local_signer(&signer1_name);
681        let signer2 = create_local_signer(&signer2_name);
682
683        // Create signers
684        repo.create(signer1).await.unwrap();
685        repo.create(signer2).await.unwrap();
686
687        // Test pagination
688        let query = PaginationQuery {
689            page: 1,
690            per_page: 1,
691        };
692
693        let result = repo.list_paginated(query).await.unwrap();
694        assert_eq!(result.items.len(), 1);
695        assert!(result.total >= 2);
696        assert_eq!(result.page, 1);
697        assert_eq!(result.per_page, 1);
698    }
699
700    #[tokio::test]
701    #[ignore = "Requires active Redis instance"]
702    async fn test_duplicate_signer_creation() {
703        let repo = setup_test_repo().await;
704        let signer_name = uuid::Uuid::new_v4().to_string();
705        let signer = create_local_signer(&signer_name);
706
707        // Create the signer first time
708        repo.create(signer.clone()).await.unwrap();
709
710        // Try to create the same signer again
711        let result = repo.create(signer).await;
712        assert!(result.is_err());
713        assert!(matches!(
714            result.unwrap_err(),
715            RepositoryError::ConstraintViolation(_)
716        ));
717    }
718
719    #[tokio::test]
720    #[ignore = "Requires active Redis instance"]
721    async fn test_debug_implementation() {
722        let repo = setup_test_repo().await;
723        let debug_str = format!("{:?}", repo);
724        assert!(debug_str.contains("RedisSignerRepository"));
725        assert!(debug_str.contains("test"));
726    }
727
728    #[tokio::test]
729    #[ignore = "Requires active Redis instance"]
730    async fn test_error_handling_empty_id() {
731        let repo = setup_test_repo().await;
732
733        let result = repo.get_by_id("".to_string()).await;
734        assert!(result.is_err());
735        assert!(result
736            .unwrap_err()
737            .to_string()
738            .contains("ID cannot be empty"));
739    }
740
741    #[tokio::test]
742    #[ignore = "Requires active Redis instance"]
743    async fn test_create_signer_with_empty_id() {
744        let repo = setup_test_repo().await;
745        let signer = SignerRepoModel {
746            id: "".to_string(),
747            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
748                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
749            }),
750        };
751
752        let result = repo.create(signer).await;
753        assert!(result.is_err());
754        assert!(result
755            .unwrap_err()
756            .to_string()
757            .contains("ID cannot be empty"));
758    }
759
760    #[tokio::test]
761    #[ignore = "Requires active Redis instance"]
762    async fn test_update_nonexistent_signer() {
763        let repo = setup_test_repo().await;
764        let signer = create_local_signer("nonexistent-id");
765
766        let result = repo.update("nonexistent-id".to_string(), signer).await;
767        assert!(result.is_err());
768        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
769    }
770
771    #[tokio::test]
772    #[ignore = "Requires active Redis instance"]
773    async fn test_delete_nonexistent_signer() {
774        let repo = setup_test_repo().await;
775
776        let result = repo.delete_by_id("nonexistent-id".to_string()).await;
777        assert!(result.is_err());
778        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
779    }
780
781    #[tokio::test]
782    #[ignore = "Requires active Redis instance"]
783    async fn test_update_with_mismatched_id() {
784        let repo = setup_test_repo().await;
785        let signer_name = uuid::Uuid::new_v4().to_string();
786        let signer = create_local_signer(&signer_name);
787
788        // Create the signer first
789        repo.create(signer).await.unwrap();
790
791        // Try to update with different ID
792        let updated_signer = create_local_signer("different-id");
793        let result = repo.update(signer_name, updated_signer).await;
794        assert!(result.is_err());
795        assert!(result
796            .unwrap_err()
797            .to_string()
798            .contains("ID in data does not match"));
799    }
800
801    #[tokio::test]
802    #[ignore = "Requires active Redis instance"]
803    async fn test_has_entries() {
804        let repo = setup_test_repo().await;
805
806        let signer_id = uuid::Uuid::new_v4().to_string();
807        let signer = create_local_signer(&signer_id);
808        repo.create(signer.clone()).await.unwrap();
809        assert!(repo.has_entries().await.unwrap());
810    }
811
812    #[tokio::test]
813    #[ignore = "Requires active Redis instance"]
814    async fn test_drop_all_entries() {
815        let repo = setup_test_repo().await;
816        let signer_id = uuid::Uuid::new_v4().to_string();
817        let signer = create_local_signer(&signer_id);
818
819        repo.create(signer.clone()).await.unwrap();
820        assert!(repo.has_entries().await.unwrap());
821
822        repo.drop_all_entries().await.unwrap();
823        assert!(!repo.has_entries().await.unwrap());
824    }
825}