behest_store/memory/
artifact.rs1use std::collections::HashMap;
4
5use async_trait::async_trait;
6use tokio::sync::RwLock;
7use uuid::Uuid;
8
9use crate::{Artifact, ArtifactStore, StoreResult};
10
11#[derive(Default)]
16pub struct MemoryArtifactStore {
17 artifacts: RwLock<HashMap<Uuid, Artifact>>,
18}
19
20impl MemoryArtifactStore {
21 #[must_use]
23 pub fn new() -> Self {
24 Self::default()
25 }
26}
27
28#[async_trait]
29impl ArtifactStore for MemoryArtifactStore {
30 async fn put(&self, artifact: Artifact) -> StoreResult<Artifact> {
31 let mut artifacts = self.artifacts.write().await;
32 let id = artifact.id;
33 artifacts.insert(id, artifact.clone());
34 Ok(artifact)
35 }
36
37 async fn get(&self, id: &Uuid) -> StoreResult<Option<Artifact>> {
38 let artifacts = self.artifacts.read().await;
39 Ok(artifacts.get(id).cloned())
40 }
41
42 async fn delete(&self, id: &Uuid) -> StoreResult<()> {
43 self.artifacts.write().await.remove(id);
44 Ok(())
45 }
46
47 async fn list_by_session(&self, session_id: &Uuid) -> StoreResult<Vec<Artifact>> {
48 let artifacts = self.artifacts.read().await;
49 Ok(artifacts
50 .values()
51 .filter(|a| a.session_id == Some(*session_id))
52 .cloned()
53 .collect())
54 }
55}
56
57#[cfg(test)]
58#[allow(clippy::unwrap_used)]
59mod tests {
60 use super::*;
61 use serde_json::json;
62
63 fn test_artifact() -> Artifact {
64 Artifact::new("test.txt", "text/plain", b"hello world".to_vec())
65 }
66
67 #[tokio::test]
68 async fn memory_artifact_store_should_put_and_get() {
69 let store = MemoryArtifactStore::new();
70 let artifact = test_artifact();
71 let id = artifact.id;
72
73 store.put(artifact).await.unwrap();
74 let loaded = store.get(&id).await.unwrap();
75
76 assert!(loaded.is_some());
77 let artifact = loaded.as_ref().unwrap();
78 assert_eq!(artifact.name, "test.txt");
79 assert_eq!(artifact.data, b"hello world");
80 }
81
82 #[tokio::test]
83 async fn memory_artifact_store_should_delete() {
84 let store = MemoryArtifactStore::new();
85 let artifact = test_artifact();
86 let id = artifact.id;
87
88 store.put(artifact).await.unwrap();
89 store.delete(&id).await.unwrap();
90
91 assert!(store.get(&id).await.unwrap().is_none());
92 }
93
94 #[tokio::test]
95 async fn memory_artifact_store_should_list_by_session() {
96 let store = MemoryArtifactStore::new();
97 let session_id = Uuid::now_v7();
98
99 store
100 .put(test_artifact().with_session(session_id))
101 .await
102 .unwrap();
103 store
104 .put(test_artifact().with_session(session_id))
105 .await
106 .unwrap();
107 store.put(test_artifact()).await.unwrap();
108
109 let results = store.list_by_session(&session_id).await.unwrap();
110 assert_eq!(results.len(), 2);
111 }
112
113 #[tokio::test]
114 async fn memory_artifact_store_should_return_none_for_unknown() {
115 let store = MemoryArtifactStore::new();
116 assert!(store.get(&Uuid::now_v7()).await.unwrap().is_none());
117 }
118
119 #[tokio::test]
120 async fn memory_artifact_store_should_preserve_binary_data() {
121 let store = MemoryArtifactStore::new();
122 let data: Vec<u8> = (0..=255).collect();
123 let artifact = Artifact::new("binary.bin", "application/octet-stream", data.clone());
124 let id = artifact.id;
125
126 store.put(artifact).await.unwrap();
127 let loaded = store.get(&id).await.unwrap().unwrap();
128
129 assert_eq!(loaded.data, data);
130 assert_eq!(loaded.content_type, "application/octet-stream");
131 }
132
133 #[tokio::test]
134 async fn memory_artifact_store_should_support_metadata() {
135 let store = MemoryArtifactStore::new();
136 let artifact = test_artifact().with_metadata(json!({"source": "upload"}));
137 let id = artifact.id;
138
139 store.put(artifact).await.unwrap();
140 let loaded = store.get(&id).await.unwrap().unwrap();
141
142 assert_eq!(loaded.metadata, json!({"source": "upload"}));
143 }
144}