1use std::collections::HashMap;
13use std::sync::{Arc, RwLock};
14
15use async_trait::async_trait;
16
17use crate::dst::{DeterministicRng, FaultConfig, FaultInjector, SimClock, SimConfig};
18
19use super::backend::StorageBackend;
20use super::entity::{Entity, EntityType};
21use super::error::{StorageError, StorageResult};
22
23#[derive(Debug, Clone)]
34pub struct SimStorageBackend {
35 storage: Arc<RwLock<HashMap<String, Entity>>>,
37 fault_injector: Arc<FaultInjector>,
39 clock: SimClock,
41 #[allow(dead_code)]
43 rng: Arc<RwLock<DeterministicRng>>,
44}
45
46impl SimStorageBackend {
47 #[must_use]
49 pub fn new(config: SimConfig) -> Self {
50 let mut rng = DeterministicRng::new(config.seed());
51 let fault_rng = rng.fork();
52
53 Self {
54 storage: Arc::new(RwLock::new(HashMap::new())),
55 fault_injector: Arc::new(FaultInjector::new(fault_rng)),
56 clock: SimClock::new(),
57 rng: Arc::new(RwLock::new(rng)),
58 }
59 }
60
61 #[must_use]
67 pub fn with_faults(mut self, config: FaultConfig) -> Self {
68 Arc::get_mut(&mut self.fault_injector)
71 .expect("cannot add faults after backend is shared")
72 .register(config);
73 self
74 }
75
76 #[must_use]
78 pub fn clock(&self) -> &SimClock {
79 &self.clock
80 }
81
82 #[must_use]
84 pub fn fault_injector(&self) -> &Arc<FaultInjector> {
85 &self.fault_injector
86 }
87
88 fn maybe_inject_fault(&self, operation: &str) -> StorageResult<()> {
90 if let Some(fault_type) = self.fault_injector.should_inject(operation) {
91 Err(StorageError::simulated_fault(format!(
92 "{:?} during {}",
93 fault_type, operation
94 )))
95 } else {
96 Ok(())
97 }
98 }
99
100 #[must_use]
102 pub fn entity_count(&self) -> usize {
103 self.storage.read().unwrap().len()
104 }
105}
106
107#[async_trait]
108impl StorageBackend for SimStorageBackend {
109 async fn store_entity(&self, entity: &Entity) -> StorageResult<String> {
110 self.maybe_inject_fault("store")?;
112
113 assert!(!entity.id.is_empty(), "entity must have id");
115
116 let mut storage = self.storage.write().unwrap();
117 storage.insert(entity.id.clone(), entity.clone());
118
119 Ok(entity.id.clone())
120 }
121
122 async fn get_entity(&self, id: &str) -> StorageResult<Option<Entity>> {
123 self.maybe_inject_fault("get")?;
125
126 let storage = self.storage.read().unwrap();
127 Ok(storage.get(id).cloned())
128 }
129
130 async fn delete_entity(&self, id: &str) -> StorageResult<bool> {
131 self.maybe_inject_fault("delete")?;
133
134 let mut storage = self.storage.write().unwrap();
135 Ok(storage.remove(id).is_some())
136 }
137
138 async fn search(&self, query: &str, limit: usize) -> StorageResult<Vec<Entity>> {
139 self.maybe_inject_fault("search")?;
141
142 let storage = self.storage.read().unwrap();
143 let query_lower = query.to_lowercase();
144
145 let mut results: Vec<Entity> = storage
146 .values()
147 .filter(|e| {
148 e.name.to_lowercase().contains(&query_lower)
149 || e.content.to_lowercase().contains(&query_lower)
150 })
151 .cloned()
152 .collect();
153
154 results.sort_by(|a, b| a.name.cmp(&b.name));
156
157 results.truncate(limit);
159
160 Ok(results)
161 }
162
163 async fn list_entities(
164 &self,
165 entity_type: Option<EntityType>,
166 limit: usize,
167 offset: usize,
168 ) -> StorageResult<Vec<Entity>> {
169 self.maybe_inject_fault("list")?;
171
172 let storage = self.storage.read().unwrap();
173
174 let mut results: Vec<Entity> = storage
175 .values()
176 .filter(|e| entity_type.map_or(true, |t| e.entity_type == t))
177 .cloned()
178 .collect();
179
180 results.sort_by(|a, b| a.created_at.cmp(&b.created_at));
182
183 let results: Vec<Entity> = results.into_iter().skip(offset).take(limit).collect();
185
186 Ok(results)
187 }
188
189 async fn count_entities(&self, entity_type: Option<EntityType>) -> StorageResult<usize> {
190 self.maybe_inject_fault("count")?;
192
193 let storage = self.storage.read().unwrap();
194
195 let count = storage
196 .values()
197 .filter(|e| entity_type.map_or(true, |t| e.entity_type == t))
198 .count();
199
200 Ok(count)
201 }
202
203 async fn clear(&self) -> StorageResult<()> {
204 self.maybe_inject_fault("clear")?;
206
207 let mut storage = self.storage.write().unwrap();
208 storage.clear();
209 Ok(())
210 }
211}
212
213#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[tokio::test]
226 async fn test_store_and_get() {
227 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
228
229 let entity = Entity::new(
230 EntityType::Person,
231 "Alice".to_string(),
232 "Friend".to_string(),
233 );
234 let id = entity.id.clone();
235
236 backend.store_entity(&entity).await.unwrap();
237
238 let retrieved = backend.get_entity(&id).await.unwrap();
239 assert!(retrieved.is_some());
240
241 let retrieved = retrieved.unwrap();
242 assert_eq!(retrieved.id, id);
243 assert_eq!(retrieved.name, "Alice");
244 assert_eq!(retrieved.entity_type, EntityType::Person);
245 }
246
247 #[tokio::test]
248 async fn test_get_nonexistent() {
249 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
250
251 let result = backend.get_entity("nonexistent").await.unwrap();
252 assert!(result.is_none());
253 }
254
255 #[tokio::test]
256 async fn test_store_updates_existing() {
257 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
258
259 let mut entity = Entity::new(EntityType::Note, "Note".to_string(), "Original".to_string());
260 let id = entity.id.clone();
261
262 backend.store_entity(&entity).await.unwrap();
263
264 entity.update_content("Updated".to_string());
266 backend.store_entity(&entity).await.unwrap();
267
268 let retrieved = backend.get_entity(&id).await.unwrap().unwrap();
269 assert_eq!(retrieved.content, "Updated");
270
271 assert_eq!(backend.entity_count(), 1);
273 }
274
275 #[tokio::test]
276 async fn test_delete() {
277 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
278
279 let entity = Entity::new(EntityType::Task, "Task".to_string(), "Do it".to_string());
280 let id = entity.id.clone();
281
282 backend.store_entity(&entity).await.unwrap();
283 assert_eq!(backend.entity_count(), 1);
284
285 let deleted = backend.delete_entity(&id).await.unwrap();
286 assert!(deleted);
287 assert_eq!(backend.entity_count(), 0);
288
289 let deleted = backend.delete_entity(&id).await.unwrap();
291 assert!(!deleted);
292 }
293
294 #[tokio::test]
295 async fn test_clear() {
296 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
297
298 backend
299 .store_entity(&Entity::new(
300 EntityType::Note,
301 "A".to_string(),
302 "a".to_string(),
303 ))
304 .await
305 .unwrap();
306 backend
307 .store_entity(&Entity::new(
308 EntityType::Note,
309 "B".to_string(),
310 "b".to_string(),
311 ))
312 .await
313 .unwrap();
314
315 assert_eq!(backend.entity_count(), 2);
316
317 backend.clear().await.unwrap();
318
319 assert_eq!(backend.entity_count(), 0);
320 }
321
322 #[tokio::test]
327 async fn test_search_by_name() {
328 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
329
330 backend
331 .store_entity(&Entity::new(
332 EntityType::Person,
333 "Alice".to_string(),
334 "Friend".to_string(),
335 ))
336 .await
337 .unwrap();
338 backend
339 .store_entity(&Entity::new(
340 EntityType::Person,
341 "Bob".to_string(),
342 "Colleague".to_string(),
343 ))
344 .await
345 .unwrap();
346 backend
347 .store_entity(&Entity::new(
348 EntityType::Person,
349 "Charlie".to_string(),
350 "Neighbor".to_string(),
351 ))
352 .await
353 .unwrap();
354
355 let results = backend.search("alice", 10).await.unwrap();
356 assert_eq!(results.len(), 1);
357 assert_eq!(results[0].name, "Alice");
358 }
359
360 #[tokio::test]
361 async fn test_search_by_content() {
362 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
363
364 backend
365 .store_entity(&Entity::new(
366 EntityType::Note,
367 "Work".to_string(),
368 "Meeting with team".to_string(),
369 ))
370 .await
371 .unwrap();
372 backend
373 .store_entity(&Entity::new(
374 EntityType::Note,
375 "Personal".to_string(),
376 "Call mom".to_string(),
377 ))
378 .await
379 .unwrap();
380
381 let results = backend.search("meeting", 10).await.unwrap();
382 assert_eq!(results.len(), 1);
383 assert_eq!(results[0].name, "Work");
384 }
385
386 #[tokio::test]
387 async fn test_search_case_insensitive() {
388 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
389
390 backend
391 .store_entity(&Entity::new(
392 EntityType::Topic,
393 "Rust".to_string(),
394 "Programming language".to_string(),
395 ))
396 .await
397 .unwrap();
398
399 let results = backend.search("RUST", 10).await.unwrap();
400 assert_eq!(results.len(), 1);
401
402 let results = backend.search("rust", 10).await.unwrap();
403 assert_eq!(results.len(), 1);
404 }
405
406 #[tokio::test]
407 async fn test_search_limit() {
408 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
409
410 for i in 0..10 {
411 backend
412 .store_entity(&Entity::new(
413 EntityType::Note,
414 format!("Note {}", i),
415 "common content".to_string(),
416 ))
417 .await
418 .unwrap();
419 }
420
421 let results = backend.search("common", 3).await.unwrap();
422 assert_eq!(results.len(), 3);
423 }
424
425 #[tokio::test]
430 async fn test_list_all() {
431 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
432
433 backend
434 .store_entity(&Entity::new(
435 EntityType::Person,
436 "Alice".to_string(),
437 "a".to_string(),
438 ))
439 .await
440 .unwrap();
441 backend
442 .store_entity(&Entity::new(
443 EntityType::Project,
444 "Umi".to_string(),
445 "b".to_string(),
446 ))
447 .await
448 .unwrap();
449
450 let results = backend.list_entities(None, 100, 0).await.unwrap();
451 assert_eq!(results.len(), 2);
452 }
453
454 #[tokio::test]
455 async fn test_list_by_type() {
456 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
457
458 backend
459 .store_entity(&Entity::new(
460 EntityType::Person,
461 "Alice".to_string(),
462 "a".to_string(),
463 ))
464 .await
465 .unwrap();
466 backend
467 .store_entity(&Entity::new(
468 EntityType::Person,
469 "Bob".to_string(),
470 "b".to_string(),
471 ))
472 .await
473 .unwrap();
474 backend
475 .store_entity(&Entity::new(
476 EntityType::Project,
477 "Umi".to_string(),
478 "c".to_string(),
479 ))
480 .await
481 .unwrap();
482
483 let results = backend
484 .list_entities(Some(EntityType::Person), 100, 0)
485 .await
486 .unwrap();
487 assert_eq!(results.len(), 2);
488
489 let results = backend
490 .list_entities(Some(EntityType::Project), 100, 0)
491 .await
492 .unwrap();
493 assert_eq!(results.len(), 1);
494 }
495
496 #[tokio::test]
497 async fn test_list_with_offset() {
498 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
499
500 for i in 0..5 {
501 backend
503 .store_entity(&Entity::new(
504 EntityType::Note,
505 format!("Note {}", i),
506 "content".to_string(),
507 ))
508 .await
509 .unwrap();
510 }
511
512 let all = backend.list_entities(None, 100, 0).await.unwrap();
513 assert_eq!(all.len(), 5);
514
515 let offset_2 = backend.list_entities(None, 100, 2).await.unwrap();
516 assert_eq!(offset_2.len(), 3);
517 }
518
519 #[tokio::test]
520 async fn test_count() {
521 let backend = SimStorageBackend::new(SimConfig::with_seed(42));
522
523 backend
524 .store_entity(&Entity::new(
525 EntityType::Person,
526 "Alice".to_string(),
527 "a".to_string(),
528 ))
529 .await
530 .unwrap();
531 backend
532 .store_entity(&Entity::new(
533 EntityType::Person,
534 "Bob".to_string(),
535 "b".to_string(),
536 ))
537 .await
538 .unwrap();
539 backend
540 .store_entity(&Entity::new(
541 EntityType::Task,
542 "Task".to_string(),
543 "c".to_string(),
544 ))
545 .await
546 .unwrap();
547
548 assert_eq!(backend.count_entities(None).await.unwrap(), 3);
549 assert_eq!(
550 backend
551 .count_entities(Some(EntityType::Person))
552 .await
553 .unwrap(),
554 2
555 );
556 assert_eq!(
557 backend
558 .count_entities(Some(EntityType::Task))
559 .await
560 .unwrap(),
561 1
562 );
563 assert_eq!(
564 backend
565 .count_entities(Some(EntityType::Project))
566 .await
567 .unwrap(),
568 0
569 );
570 }
571}
572
573#[cfg(test)]
578mod dst_tests {
579 use super::*;
580 use crate::dst::FaultType;
581
582 #[tokio::test]
583 async fn test_fault_injection_on_store() {
584 let backend = SimStorageBackend::new(SimConfig::with_seed(42)).with_faults(
585 FaultConfig::new(FaultType::StorageWriteFail, 1.0) .with_filter("store"),
587 );
588
589 let entity = Entity::new(EntityType::Note, "Test".to_string(), "content".to_string());
590 let result = backend.store_entity(&entity).await;
591
592 assert!(result.is_err());
593 assert!(matches!(result, Err(StorageError::SimulatedFault { .. })));
594 }
595
596 #[tokio::test]
597 async fn test_fault_injection_on_get() {
598 let backend = SimStorageBackend::new(SimConfig::with_seed(42))
599 .with_faults(FaultConfig::new(FaultType::StorageReadFail, 1.0).with_filter("get"));
600
601 let entity = Entity::new(EntityType::Note, "Test".to_string(), "content".to_string());
603 backend.store_entity(&entity).await.unwrap();
604
605 let result = backend.get_entity(&entity.id).await;
607 assert!(result.is_err());
608 }
609
610 #[tokio::test]
611 async fn test_fault_injection_probability() {
612 let backend = SimStorageBackend::new(SimConfig::with_seed(42))
614 .with_faults(FaultConfig::new(FaultType::StorageWriteFail, 0.5).with_filter("store"));
615
616 let mut successes = 0;
617 let mut failures = 0;
618
619 for i in 0..100 {
620 let entity = Entity::new(
621 EntityType::Note,
622 format!("Test {}", i),
623 "content".to_string(),
624 );
625 match backend.store_entity(&entity).await {
626 Ok(_) => successes += 1,
627 Err(_) => failures += 1,
628 }
629 }
630
631 assert!(successes > 0, "expected some successes");
634 assert!(failures > 0, "expected some failures");
635 }
636
637 #[tokio::test]
638 async fn test_fault_injection_stats() {
639 let backend = SimStorageBackend::new(SimConfig::with_seed(42))
640 .with_faults(FaultConfig::new(FaultType::StorageWriteFail, 1.0).with_filter("store"));
641
642 let entity = Entity::new(EntityType::Note, "Test".to_string(), "content".to_string());
643
644 for _ in 0..5 {
646 let _ = backend.store_entity(&entity).await;
647 }
648
649 let total = backend.fault_injector().total_injections();
650 assert_eq!(total, 5);
651 }
652}
653
654#[cfg(test)]
659mod property_tests {
660 use super::*;
661 use crate::dst::{PropertyTest, PropertyTestable, TimeAdvanceConfig};
662
663 #[derive(Debug, Clone)]
665 enum StorageOp {
666 Store {
667 entity_type: EntityType,
668 name: String,
669 },
670 Get {
671 id: String,
672 },
673 Delete {
674 id: String,
675 },
676 Search {
677 query: String,
678 },
679 List {
680 entity_type: Option<EntityType>,
681 },
682 }
683
684 struct StorageWrapper {
686 backend: SimStorageBackend,
687 known_ids: Vec<String>,
688 }
689
690 impl PropertyTestable for StorageWrapper {
691 type Operation = StorageOp;
692
693 fn generate_operation(&self, rng: &mut DeterministicRng) -> Self::Operation {
694 let op_type = rng.next_usize(0, 4);
695
696 match op_type {
697 0 => {
698 let types = EntityType::all();
700 let type_idx = rng.next_usize(0, types.len() - 1);
701 StorageOp::Store {
702 entity_type: types[type_idx],
703 name: format!("entity_{}", rng.next_usize(0, 999)),
704 }
705 }
706 1 => {
707 let id = if !self.known_ids.is_empty() && rng.next_bool(0.7) {
709 let idx = rng.next_usize(0, self.known_ids.len() - 1);
710 self.known_ids[idx].clone()
711 } else {
712 format!("unknown_{}", rng.next_usize(0, 99))
713 };
714 StorageOp::Get { id }
715 }
716 2 => {
717 let id = if !self.known_ids.is_empty() && rng.next_bool(0.5) {
719 let idx = rng.next_usize(0, self.known_ids.len() - 1);
720 self.known_ids[idx].clone()
721 } else {
722 format!("unknown_{}", rng.next_usize(0, 99))
723 };
724 StorageOp::Delete { id }
725 }
726 3 => {
727 StorageOp::Search {
729 query: format!("entity_{}", rng.next_usize(0, 9)),
730 }
731 }
732 _ => {
733 let types = EntityType::all();
735 let entity_type = if rng.next_bool(0.3) {
736 Some(types[rng.next_usize(0, types.len() - 1)])
737 } else {
738 None
739 };
740 StorageOp::List { entity_type }
741 }
742 }
743 }
744
745 fn apply_operation(&mut self, op: &Self::Operation, _clock: &SimClock) {
746 let rt = tokio::runtime::Builder::new_current_thread()
747 .enable_all()
748 .build()
749 .unwrap();
750
751 rt.block_on(async {
752 match op {
753 StorageOp::Store { entity_type, name } => {
754 let entity = Entity::new(*entity_type, name.clone(), "content".to_string());
755 if self.backend.store_entity(&entity).await.is_ok() {
756 if !self.known_ids.contains(&entity.id) {
757 self.known_ids.push(entity.id);
758 }
759 }
760 }
761 StorageOp::Get { id } => {
762 let _ = self.backend.get_entity(id).await;
763 }
764 StorageOp::Delete { id } => {
765 if self.backend.delete_entity(id).await.unwrap_or(false) {
766 self.known_ids.retain(|i| i != id);
767 }
768 }
769 StorageOp::Search { query } => {
770 let _ = self.backend.search(query, 10).await;
771 }
772 StorageOp::List { entity_type } => {
773 let _ = self.backend.list_entities(*entity_type, 10, 0).await;
774 }
775 }
776 });
777 }
778
779 fn check_invariants(&self) -> Result<(), String> {
780 let rt = tokio::runtime::Builder::new_current_thread()
781 .enable_all()
782 .build()
783 .unwrap();
784
785 rt.block_on(async {
786 let count = self
788 .backend
789 .count_entities(None)
790 .await
791 .map_err(|e| e.to_string())?;
792
793 if count != self.known_ids.len() {
797 return Err(format!(
800 "count {} != known_ids.len() {}",
801 count,
802 self.known_ids.len()
803 ));
804 }
805
806 Ok(())
807 })
808 }
809
810 fn describe_state(&self) -> String {
811 format!(
812 "SimStorageBackend {{ entities: {}, known_ids: {} }}",
813 self.backend.entity_count(),
814 self.known_ids.len()
815 )
816 }
817 }
818
819 #[test]
820 fn test_property_invariants() {
821 let wrapper = StorageWrapper {
822 backend: SimStorageBackend::new(SimConfig::with_seed(42)),
823 known_ids: Vec::new(),
824 };
825
826 PropertyTest::new(42)
827 .with_max_operations(200)
828 .with_time_advance(TimeAdvanceConfig::none())
829 .run_and_assert(wrapper);
830 }
831
832 #[test]
833 fn test_property_multi_seed() {
834 for seed in [0, 1, 42, 12345, 99999] {
835 let wrapper = StorageWrapper {
836 backend: SimStorageBackend::new(SimConfig::with_seed(seed)),
837 known_ids: Vec::new(),
838 };
839
840 PropertyTest::new(seed)
841 .with_max_operations(100)
842 .with_time_advance(TimeAdvanceConfig::none())
843 .run_and_assert(wrapper);
844 }
845 }
846}