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