Skip to main content

rustodo/storage/
memory.rs

1//! In-memory storage implementation for testing
2
3use anyhow::Result;
4use std::cell::RefCell;
5
6use super::Storage;
7use crate::models::Task;
8
9/// In-memory storage implementation
10///
11/// Stores tasks in memory without any file I/O, making tests fast and isolated.
12/// Uses `RefCell` for interior mutability since `Storage` trait methods take `&self`.
13///
14/// # Examples
15///
16/// ```
17/// use rustodo::storage::memory::InMemoryStorage;
18///
19/// let storage = InMemoryStorage::default();
20/// // Use in tests without touching filesystem
21/// ```
22#[derive(Default)]
23pub struct InMemoryStorage {
24    tasks: RefCell<Vec<Task>>,
25}
26
27#[allow(dead_code)]
28impl InMemoryStorage {
29    /// Create in-memory storage pre-populated with tasks
30    ///
31    /// Useful for setting up test fixtures.
32    pub fn with_tasks(tasks: Vec<Task>) -> Self {
33        Self {
34            tasks: RefCell::new(tasks),
35        }
36    }
37
38    /// Get current number of tasks (for assertions)
39    pub fn len(&self) -> usize {
40        self.tasks.borrow().len()
41    }
42
43    /// Check if storage is empty (for assertions)
44    pub fn is_empty(&self) -> bool {
45        self.tasks.borrow().is_empty()
46    }
47}
48
49impl Storage for InMemoryStorage {
50    fn load(&self) -> Result<Vec<Task>> {
51        Ok(self.tasks.borrow().clone())
52    }
53
54    fn save(&self, tasks: &[Task]) -> Result<()> {
55        *self.tasks.borrow_mut() = tasks.to_vec();
56        Ok(())
57    }
58
59    fn location(&self) -> String {
60        "memory".to_string()
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::models::Priority;
68
69    #[test]
70    fn test_memory_storage_starts_empty() {
71        let storage = InMemoryStorage::default();
72
73        assert_eq!(storage.len(), 0);
74        assert!(storage.is_empty());
75        assert_eq!(storage.load().unwrap().len(), 0);
76    }
77
78    #[test]
79    fn test_memory_storage_save_and_load() {
80        let storage = InMemoryStorage::default();
81
82        // Save
83        let tasks = vec![
84            Task::new(
85                "Task 1".to_string(),
86                Priority::High,
87                vec![],
88                None,
89                None,
90                None,
91            ),
92            Task::new(
93                "Task 2".to_string(),
94                Priority::Low,
95                vec![],
96                None,
97                None,
98                None,
99            ),
100        ];
101        storage.save(&tasks).unwrap();
102
103        // Load
104        let loaded = storage.load().unwrap();
105        assert_eq!(loaded.len(), 2);
106        assert_eq!(loaded[0].text, "Task 1");
107        assert_eq!(loaded[1].text, "Task 2");
108    }
109
110    #[test]
111    fn test_memory_storage_with_tasks() {
112        let tasks = vec![Task::new(
113            "Existing".to_string(),
114            Priority::Medium,
115            vec![],
116            None,
117            None,
118            None,
119        )];
120
121        let storage = InMemoryStorage::with_tasks(tasks);
122
123        assert_eq!(storage.len(), 1);
124        assert!(!storage.is_empty());
125
126        let loaded = storage.load().unwrap();
127        assert_eq!(loaded[0].text, "Existing");
128    }
129
130    #[test]
131    fn test_memory_storage_overwrite() {
132        let storage = InMemoryStorage::default();
133
134        // Save first batch
135        let tasks1 = vec![Task::new(
136            "Task 1".to_string(),
137            Priority::Medium,
138            vec![],
139            None,
140            None,
141            None,
142        )];
143        storage.save(&tasks1).unwrap();
144        assert_eq!(storage.len(), 1);
145
146        // Save second batch (overwrites)
147        let tasks2 = vec![
148            Task::new(
149                "Task 2".to_string(),
150                Priority::High,
151                vec![],
152                None,
153                None,
154                None,
155            ),
156            Task::new(
157                "Task 3".to_string(),
158                Priority::Low,
159                vec![],
160                None,
161                None,
162                None,
163            ),
164        ];
165        storage.save(&tasks2).unwrap();
166
167        // Should have new tasks only
168        assert_eq!(storage.len(), 2);
169        let loaded = storage.load().unwrap();
170        assert_eq!(loaded[0].text, "Task 2");
171        assert_eq!(loaded[1].text, "Task 3");
172    }
173
174    #[test]
175    fn test_memory_storage_location() {
176        let storage = InMemoryStorage::default();
177        assert_eq!(storage.location(), "memory");
178    }
179}