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 redis::aio::ConnectionManager;
8use redis::{AsyncCommands, RedisError};
9use std::fmt;
10use std::sync::Arc;
11use tracing::{debug, error, warn};
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!(signer_id = %id, error = %e, "failed to add signer to list");
56            RepositoryError::Other(format!("Failed to add signer to list: {}", e))
57        })?;
58
59        debug!(signer_id = %id, "added signer to list");
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!(signer_id = %id, error = %e, "failed to remove signer from list");
70            RepositoryError::Other(format!("Failed to remove signer from list: {}", e))
71        })?;
72
73        debug!(signer_id = %id, "removed signer from list");
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!(error = %e, "failed to get signer IDs");
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!(count = ids.len(), "batch fetching signers");
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!(signer_id = %ids[i], error = %e, "failed to deserialize signer");
123                            failed_ids.push(ids[i].clone());
124                        }
125                    }
126                }
127                None => {
128                    warn!(signer_id = %ids[i], "signer not found in batch fetch");
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_ids = ?failed_ids, "failed to deserialize signers");
140        }
141
142        debug!(count = signers.len(), "successfully fetched signers");
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!(error = %e, "failed to check if signer exists");
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!(signer_id = %signer.id, error = %e, "failed to store signer");
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!(signer_id = %signer.id, "created signer");
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!(signer_id = %id, "retrieved signer");
224                Ok(signer)
225            }
226            Ok(None) => {
227                debug!(signer_id = %id, "signer not found");
228                Err(RepositoryError::NotFound(format!(
229                    "Signer with ID {} not found",
230                    id
231                )))
232            }
233            Err(e) => {
234                error!(signer_id = %id, error = %e, "failed to retrieve signer");
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!(error = %e, "failed to check if signer exists");
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!(signer_id = %id, error = %e, "failed to update signer");
291            RepositoryError::Other(format!("Failed to update signer: {}", e))
292        })?;
293
294        debug!(signer_id = %id, "updated signer");
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!(error = %e, "failed to check if signer exists");
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!(signer_id = %id, error = %e, "failed to delete signer");
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!(signer_id = %id, "deleted signer");
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!(
353            count = signers.results.len(),
354            "successfully fetched signers"
355        );
356        Ok(signers.results)
357    }
358
359    async fn list_paginated(
360        &self,
361        query: PaginationQuery,
362    ) -> Result<PaginatedResult<SignerRepoModel>, RepositoryError> {
363        if query.per_page == 0 {
364            return Err(RepositoryError::InvalidData(
365                "per_page must be greater than 0".to_string(),
366            ));
367        }
368
369        debug!(
370            "Listing paginated signers: page {}, per_page {}",
371            query.page, query.per_page
372        );
373
374        let all_ids: Vec<String> = self.get_all_ids().await?;
375        let total = all_ids.len() as u64;
376        let per_page = query.per_page as usize;
377        let page = query.page as usize;
378        let total_pages = all_ids.len().div_ceil(per_page);
379
380        if page > total_pages && !all_ids.is_empty() {
381            debug!(
382                "Requested page {} exceeds total pages {}",
383                page, total_pages
384            );
385            return Ok(PaginatedResult {
386                items: Vec::new(),
387                total,
388                page: query.page,
389                per_page: query.per_page,
390            });
391        }
392
393        let start_idx = (page - 1) * per_page;
394        let end_idx = std::cmp::min(start_idx + per_page, all_ids.len());
395
396        let page_ids = all_ids[start_idx..end_idx].to_vec();
397        let signers = self.get_signers_by_ids(&page_ids).await?;
398
399        debug!(
400            "Successfully retrieved {} signers for page {}",
401            signers.results.len(),
402            query.page
403        );
404        Ok(PaginatedResult {
405            items: signers.results.clone(),
406            total,
407            page: query.page,
408            per_page: query.per_page,
409        })
410    }
411
412    async fn count(&self) -> Result<usize, RepositoryError> {
413        let ids = self.get_all_ids().await?;
414        Ok(ids.len())
415    }
416
417    async fn has_entries(&self) -> Result<bool, RepositoryError> {
418        let mut conn = self.client.as_ref().clone();
419        let signer_list_key = self.signer_list_key();
420
421        debug!("Checking if signer entries exist");
422
423        let exists: bool = conn
424            .exists(&signer_list_key)
425            .await
426            .map_err(|e| self.map_redis_error(e, "has_entries_check"))?;
427
428        debug!(exists = %exists, "signer entries exist");
429        Ok(exists)
430    }
431
432    async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
433        let mut conn = self.client.as_ref().clone();
434        let signer_list_key = self.signer_list_key();
435
436        debug!("Dropping all signer entries");
437
438        // Get all signer IDs first
439        let signer_ids: Vec<String> = conn
440            .smembers(&signer_list_key)
441            .await
442            .map_err(|e| self.map_redis_error(e, "drop_all_entries_get_ids"))?;
443
444        if signer_ids.is_empty() {
445            debug!("No signer entries to drop");
446            return Ok(());
447        }
448
449        // Use pipeline for atomic operations
450        let mut pipe = redis::pipe();
451        pipe.atomic();
452
453        // Delete all individual signer entries
454        for signer_id in &signer_ids {
455            let signer_key = self.signer_key(signer_id);
456            pipe.del(&signer_key);
457        }
458
459        // Delete the signer list key
460        pipe.del(&signer_list_key);
461
462        pipe.exec_async(&mut conn)
463            .await
464            .map_err(|e| self.map_redis_error(e, "drop_all_entries_pipeline"))?;
465
466        debug!(count = %signer_ids.len(), "dropped signer entries");
467        Ok(())
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474    use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
475    use secrets::SecretVec;
476    use std::sync::Arc;
477
478    fn create_local_signer(id: &str) -> SignerRepoModel {
479        SignerRepoModel {
480            id: id.to_string(),
481            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
482                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
483            }),
484        }
485    }
486
487    async fn setup_test_repo() -> RedisSignerRepository {
488        let client =
489            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
490        let connection_manager = redis::aio::ConnectionManager::new(client)
491            .await
492            .expect("Failed to create connection manager");
493
494        RedisSignerRepository::new(Arc::new(connection_manager), "test".to_string())
495            .expect("Failed to create repository")
496    }
497
498    #[tokio::test]
499    #[ignore = "Requires active Redis instance"]
500    async fn test_new_repository_creation() {
501        let repo = setup_test_repo().await;
502        assert_eq!(repo.key_prefix, "test");
503    }
504
505    #[tokio::test]
506    #[ignore = "Requires active Redis instance"]
507    async fn test_new_repository_empty_prefix_fails() {
508        let client =
509            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
510        let connection_manager = redis::aio::ConnectionManager::new(client)
511            .await
512            .expect("Failed to create connection manager");
513
514        let result = RedisSignerRepository::new(Arc::new(connection_manager), "".to_string());
515        assert!(result.is_err());
516        assert!(result
517            .unwrap_err()
518            .to_string()
519            .contains("key prefix cannot be empty"));
520    }
521
522    #[tokio::test]
523    #[ignore = "Requires active Redis instance"]
524    async fn test_key_generation() {
525        let repo = setup_test_repo().await;
526        let signer_key = repo.signer_key("test-id");
527        let list_key = repo.signer_list_key();
528
529        assert_eq!(signer_key, "test:signer:test-id");
530        assert_eq!(list_key, "test:signer_list");
531    }
532
533    #[tokio::test]
534    #[ignore = "Requires active Redis instance"]
535    async fn test_serialize_deserialize_signer() {
536        let repo = setup_test_repo().await;
537        let signer = create_local_signer("test-signer");
538
539        let serialized = repo.serialize_entity(&signer, |s| &s.id, "signer").unwrap();
540        let deserialized: SignerRepoModel = repo
541            .deserialize_entity(&serialized, &signer.id, "signer")
542            .unwrap();
543
544        assert_eq!(signer.id, deserialized.id);
545        assert!(matches!(signer.config, SignerConfigStorage::Local(_)));
546        assert!(matches!(deserialized.config, SignerConfigStorage::Local(_)));
547    }
548
549    #[tokio::test]
550    #[ignore = "Requires active Redis instance"]
551    async fn test_create_signer() {
552        let repo = setup_test_repo().await;
553        let signer_name = uuid::Uuid::new_v4().to_string();
554        let signer = create_local_signer(&signer_name);
555
556        let result = repo.create(signer).await;
557        assert!(result.is_ok());
558
559        let created_signer = result.unwrap();
560        assert_eq!(created_signer.id, signer_name);
561    }
562
563    #[tokio::test]
564    #[ignore = "Requires active Redis instance"]
565    async fn test_get_signer() {
566        let repo = setup_test_repo().await;
567        let signer_name = uuid::Uuid::new_v4().to_string();
568        let signer = create_local_signer(&signer_name);
569
570        // Create the signer first
571        repo.create(signer.clone()).await.unwrap();
572
573        // Get the signer
574        let retrieved = repo.get_by_id(signer_name.clone()).await.unwrap();
575        assert_eq!(retrieved.id, signer.id);
576        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
577    }
578
579    #[tokio::test]
580    #[ignore = "Requires active Redis instance"]
581    async fn test_get_nonexistent_signer() {
582        let repo = setup_test_repo().await;
583        let result = repo.get_by_id("nonexistent-id".to_string()).await;
584
585        assert!(result.is_err());
586        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
587    }
588
589    #[tokio::test]
590    #[ignore = "Requires active Redis instance"]
591    async fn test_update_signer() {
592        let repo = setup_test_repo().await;
593        let signer_name = uuid::Uuid::new_v4().to_string();
594        let signer = create_local_signer(&signer_name);
595
596        // Create the signer first
597        repo.create(signer.clone()).await.unwrap();
598
599        // Update the signer
600        let updated_signer = SignerRepoModel {
601            id: signer_name.clone(),
602            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
603                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[2; 32])),
604            }),
605        };
606
607        let result = repo.update(signer_name.clone(), updated_signer).await;
608        assert!(result.is_ok());
609
610        // Verify the update
611        let retrieved = repo.get_by_id(signer_name).await.unwrap();
612        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
613    }
614
615    #[tokio::test]
616    #[ignore = "Requires active Redis instance"]
617    async fn test_delete_signer() {
618        let repo = setup_test_repo().await;
619        let signer_name = uuid::Uuid::new_v4().to_string();
620        let signer = create_local_signer(&signer_name);
621
622        // Create the signer first
623        repo.create(signer).await.unwrap();
624
625        // Delete the signer
626        let result = repo.delete_by_id(signer_name.clone()).await;
627        assert!(result.is_ok());
628
629        // Verify deletion
630        let get_result = repo.get_by_id(signer_name).await;
631        assert!(get_result.is_err());
632        assert!(matches!(
633            get_result.unwrap_err(),
634            RepositoryError::NotFound(_)
635        ));
636    }
637
638    #[tokio::test]
639    #[ignore = "Requires active Redis instance"]
640    async fn test_list_all_signers() {
641        let repo = setup_test_repo().await;
642        let signer1_name = uuid::Uuid::new_v4().to_string();
643        let signer2_name = uuid::Uuid::new_v4().to_string();
644        let signer1 = create_local_signer(&signer1_name);
645        let signer2 = create_local_signer(&signer2_name);
646
647        // Create signers
648        repo.create(signer1).await.unwrap();
649        repo.create(signer2).await.unwrap();
650
651        // List all signers
652        let signers = repo.list_all().await.unwrap();
653        assert!(signers.len() >= 2);
654
655        let ids: Vec<String> = signers.iter().map(|s| s.id.clone()).collect();
656        assert!(ids.contains(&signer1_name));
657        assert!(ids.contains(&signer2_name));
658    }
659
660    #[tokio::test]
661    #[ignore = "Requires active Redis instance"]
662    async fn test_count_signers() {
663        let repo = setup_test_repo().await;
664        let initial_count = repo.count().await.unwrap();
665
666        let signer_name = uuid::Uuid::new_v4().to_string();
667        let signer = create_local_signer(&signer_name);
668
669        // Create a signer
670        repo.create(signer).await.unwrap();
671
672        // Check count increased
673        let new_count = repo.count().await.unwrap();
674        assert!(new_count > initial_count);
675    }
676
677    #[tokio::test]
678    #[ignore = "Requires active Redis instance"]
679    async fn test_list_paginated_signers() {
680        let repo = setup_test_repo().await;
681        let signer1_name = uuid::Uuid::new_v4().to_string();
682        let signer2_name = uuid::Uuid::new_v4().to_string();
683        let signer1 = create_local_signer(&signer1_name);
684        let signer2 = create_local_signer(&signer2_name);
685
686        // Create signers
687        repo.create(signer1).await.unwrap();
688        repo.create(signer2).await.unwrap();
689
690        // Test pagination
691        let query = PaginationQuery {
692            page: 1,
693            per_page: 1,
694        };
695
696        let result = repo.list_paginated(query).await.unwrap();
697        assert_eq!(result.items.len(), 1);
698        assert!(result.total >= 2);
699        assert_eq!(result.page, 1);
700        assert_eq!(result.per_page, 1);
701    }
702
703    #[tokio::test]
704    #[ignore = "Requires active Redis instance"]
705    async fn test_duplicate_signer_creation() {
706        let repo = setup_test_repo().await;
707        let signer_name = uuid::Uuid::new_v4().to_string();
708        let signer = create_local_signer(&signer_name);
709
710        // Create the signer first time
711        repo.create(signer.clone()).await.unwrap();
712
713        // Try to create the same signer again
714        let result = repo.create(signer).await;
715        assert!(result.is_err());
716        assert!(matches!(
717            result.unwrap_err(),
718            RepositoryError::ConstraintViolation(_)
719        ));
720    }
721
722    #[tokio::test]
723    #[ignore = "Requires active Redis instance"]
724    async fn test_debug_implementation() {
725        let repo = setup_test_repo().await;
726        let debug_str = format!("{:?}", repo);
727        assert!(debug_str.contains("RedisSignerRepository"));
728        assert!(debug_str.contains("test"));
729    }
730
731    #[tokio::test]
732    #[ignore = "Requires active Redis instance"]
733    async fn test_error_handling_empty_id() {
734        let repo = setup_test_repo().await;
735
736        let result = repo.get_by_id("".to_string()).await;
737        assert!(result.is_err());
738        assert!(result
739            .unwrap_err()
740            .to_string()
741            .contains("ID cannot be empty"));
742    }
743
744    #[tokio::test]
745    #[ignore = "Requires active Redis instance"]
746    async fn test_create_signer_with_empty_id() {
747        let repo = setup_test_repo().await;
748        let signer = SignerRepoModel {
749            id: "".to_string(),
750            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
751                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
752            }),
753        };
754
755        let result = repo.create(signer).await;
756        assert!(result.is_err());
757        assert!(result
758            .unwrap_err()
759            .to_string()
760            .contains("ID cannot be empty"));
761    }
762
763    #[tokio::test]
764    #[ignore = "Requires active Redis instance"]
765    async fn test_update_nonexistent_signer() {
766        let repo = setup_test_repo().await;
767        let signer = create_local_signer("nonexistent-id");
768
769        let result = repo.update("nonexistent-id".to_string(), signer).await;
770        assert!(result.is_err());
771        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
772    }
773
774    #[tokio::test]
775    #[ignore = "Requires active Redis instance"]
776    async fn test_delete_nonexistent_signer() {
777        let repo = setup_test_repo().await;
778
779        let result = repo.delete_by_id("nonexistent-id".to_string()).await;
780        assert!(result.is_err());
781        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
782    }
783
784    #[tokio::test]
785    #[ignore = "Requires active Redis instance"]
786    async fn test_update_with_mismatched_id() {
787        let repo = setup_test_repo().await;
788        let signer_name = uuid::Uuid::new_v4().to_string();
789        let signer = create_local_signer(&signer_name);
790
791        // Create the signer first
792        repo.create(signer).await.unwrap();
793
794        // Try to update with different ID
795        let updated_signer = create_local_signer("different-id");
796        let result = repo.update(signer_name, updated_signer).await;
797        assert!(result.is_err());
798        assert!(result
799            .unwrap_err()
800            .to_string()
801            .contains("ID in data does not match"));
802    }
803
804    #[tokio::test]
805    #[ignore = "Requires active Redis instance"]
806    async fn test_has_entries() {
807        let repo = setup_test_repo().await;
808
809        let signer_id = uuid::Uuid::new_v4().to_string();
810        let signer = create_local_signer(&signer_id);
811        repo.create(signer.clone()).await.unwrap();
812        assert!(repo.has_entries().await.unwrap());
813    }
814
815    #[tokio::test]
816    #[ignore = "Requires active Redis instance"]
817    async fn test_drop_all_entries() {
818        let repo = setup_test_repo().await;
819        let signer_id = uuid::Uuid::new_v4().to_string();
820        let signer = create_local_signer(&signer_id);
821
822        repo.create(signer.clone()).await.unwrap();
823        assert!(repo.has_entries().await.unwrap());
824
825        repo.drop_all_entries().await.unwrap();
826        assert!(!repo.has_entries().await.unwrap());
827    }
828}