1use ryo_storage::{StorageResult, TxLog};
29use std::path::Path;
30
31pub trait Storage: Send + Sync {
35 fn init(&mut self) -> StorageResult<()>;
37
38 fn save(&mut self, log: &TxLog) -> StorageResult<String>;
40
41 fn load(&self, session_id: &str) -> StorageResult<TxLog>;
43
44 fn list_sessions(&self) -> StorageResult<Vec<String>>;
46
47 fn sessions_for_project(&self, project_path: &Path) -> StorageResult<Vec<String>>;
49
50 fn delete(&mut self, session_id: &str) -> StorageResult<()>;
52}
53
54#[derive(Default)]
56pub struct InMemoryStorage {
57 sessions: std::collections::HashMap<String, TxLog>,
58}
59
60impl InMemoryStorage {
61 pub fn new() -> Self {
62 Self::default()
63 }
64}
65
66impl Storage for InMemoryStorage {
67 fn init(&mut self) -> StorageResult<()> {
68 Ok(())
69 }
70
71 fn save(&mut self, log: &TxLog) -> StorageResult<String> {
72 let id = uuid::Uuid::new_v4().to_string();
73 self.sessions.insert(id.clone(), log.clone());
74 Ok(id)
75 }
76
77 fn load(&self, session_id: &str) -> StorageResult<TxLog> {
78 self.sessions
79 .get(session_id)
80 .cloned()
81 .ok_or_else(|| ryo_storage::StorageError::SessionNotFound(session_id.to_string()))
82 }
83
84 fn list_sessions(&self) -> StorageResult<Vec<String>> {
85 Ok(self.sessions.keys().cloned().collect())
86 }
87
88 fn sessions_for_project(&self, _project_path: &Path) -> StorageResult<Vec<String>> {
89 self.list_sessions()
91 }
92
93 fn delete(&mut self, session_id: &str) -> StorageResult<()> {
94 self.sessions
95 .remove(session_id)
96 .map(|_| ())
97 .ok_or_else(|| ryo_storage::StorageError::SessionNotFound(session_id.to_string()))
98 }
99}
100
101#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
114 fn test_new() {
115 let storage = InMemoryStorage::new();
116 assert!(storage.sessions.is_empty());
117 }
118
119 #[test]
120 fn test_default() {
121 let storage = InMemoryStorage::default();
122 assert!(storage.sessions.is_empty());
123 }
124
125 #[test]
126 fn test_init() {
127 let mut storage = InMemoryStorage::new();
128 assert!(storage.init().is_ok());
129 }
130
131 #[test]
132 fn test_save_and_load() {
133 let mut storage = InMemoryStorage::new();
134 let log = TxLog::new();
135
136 let id = storage.save(&log).unwrap();
137 assert!(!id.is_empty());
138
139 let loaded = storage.load(&id).unwrap();
140 assert_eq!(loaded.entries().len(), log.entries().len());
141 }
142
143 #[test]
144 fn test_load_nonexistent() {
145 let storage = InMemoryStorage::new();
146 let result = storage.load("nonexistent-id");
147 assert!(result.is_err());
148 }
149
150 #[test]
151 fn test_list_sessions_empty() {
152 let storage = InMemoryStorage::new();
153 let sessions = storage.list_sessions().unwrap();
154 assert!(sessions.is_empty());
155 }
156
157 #[test]
158 fn test_list_sessions_with_data() {
159 let mut storage = InMemoryStorage::new();
160 let log1 = TxLog::new();
161 let log2 = TxLog::new();
162
163 let id1 = storage.save(&log1).unwrap();
164 let id2 = storage.save(&log2).unwrap();
165
166 let sessions = storage.list_sessions().unwrap();
167 assert_eq!(sessions.len(), 2);
168 assert!(sessions.contains(&id1));
169 assert!(sessions.contains(&id2));
170 }
171
172 #[test]
173 fn test_sessions_for_project() {
174 let mut storage = InMemoryStorage::new();
175 let log = TxLog::new();
176 storage.save(&log).unwrap();
177
178 let sessions = storage
180 .sessions_for_project(Path::new("/some/project"))
181 .unwrap();
182 assert_eq!(sessions.len(), 1);
183 }
184
185 #[test]
186 fn test_delete() {
187 let mut storage = InMemoryStorage::new();
188 let log = TxLog::new();
189 let id = storage.save(&log).unwrap();
190
191 assert!(storage.load(&id).is_ok());
192 assert!(storage.delete(&id).is_ok());
193 assert!(storage.load(&id).is_err());
194 }
195
196 #[test]
197 fn test_delete_nonexistent() {
198 let mut storage = InMemoryStorage::new();
199 let result = storage.delete("nonexistent-id");
200 assert!(result.is_err());
201 }
202
203 #[test]
204 fn test_multiple_saves() {
205 let mut storage = InMemoryStorage::new();
206
207 for i in 0..5 {
208 let log = TxLog::with_project(format!("project-{}", i));
209 storage.save(&log).unwrap();
210 }
211
212 assert_eq!(storage.list_sessions().unwrap().len(), 5);
213 }
214
215 #[test]
220 fn test_as_dyn_storage() {
221 let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
222 let sessions = storage.list_sessions().unwrap();
223 assert!(sessions.is_empty());
224 }
225
226 #[test]
227 fn test_dyn_storage_save_load() {
228 let mut storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
229 let log = TxLog::new();
230
231 let id = storage.save(&log).unwrap();
232 let loaded = storage.load(&id).unwrap();
233 assert_eq!(loaded.entries().len(), log.entries().len());
234 }
235}