1use 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 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 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 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 let serialized = EncryptionContext::with_aad_sync(key.clone(), || {
195 self.serialize_entity(&signer, |s| &s.id, "signer")
196 })?;
197
198 let _: () = conn
200 .set(&key, &serialized)
201 .await
202 .map_err(|e| self.map_redis_error(e, "create_signer_set"))?;
203
204 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 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 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 let serialized = EncryptionContext::with_aad_sync(key.clone(), || {
282 self.serialize_entity(&signer, |s| &s.id, "signer")
283 })?;
284
285 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 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 let _: i64 = conn
321 .del(&key)
322 .await
323 .map_err(|e| self.map_redis_error(e, "delete_signer"))?;
324
325 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 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 let mut pipe = redis::pipe();
444 pipe.atomic();
445
446 for signer_id in &signer_ids {
448 let signer_key = self.signer_key(signer_id);
449 pipe.del(&signer_key);
450 }
451
452 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 repo.create(signer.clone()).await.unwrap();
583
584 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 repo.create(signer.clone()).await.unwrap();
609
610 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 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 repo.create(signer).await.unwrap();
635
636 let result = repo.delete_by_id(signer_name.clone()).await;
638 assert!(result.is_ok());
639
640 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 repo.create(signer1).await.unwrap();
660 repo.create(signer2).await.unwrap();
661
662 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 repo.create(signer).await.unwrap();
682
683 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 repo.create(signer1).await.unwrap();
699 repo.create(signer2).await.unwrap();
700
701 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 repo.create(signer.clone()).await.unwrap();
723
724 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 repo.create(signer).await.unwrap();
804
805 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}