use ryo_storage::{StorageResult, TxLog};
use std::path::Path;
pub trait Storage: Send + Sync {
fn init(&mut self) -> StorageResult<()>;
fn save(&mut self, log: &TxLog) -> StorageResult<String>;
fn load(&self, session_id: &str) -> StorageResult<TxLog>;
fn list_sessions(&self) -> StorageResult<Vec<String>>;
fn sessions_for_project(&self, project_path: &Path) -> StorageResult<Vec<String>>;
fn delete(&mut self, session_id: &str) -> StorageResult<()>;
}
#[derive(Default)]
pub struct InMemoryStorage {
sessions: std::collections::HashMap<String, TxLog>,
}
impl InMemoryStorage {
pub fn new() -> Self {
Self::default()
}
}
impl Storage for InMemoryStorage {
fn init(&mut self) -> StorageResult<()> {
Ok(())
}
fn save(&mut self, log: &TxLog) -> StorageResult<String> {
let id = uuid::Uuid::new_v4().to_string();
self.sessions.insert(id.clone(), log.clone());
Ok(id)
}
fn load(&self, session_id: &str) -> StorageResult<TxLog> {
self.sessions
.get(session_id)
.cloned()
.ok_or_else(|| ryo_storage::StorageError::SessionNotFound(session_id.to_string()))
}
fn list_sessions(&self) -> StorageResult<Vec<String>> {
Ok(self.sessions.keys().cloned().collect())
}
fn sessions_for_project(&self, _project_path: &Path) -> StorageResult<Vec<String>> {
self.list_sessions()
}
fn delete(&mut self, session_id: &str) -> StorageResult<()> {
self.sessions
.remove(session_id)
.map(|_| ())
.ok_or_else(|| ryo_storage::StorageError::SessionNotFound(session_id.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let storage = InMemoryStorage::new();
assert!(storage.sessions.is_empty());
}
#[test]
fn test_default() {
let storage = InMemoryStorage::default();
assert!(storage.sessions.is_empty());
}
#[test]
fn test_init() {
let mut storage = InMemoryStorage::new();
assert!(storage.init().is_ok());
}
#[test]
fn test_save_and_load() {
let mut storage = InMemoryStorage::new();
let log = TxLog::new();
let id = storage.save(&log).unwrap();
assert!(!id.is_empty());
let loaded = storage.load(&id).unwrap();
assert_eq!(loaded.entries().len(), log.entries().len());
}
#[test]
fn test_load_nonexistent() {
let storage = InMemoryStorage::new();
let result = storage.load("nonexistent-id");
assert!(result.is_err());
}
#[test]
fn test_list_sessions_empty() {
let storage = InMemoryStorage::new();
let sessions = storage.list_sessions().unwrap();
assert!(sessions.is_empty());
}
#[test]
fn test_list_sessions_with_data() {
let mut storage = InMemoryStorage::new();
let log1 = TxLog::new();
let log2 = TxLog::new();
let id1 = storage.save(&log1).unwrap();
let id2 = storage.save(&log2).unwrap();
let sessions = storage.list_sessions().unwrap();
assert_eq!(sessions.len(), 2);
assert!(sessions.contains(&id1));
assert!(sessions.contains(&id2));
}
#[test]
fn test_sessions_for_project() {
let mut storage = InMemoryStorage::new();
let log = TxLog::new();
storage.save(&log).unwrap();
let sessions = storage
.sessions_for_project(Path::new("/some/project"))
.unwrap();
assert_eq!(sessions.len(), 1);
}
#[test]
fn test_delete() {
let mut storage = InMemoryStorage::new();
let log = TxLog::new();
let id = storage.save(&log).unwrap();
assert!(storage.load(&id).is_ok());
assert!(storage.delete(&id).is_ok());
assert!(storage.load(&id).is_err());
}
#[test]
fn test_delete_nonexistent() {
let mut storage = InMemoryStorage::new();
let result = storage.delete("nonexistent-id");
assert!(result.is_err());
}
#[test]
fn test_multiple_saves() {
let mut storage = InMemoryStorage::new();
for i in 0..5 {
let log = TxLog::with_project(format!("project-{}", i));
storage.save(&log).unwrap();
}
assert_eq!(storage.list_sessions().unwrap().len(), 5);
}
#[test]
fn test_as_dyn_storage() {
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let sessions = storage.list_sessions().unwrap();
assert!(sessions.is_empty());
}
#[test]
fn test_dyn_storage_save_load() {
let mut storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let log = TxLog::new();
let id = storage.save(&log).unwrap();
let loaded = storage.load(&id).unwrap();
assert_eq!(loaded.entries().len(), log.entries().len());
}
}