stygian_graph/adapters/
mock_ai.rs1use crate::domain::error::Result;
7use crate::ports::{AIProvider, ProviderCapabilities};
8use async_trait::async_trait;
9use futures::stream::{self, BoxStream};
10use serde_json::{Value, json};
11
12pub struct MockAIProvider;
35
36#[async_trait]
37impl AIProvider for MockAIProvider {
38 async fn extract(&self, content: String, _schema: Value) -> Result<Value> {
39 Ok(json!({
40 "mock": true,
41 "provider": self.name(),
42 "content_length": content.len(),
43 "extracted_data": {
44 "title": "Mock Title",
45 "description": "Mock Description",
46 }
47 }))
48 }
49
50 async fn stream_extract(
51 &self,
52 content: String,
53 _schema: Value,
54 ) -> Result<BoxStream<'static, Result<Value>>> {
55 let chunks = vec![
57 Ok(json!({"chunk": 1, "data": "first"})),
58 Ok(json!({"chunk": 2, "data": "second"})),
59 Ok(json!({"chunk": 3, "data": "third", "content_length": content.len()})),
60 ];
61 Ok(Box::pin(stream::iter(chunks)))
62 }
63
64 fn capabilities(&self) -> ProviderCapabilities {
65 ProviderCapabilities {
66 streaming: true,
67 vision: false,
68 tool_use: false,
69 json_mode: true,
70 }
71 }
72
73 fn name(&self) -> &'static str {
74 "mock-ai"
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use futures::StreamExt;
82 use serde_json::json;
83
84 #[tokio::test]
85 async fn test_mock_provider_extract() -> crate::domain::error::Result<()> {
86 let provider = MockAIProvider;
87 let schema = json!({
88 "type": "object",
89 "properties": {
90 "title": {"type": "string"}
91 }
92 });
93 let content = "Test HTML content".to_string();
94
95 let output = provider.extract(content.clone(), schema).await?;
96 assert_eq!(output.get("mock").and_then(Value::as_bool), Some(true));
97 assert_eq!(
98 output.get("provider").and_then(Value::as_str),
99 Some("mock-ai")
100 );
101 assert_eq!(
102 output.get("content_length").and_then(Value::as_u64),
103 u64::try_from(content.len()).ok()
104 );
105 assert_eq!(
106 output
107 .get("extracted_data")
108 .and_then(|d| d.get("title"))
109 .and_then(Value::as_str),
110 Some("Mock Title")
111 );
112 Ok(())
113 }
114
115 #[tokio::test]
116 async fn test_mock_provider_stream_extract() -> crate::domain::error::Result<()> {
117 let provider = MockAIProvider;
118 let schema = json!({"type": "object"});
119 let content = "Stream test content".to_string();
120
121 let mut stream = provider.stream_extract(content.clone(), schema).await?;
122 let mut chunks = Vec::new();
123
124 while let Some(chunk_result) = stream.next().await {
125 chunks.push(chunk_result?);
126 }
127
128 assert_eq!(chunks.len(), 3, "Should emit 3 chunks");
129 assert_eq!(
130 chunks
131 .first()
132 .and_then(|c| c.get("chunk"))
133 .and_then(Value::as_u64),
134 Some(1)
135 );
136 assert_eq!(
137 chunks
138 .get(1)
139 .and_then(|c| c.get("chunk"))
140 .and_then(Value::as_u64),
141 Some(2)
142 );
143 assert_eq!(
144 chunks
145 .get(2)
146 .and_then(|c| c.get("chunk"))
147 .and_then(Value::as_u64),
148 Some(3)
149 );
150 assert_eq!(
151 chunks
152 .get(2)
153 .and_then(|c| c.get("content_length"))
154 .and_then(Value::as_u64),
155 u64::try_from(content.len()).ok()
156 );
157 Ok(())
158 }
159
160 #[tokio::test]
161 async fn test_mock_provider_capabilities() {
162 let provider = MockAIProvider;
163 let caps = provider.capabilities();
164
165 assert!(caps.streaming, "Mock provider supports streaming");
166 assert!(!caps.vision, "Mock provider does not support vision");
167 assert!(!caps.tool_use, "Mock provider does not support tool use");
168 assert!(caps.json_mode, "Mock provider supports JSON mode");
169 }
170
171 #[tokio::test]
172 async fn test_mock_provider_name() {
173 let provider = MockAIProvider;
174 assert_eq!(provider.name(), "mock-ai");
175 }
176
177 #[tokio::test]
178 async fn test_mock_provider_is_send_sync() {
179 fn assert_send_sync<T: Send + Sync>() {}
181 assert_send_sync::<MockAIProvider>();
182 }
183
184 #[tokio::test]
185 async fn test_default_capabilities() {
186 let default_caps = ProviderCapabilities::default();
187 assert!(!default_caps.streaming);
188 assert!(!default_caps.vision);
189 assert!(!default_caps.tool_use);
190 assert!(!default_caps.json_mode);
191 }
192
193 #[tokio::test]
194 async fn test_capabilities_equality() {
195 let caps1 = ProviderCapabilities {
196 streaming: true,
197 vision: false,
198 tool_use: true,
199 json_mode: true,
200 };
201 let caps2 = ProviderCapabilities {
202 streaming: true,
203 vision: false,
204 tool_use: true,
205 json_mode: true,
206 };
207 assert_eq!(caps1, caps2);
208 }
209}