Skip to main content

agent_sdk_toolkit/testing/
stores.rs

1//! Toolkit-specific deterministic test helpers. Use these fakes for content stores,
2//! argument stores, and scripted protocol harnesses without live editors, MCP
3//! servers, or product hosts. Helpers mutate only in-memory state unless noted. This
4//! file contains the stores portion of that contract.
5//!
6use std::{
7    collections::BTreeMap,
8    sync::{Arc, Mutex},
9};
10
11use agent_sdk_core::{AgentError, domain::ContentRef};
12use serde::{Serialize, de::DeserializeOwned};
13use serde_json::Value;
14
15#[derive(Clone, Debug, Default)]
16/// In-memory JSON argument store fixture for SDK conformance tests.
17/// Use it to script deterministic behavior in memory; any transcript or endpoint mutation is documented on the method that performs it.
18pub struct InMemoryJsonArgumentStore {
19    entries: Arc<Mutex<BTreeMap<ContentRef, Value>>>,
20}
21
22impl InMemoryJsonArgumentStore {
23    /// Adds data to this in-memory testing::stores collection. It does not
24    /// perform external I/O, execute tools, or append journals.
25    pub fn insert<T: Serialize>(
26        &self,
27        content_ref: ContentRef,
28        value: &T,
29    ) -> Result<(), AgentError> {
30        let value = serde_json::to_value(value).map_err(|error| {
31            AgentError::contract_violation(format!("argument serialization failed: {error}"))
32        })?;
33        self.entries
34            .lock()
35            .map_err(|_| AgentError::contract_violation("argument store lock poisoned"))?
36            .insert(content_ref, value);
37        Ok(())
38    }
39
40    /// Looks up an entry in this local store without registry or runtime work.
41    /// This reads deterministic in-memory test store state and performs no external I/O.
42    pub fn get<T: DeserializeOwned>(&self, content_ref: &ContentRef) -> Result<T, AgentError> {
43        let value = self
44            .entries
45            .lock()
46            .map_err(|_| AgentError::contract_violation("argument store lock poisoned"))?
47            .get(content_ref)
48            .cloned()
49            .ok_or_else(|| AgentError::missing_required_field("tool_argument.content_ref"))?;
50        serde_json::from_value(value).map_err(|error| {
51            AgentError::contract_violation(format!("argument deserialization failed: {error}"))
52        })
53    }
54}
55
56#[derive(Clone, Debug, Default)]
57/// In-memory toolkit content store fixture for SDK conformance tests.
58/// Use it to script deterministic behavior in memory; any transcript or endpoint mutation is documented on the method that performs it.
59pub struct InMemoryToolkitContentStore {
60    entries: Arc<Mutex<BTreeMap<ContentRef, Value>>>,
61}
62
63impl InMemoryToolkitContentStore {
64    /// Adds data to this in-memory testing::stores collection. It does not
65    /// perform external I/O, execute tools, or append journals.
66    pub fn put<T: Serialize>(&self, content_ref: ContentRef, value: &T) -> Result<(), AgentError> {
67        let value = serde_json::to_value(value).map_err(|error| {
68            AgentError::contract_violation(format!("content serialization failed: {error}"))
69        })?;
70        self.entries
71            .lock()
72            .map_err(|_| AgentError::contract_violation("content store lock poisoned"))?
73            .insert(content_ref, value);
74        Ok(())
75    }
76
77    /// Looks up an entry in this local store without registry or runtime work.
78    /// This reads deterministic in-memory test store state and performs no external I/O.
79    pub fn get<T: DeserializeOwned>(&self, content_ref: &ContentRef) -> Result<T, AgentError> {
80        let value = self
81            .entries
82            .lock()
83            .map_err(|_| AgentError::contract_violation("content store lock poisoned"))?
84            .get(content_ref)
85            .cloned()
86            .ok_or_else(|| AgentError::missing_required_field("tool_content.content_ref"))?;
87        serde_json::from_value(value).map_err(|error| {
88            AgentError::contract_violation(format!("content deserialization failed: {error}"))
89        })
90    }
91}