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: {e}"
186                )));
187            }
188        }
189
190        // Serialize signer (encryption happens automatically for human-readable formats)
191        let serialized = self.serialize_entity(&signer, |s| &s.id, "signer")?;
192
193        // Store signer
194        let result: Result<(), RedisError> = conn.set(&key, &serialized).await;
195        result.map_err(|e| {
196            error!(signer_id = %signer.id, error = %e, "failed to store signer");
197            RepositoryError::Other(format!("Failed to store signer: {e}"))
198        })?;
199
200        // Add to list
201        self.add_to_list(&signer.id).await?;
202
203        debug!(signer_id = %signer.id, "created signer");
204        Ok(signer)
205    }
206
207    async fn get_by_id(&self, id: String) -> Result<SignerRepoModel, RepositoryError> {
208        if id.is_empty() {
209            return Err(RepositoryError::InvalidData(
210                "Signer ID cannot be empty".to_string(),
211            ));
212        }
213
214        let key = self.signer_key(&id);
215        let mut conn = self.client.as_ref().clone();
216
217        let result: Result<Option<String>, RedisError> = conn.get(&key).await;
218        match result {
219            Ok(Some(data)) => {
220                // Deserialize signer (decryption happens automatically)
221                let signer = self.deserialize_entity::<SignerRepoModel>(&data, &id, "signer")?;
222                debug!(signer_id = %id, "retrieved signer");
223                Ok(signer)
224            }
225            Ok(None) => {
226                debug!(signer_id = %id, "signer not found");
227                Err(RepositoryError::NotFound(format!(
228                    "Signer with ID {id} not found"
229                )))
230            }
231            Err(e) => {
232                error!(signer_id = %id, error = %e, "failed to retrieve signer");
233                Err(RepositoryError::Other(format!(
234                    "Failed to retrieve signer: {e}"
235                )))
236            }
237        }
238    }
239
240    async fn update(
241        &self,
242        id: String,
243        signer: SignerRepoModel,
244    ) -> Result<SignerRepoModel, RepositoryError> {
245        if id.is_empty() {
246            return Err(RepositoryError::InvalidData(
247                "Signer ID cannot be empty".to_string(),
248            ));
249        }
250
251        if signer.id != id {
252            return Err(RepositoryError::InvalidData(
253                "Signer ID in data does not match provided ID".to_string(),
254            ));
255        }
256
257        let key = self.signer_key(&id);
258        let mut conn = self.client.as_ref().clone();
259
260        // Check if signer exists
261        let exists: Result<bool, RedisError> = conn.exists(&key).await;
262        match exists {
263            Ok(false) => {
264                return Err(RepositoryError::NotFound(format!(
265                    "Signer with ID {id} not found"
266                )));
267            }
268            Ok(true) => {
269                // Continue with update
270            }
271            Err(e) => {
272                error!(error = %e, "failed to check if signer exists");
273                return Err(RepositoryError::Other(format!(
274                    "Failed to check signer existence: {e}"
275                )));
276            }
277        }
278
279        // Serialize signer (encryption happens automatically for human-readable formats)
280        let serialized = self.serialize_entity(&signer, |s| &s.id, "signer")?;
281
282        // Update signer
283        let result: Result<(), RedisError> = conn.set(&key, &serialized).await;
284        result.map_err(|e| {
285            error!(signer_id = %id, error = %e, "failed to update signer");
286            RepositoryError::Other(format!("Failed to update signer: {e}"))
287        })?;
288
289        debug!(signer_id = %id, "updated signer");
290        Ok(signer)
291    }
292
293    async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
294        if id.is_empty() {
295            return Err(RepositoryError::InvalidData(
296                "Signer ID cannot be empty".to_string(),
297            ));
298        }
299
300        let key = self.signer_key(&id);
301        let mut conn = self.client.as_ref().clone();
302
303        // Check if signer exists
304        let exists: Result<bool, RedisError> = conn.exists(&key).await;
305        match exists {
306            Ok(false) => {
307                return Err(RepositoryError::NotFound(format!(
308                    "Signer with ID {id} not found"
309                )));
310            }
311            Ok(true) => {
312                // Continue with deletion
313            }
314            Err(e) => {
315                error!(error = %e, "failed to check if signer exists");
316                return Err(RepositoryError::Other(format!(
317                    "Failed to check signer existence: {e}"
318                )));
319            }
320        }
321
322        // Delete signer
323        let result: Result<i64, RedisError> = conn.del(&key).await;
324        result.map_err(|e| {
325            error!(signer_id = %id, error = %e, "failed to delete signer");
326            RepositoryError::Other(format!("Failed to delete signer: {e}"))
327        })?;
328
329        // Remove from list
330        self.remove_from_list(&id).await?;
331
332        debug!(signer_id = %id, "deleted signer");
333        Ok(())
334    }
335
336    async fn list_all(&self) -> Result<Vec<SignerRepoModel>, RepositoryError> {
337        let ids = self.get_all_ids().await?;
338
339        if ids.is_empty() {
340            debug!("No signers found");
341            return Ok(Vec::new());
342        }
343
344        let signers = self.get_signers_by_ids(&ids).await?;
345        debug!(
346            count = signers.results.len(),
347            "successfully fetched signers"
348        );
349        Ok(signers.results)
350    }
351
352    async fn list_paginated(
353        &self,
354        query: PaginationQuery,
355    ) -> Result<PaginatedResult<SignerRepoModel>, RepositoryError> {
356        if query.per_page == 0 {
357            return Err(RepositoryError::InvalidData(
358                "per_page must be greater than 0".to_string(),
359            ));
360        }
361
362        debug!(
363            "Listing paginated signers: page {}, per_page {}",
364            query.page, query.per_page
365        );
366
367        let all_ids: Vec<String> = self.get_all_ids().await?;
368        let total = all_ids.len() as u64;
369        let per_page = query.per_page as usize;
370        let page = query.page as usize;
371        let total_pages = all_ids.len().div_ceil(per_page);
372
373        if page > total_pages && !all_ids.is_empty() {
374            debug!(
375                "Requested page {} exceeds total pages {}",
376                page, total_pages
377            );
378            return Ok(PaginatedResult {
379                items: Vec::new(),
380                total,
381                page: query.page,
382                per_page: query.per_page,
383            });
384        }
385
386        let start_idx = (page - 1) * per_page;
387        let end_idx = std::cmp::min(start_idx + per_page, all_ids.len());
388
389        let page_ids = all_ids[start_idx..end_idx].to_vec();
390        let signers = self.get_signers_by_ids(&page_ids).await?;
391
392        debug!(
393            "Successfully retrieved {} signers for page {}",
394            signers.results.len(),
395            query.page
396        );
397        Ok(PaginatedResult {
398            items: signers.results.clone(),
399            total,
400            page: query.page,
401            per_page: query.per_page,
402        })
403    }
404
405    async fn count(&self) -> Result<usize, RepositoryError> {
406        let ids = self.get_all_ids().await?;
407        Ok(ids.len())
408    }
409
410    async fn has_entries(&self) -> Result<bool, RepositoryError> {
411        let mut conn = self.client.as_ref().clone();
412        let signer_list_key = self.signer_list_key();
413
414        debug!("Checking if signer entries exist");
415
416        let exists: bool = conn
417            .exists(&signer_list_key)
418            .await
419            .map_err(|e| self.map_redis_error(e, "has_entries_check"))?;
420
421        debug!(exists = %exists, "signer entries exist");
422        Ok(exists)
423    }
424
425    async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
426        let mut conn = self.client.as_ref().clone();
427        let signer_list_key = self.signer_list_key();
428
429        debug!("Dropping all signer entries");
430
431        // Get all signer IDs first
432        let signer_ids: Vec<String> = conn
433            .smembers(&signer_list_key)
434            .await
435            .map_err(|e| self.map_redis_error(e, "drop_all_entries_get_ids"))?;
436
437        if signer_ids.is_empty() {
438            debug!("No signer entries to drop");
439            return Ok(());
440        }
441
442        // Use pipeline for atomic operations
443        let mut pipe = redis::pipe();
444        pipe.atomic();
445
446        // Delete all individual signer entries
447        for signer_id in &signer_ids {
448            let signer_key = self.signer_key(signer_id);
449            pipe.del(&signer_key);
450        }
451
452        // Delete the signer list key
453        pipe.del(&signer_list_key);
454
455        pipe.exec_async(&mut conn)
456            .await
457            .map_err(|e| self.map_redis_error(e, "drop_all_entries_pipeline"))?;
458
459        debug!(count = %signer_ids.len(), "dropped signer entries");
460        Ok(())
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467    use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
468    use secrets::SecretVec;
469    use std::sync::Arc;
470
471    fn create_local_signer(id: &str) -> SignerRepoModel {
472        SignerRepoModel {
473            id: id.to_string(),
474            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
475                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
476            }),
477        }
478    }
479
480    async fn setup_test_repo() -> RedisSignerRepository {
481        let client =
482            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
483        let connection_manager = redis::aio::ConnectionManager::new(client)
484            .await
485            .expect("Failed to create connection manager");
486
487        RedisSignerRepository::new(Arc::new(connection_manager), "test".to_string())
488            .expect("Failed to create repository")
489    }
490
491    #[tokio::test]
492    #[ignore = "Requires active Redis instance"]
493    async fn test_new_repository_creation() {
494        let repo = setup_test_repo().await;
495        assert_eq!(repo.key_prefix, "test");
496    }
497
498    #[tokio::test]
499    #[ignore = "Requires active Redis instance"]
500    async fn test_new_repository_empty_prefix_fails() {
501        let client =
502            redis::Client::open("redis://127.0.0.1:6379/").expect("Failed to create Redis client");
503        let connection_manager = redis::aio::ConnectionManager::new(client)
504            .await
505            .expect("Failed to create connection manager");
506
507        let result = RedisSignerRepository::new(Arc::new(connection_manager), "".to_string());
508        assert!(result.is_err());
509        assert!(result
510            .unwrap_err()
511            .to_string()
512            .contains("key prefix cannot be empty"));
513    }
514
515    #[tokio::test]
516    #[ignore = "Requires active Redis instance"]
517    async fn test_key_generation() {
518        let repo = setup_test_repo().await;
519        let signer_key = repo.signer_key("test-id");
520        let list_key = repo.signer_list_key();
521
522        assert_eq!(signer_key, "test:signer:test-id");
523        assert_eq!(list_key, "test:signer_list");
524    }
525
526    #[tokio::test]
527    #[ignore = "Requires active Redis instance"]
528    async fn test_serialize_deserialize_signer() {
529        let repo = setup_test_repo().await;
530        let signer = create_local_signer("test-signer");
531
532        let serialized = repo.serialize_entity(&signer, |s| &s.id, "signer").unwrap();
533        let deserialized: SignerRepoModel = repo
534            .deserialize_entity(&serialized, &signer.id, "signer")
535            .unwrap();
536
537        assert_eq!(signer.id, deserialized.id);
538        assert!(matches!(signer.config, SignerConfigStorage::Local(_)));
539        assert!(matches!(deserialized.config, SignerConfigStorage::Local(_)));
540    }
541
542    #[tokio::test]
543    #[ignore = "Requires active Redis instance"]
544    async fn test_create_signer() {
545        let repo = setup_test_repo().await;
546        let signer_name = uuid::Uuid::new_v4().to_string();
547        let signer = create_local_signer(&signer_name);
548
549        let result = repo.create(signer).await;
550        assert!(result.is_ok());
551
552        let created_signer = result.unwrap();
553        assert_eq!(created_signer.id, signer_name);
554    }
555
556    #[tokio::test]
557    #[ignore = "Requires active Redis instance"]
558    async fn test_get_signer() {
559        let repo = setup_test_repo().await;
560        let signer_name = uuid::Uuid::new_v4().to_string();
561        let signer = create_local_signer(&signer_name);
562
563        // Create the signer first
564        repo.create(signer.clone()).await.unwrap();
565
566        // Get the signer
567        let retrieved = repo.get_by_id(signer_name.clone()).await.unwrap();
568        assert_eq!(retrieved.id, signer.id);
569        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
570    }
571
572    #[tokio::test]
573    #[ignore = "Requires active Redis instance"]
574    async fn test_get_nonexistent_signer() {
575        let repo = setup_test_repo().await;
576        let result = repo.get_by_id("nonexistent-id".to_string()).await;
577
578        assert!(result.is_err());
579        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
580    }
581
582    #[tokio::test]
583    #[ignore = "Requires active Redis instance"]
584    async fn test_update_signer() {
585        let repo = setup_test_repo().await;
586        let signer_name = uuid::Uuid::new_v4().to_string();
587        let signer = create_local_signer(&signer_name);
588
589        // Create the signer first
590        repo.create(signer.clone()).await.unwrap();
591
592        // Update the signer
593        let updated_signer = SignerRepoModel {
594            id: signer_name.clone(),
595            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
596                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[2; 32])),
597            }),
598        };
599
600        let result = repo.update(signer_name.clone(), updated_signer).await;
601        assert!(result.is_ok());
602
603        // Verify the update
604        let retrieved = repo.get_by_id(signer_name).await.unwrap();
605        assert!(matches!(retrieved.config, SignerConfigStorage::Local(_)));
606    }
607
608    #[tokio::test]
609    #[ignore = "Requires active Redis instance"]
610    async fn test_delete_signer() {
611        let repo = setup_test_repo().await;
612        let signer_name = uuid::Uuid::new_v4().to_string();
613        let signer = create_local_signer(&signer_name);
614
615        // Create the signer first
616        repo.create(signer).await.unwrap();
617
618        // Delete the signer
619        let result = repo.delete_by_id(signer_name.clone()).await;
620        assert!(result.is_ok());
621
622        // Verify deletion
623        let get_result = repo.get_by_id(signer_name).await;
624        assert!(get_result.is_err());
625        assert!(matches!(
626            get_result.unwrap_err(),
627            RepositoryError::NotFound(_)
628        ));
629    }
630
631    #[tokio::test]
632    #[ignore = "Requires active Redis instance"]
633    async fn test_list_all_signers() {
634        let repo = setup_test_repo().await;
635        let signer1_name = uuid::Uuid::new_v4().to_string();
636        let signer2_name = uuid::Uuid::new_v4().to_string();
637        let signer1 = create_local_signer(&signer1_name);
638        let signer2 = create_local_signer(&signer2_name);
639
640        // Create signers
641        repo.create(signer1).await.unwrap();
642        repo.create(signer2).await.unwrap();
643
644        // List all signers
645        let signers = repo.list_all().await.unwrap();
646        assert!(signers.len() >= 2);
647
648        let ids: Vec<String> = signers.iter().map(|s| s.id.clone()).collect();
649        assert!(ids.contains(&signer1_name));
650        assert!(ids.contains(&signer2_name));
651    }
652
653    #[tokio::test]
654    #[ignore = "Requires active Redis instance"]
655    async fn test_count_signers() {
656        let repo = setup_test_repo().await;
657        let initial_count = repo.count().await.unwrap();
658
659        let signer_name = uuid::Uuid::new_v4().to_string();
660        let signer = create_local_signer(&signer_name);
661
662        // Create a signer
663        repo.create(signer).await.unwrap();
664
665        // Check count increased
666        let new_count = repo.count().await.unwrap();
667        assert!(new_count > initial_count);
668    }
669
670    #[tokio::test]
671    #[ignore = "Requires active Redis instance"]
672    async fn test_list_paginated_signers() {
673        let repo = setup_test_repo().await;
674        let signer1_name = uuid::Uuid::new_v4().to_string();
675        let signer2_name = uuid::Uuid::new_v4().to_string();
676        let signer1 = create_local_signer(&signer1_name);
677        let signer2 = create_local_signer(&signer2_name);
678
679        // Create signers
680        repo.create(signer1).await.unwrap();
681        repo.create(signer2).await.unwrap();
682
683        // Test pagination
684        let query = PaginationQuery {
685            page: 1,
686            per_page: 1,
687        };
688
689        let result = repo.list_paginated(query).await.unwrap();
690        assert_eq!(result.items.len(), 1);
691        assert!(result.total >= 2);
692        assert_eq!(result.page, 1);
693        assert_eq!(result.per_page, 1);
694    }
695
696    #[tokio::test]
697    #[ignore = "Requires active Redis instance"]
698    async fn test_duplicate_signer_creation() {
699        let repo = setup_test_repo().await;
700        let signer_name = uuid::Uuid::new_v4().to_string();
701        let signer = create_local_signer(&signer_name);
702
703        // Create the signer first time
704        repo.create(signer.clone()).await.unwrap();
705
706        // Try to create the same signer again
707        let result = repo.create(signer).await;
708        assert!(result.is_err());
709        assert!(matches!(
710            result.unwrap_err(),
711            RepositoryError::ConstraintViolation(_)
712        ));
713    }
714
715    #[tokio::test]
716    #[ignore = "Requires active Redis instance"]
717    async fn test_debug_implementation() {
718        let repo = setup_test_repo().await;
719        let debug_str = format!("{:?}", repo);
720        assert!(debug_str.contains("RedisSignerRepository"));
721        assert!(debug_str.contains("test"));
722    }
723
724    #[tokio::test]
725    #[ignore = "Requires active Redis instance"]
726    async fn test_error_handling_empty_id() {
727        let repo = setup_test_repo().await;
728
729        let result = repo.get_by_id("".to_string()).await;
730        assert!(result.is_err());
731        assert!(result
732            .unwrap_err()
733            .to_string()
734            .contains("ID cannot be empty"));
735    }
736
737    #[tokio::test]
738    #[ignore = "Requires active Redis instance"]
739    async fn test_create_signer_with_empty_id() {
740        let repo = setup_test_repo().await;
741        let signer = SignerRepoModel {
742            id: "".to_string(),
743            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
744                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
745            }),
746        };
747
748        let result = repo.create(signer).await;
749        assert!(result.is_err());
750        assert!(result
751            .unwrap_err()
752            .to_string()
753            .contains("ID cannot be empty"));
754    }
755
756    #[tokio::test]
757    #[ignore = "Requires active Redis instance"]
758    async fn test_update_nonexistent_signer() {
759        let repo = setup_test_repo().await;
760        let signer = create_local_signer("nonexistent-id");
761
762        let result = repo.update("nonexistent-id".to_string(), signer).await;
763        assert!(result.is_err());
764        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
765    }
766
767    #[tokio::test]
768    #[ignore = "Requires active Redis instance"]
769    async fn test_delete_nonexistent_signer() {
770        let repo = setup_test_repo().await;
771
772        let result = repo.delete_by_id("nonexistent-id".to_string()).await;
773        assert!(result.is_err());
774        assert!(matches!(result.unwrap_err(), RepositoryError::NotFound(_)));
775    }
776
777    #[tokio::test]
778    #[ignore = "Requires active Redis instance"]
779    async fn test_update_with_mismatched_id() {
780        let repo = setup_test_repo().await;
781        let signer_name = uuid::Uuid::new_v4().to_string();
782        let signer = create_local_signer(&signer_name);
783
784        // Create the signer first
785        repo.create(signer).await.unwrap();
786
787        // Try to update with different ID
788        let updated_signer = create_local_signer("different-id");
789        let result = repo.update(signer_name, updated_signer).await;
790        assert!(result.is_err());
791        assert!(result
792            .unwrap_err()
793            .to_string()
794            .contains("ID in data does not match"));
795    }
796
797    #[tokio::test]
798    #[ignore = "Requires active Redis instance"]
799    async fn test_has_entries() {
800        let repo = setup_test_repo().await;
801
802        let signer_id = uuid::Uuid::new_v4().to_string();
803        let signer = create_local_signer(&signer_id);
804        repo.create(signer.clone()).await.unwrap();
805        assert!(repo.has_entries().await.unwrap());
806    }
807
808    #[tokio::test]
809    #[ignore = "Requires active Redis instance"]
810    async fn test_drop_all_entries() {
811        let repo = setup_test_repo().await;
812        let signer_id = uuid::Uuid::new_v4().to_string();
813        let signer = create_local_signer(&signer_id);
814
815        repo.create(signer.clone()).await.unwrap();
816        assert!(repo.has_entries().await.unwrap());
817
818        repo.drop_all_entries().await.unwrap();
819        assert!(!repo.has_entries().await.unwrap());
820    }
821}