Skip to main content

entrenar/storage/cloud/
memory.rs

1//! In-memory artifact backend for testing
2
3use crate::storage::cloud::error::{CloudError, Result};
4use crate::storage::cloud::metadata::ArtifactMetadata;
5use crate::storage::cloud::traits::{compute_hash, ArtifactBackend};
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9/// In-memory artifact backend for testing
10#[derive(Debug, Default)]
11pub struct InMemoryBackend {
12    data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
13    metadata: Arc<RwLock<HashMap<String, ArtifactMetadata>>>,
14}
15
16impl InMemoryBackend {
17    /// Create a new in-memory backend
18    pub fn new() -> Self {
19        Self::default()
20    }
21}
22
23impl ArtifactBackend for InMemoryBackend {
24    fn put(&self, name: &str, data: &[u8]) -> Result<String> {
25        let hash = compute_hash(data);
26
27        self.data
28            .write()
29            .map_err(|e| CloudError::Backend(e.to_string()))?
30            .insert(hash.clone(), data.to_vec());
31
32        let metadata = ArtifactMetadata::new(name, &hash, data.len() as u64);
33        self.metadata
34            .write()
35            .map_err(|e| CloudError::Backend(e.to_string()))?
36            .insert(hash.clone(), metadata);
37
38        Ok(hash)
39    }
40
41    fn get(&self, hash: &str) -> Result<Vec<u8>> {
42        self.data
43            .read()
44            .map_err(|e| CloudError::Backend(e.to_string()))?
45            .get(hash)
46            .cloned()
47            .ok_or_else(|| CloudError::NotFound(hash.to_string()))
48    }
49
50    fn exists(&self, hash: &str) -> Result<bool> {
51        Ok(self.data.read().map_err(|e| CloudError::Backend(e.to_string()))?.contains_key(hash))
52    }
53
54    fn delete(&self, hash: &str) -> Result<()> {
55        let removed =
56            self.data.write().map_err(|e| CloudError::Backend(e.to_string()))?.remove(hash);
57        if removed.is_none() {
58            return Err(CloudError::NotFound(hash.to_string()));
59        }
60        self.metadata.write().map_err(|e| CloudError::Backend(e.to_string()))?.remove(hash);
61        Ok(())
62    }
63
64    fn get_metadata(&self, hash: &str) -> Result<ArtifactMetadata> {
65        self.metadata
66            .read()
67            .map_err(|e| CloudError::Backend(e.to_string()))?
68            .get(hash)
69            .cloned()
70            .ok_or_else(|| CloudError::NotFound(hash.to_string()))
71    }
72
73    fn list(&self) -> Result<Vec<ArtifactMetadata>> {
74        Ok(self
75            .metadata
76            .read()
77            .map_err(|e| CloudError::Backend(e.to_string()))?
78            .values()
79            .cloned()
80            .collect())
81    }
82
83    fn backend_type(&self) -> &'static str {
84        "memory"
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_in_memory_backend_put_get() {
94        let backend = InMemoryBackend::new();
95        let data = b"test data";
96        let hash = backend.put("test.bin", data).expect("operation should succeed");
97
98        let retrieved = backend.get(&hash).expect("key should exist");
99        assert_eq!(retrieved, data);
100    }
101
102    #[test]
103    fn test_in_memory_backend_exists() {
104        let backend = InMemoryBackend::new();
105        let hash = backend.put("test.bin", b"data").expect("operation should succeed");
106
107        assert!(backend.exists(&hash).expect("operation should succeed"));
108        assert!(!backend.exists("nonexistent").expect("operation should succeed"));
109    }
110
111    #[test]
112    fn test_in_memory_backend_delete() {
113        let backend = InMemoryBackend::new();
114        let hash = backend.put("test.bin", b"data").expect("operation should succeed");
115
116        backend.delete(&hash).expect("operation should succeed");
117        assert!(!backend.exists(&hash).expect("operation should succeed"));
118    }
119
120    #[test]
121    fn test_in_memory_backend_delete_not_found() {
122        let backend = InMemoryBackend::new();
123        let result = backend.delete("nonexistent");
124        assert!(result.is_err());
125    }
126
127    #[test]
128    fn test_in_memory_backend_get_metadata() {
129        let backend = InMemoryBackend::new();
130        let data = b"test data 123";
131        let hash = backend.put("model.bin", data).expect("operation should succeed");
132
133        let meta = backend.get_metadata(&hash).expect("operation should succeed");
134        assert_eq!(meta.name, "model.bin");
135        assert_eq!(meta.size, data.len() as u64);
136    }
137
138    #[test]
139    fn test_in_memory_backend_list() {
140        let backend = InMemoryBackend::new();
141        backend.put("file1.bin", b"data1").expect("operation should succeed");
142        backend.put("file2.bin", b"data2").expect("operation should succeed");
143
144        let list = backend.list().expect("operation should succeed");
145        assert_eq!(list.len(), 2);
146    }
147
148    #[test]
149    fn test_in_memory_backend_type() {
150        let backend = InMemoryBackend::new();
151        assert_eq!(backend.backend_type(), "memory");
152    }
153
154    #[test]
155    fn test_in_memory_backend_get_not_found() {
156        let backend = InMemoryBackend::new();
157        let result = backend.get("nonexistent");
158        assert!(result.is_err());
159    }
160
161    #[test]
162    fn test_in_memory_backend_get_metadata_not_found() {
163        let backend = InMemoryBackend::new();
164        let result = backend.get_metadata("nonexistent");
165        assert!(result.is_err());
166    }
167
168    #[test]
169    fn test_in_memory_backend_default() {
170        let backend = InMemoryBackend::default();
171        assert_eq!(backend.backend_type(), "memory");
172    }
173}
174
175#[cfg(test)]
176mod property_tests {
177    use super::*;
178    use proptest::prelude::*;
179
180    proptest! {
181        #![proptest_config(ProptestConfig::with_cases(200))]
182
183        #[test]
184        fn prop_memory_backend_roundtrip(
185            name in "[a-zA-Z0-9_]{1,50}",
186            data in prop::collection::vec(any::<u8>(), 1..1000)
187        ) {
188            let backend = InMemoryBackend::new();
189            let hash = backend.put(&name, &data).expect("operation should succeed");
190            let retrieved = backend.get(&hash).expect("key should exist");
191            prop_assert_eq!(retrieved, data);
192        }
193    }
194}