1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Mock HTTP client for deterministic testing
use crate::error::Result;
use crate::http_types::*;
/// Mock HTTP client that returns predefined responses
///
/// Use this for deterministic unit tests without a running server.
///
/// # Example
/// ```rust,no_run
/// use wauldo::MockHttpClient;
/// let mock = MockHttpClient::new();
/// ```
pub struct MockHttpClient {
chat_response: Option<ChatResponse>,
models: Option<ModelList>,
rag_upload_response: Option<RagUploadResponse>,
rag_query_response: Option<RagQueryResponse>,
fact_check_response: Option<FactCheckResponse>,
}
impl MockHttpClient {
/// Create a new empty mock client with no preconfigured responses
///
/// # Example
/// ```rust
/// use wauldo::MockHttpClient;
/// let mock = MockHttpClient::new();
/// ```
pub fn new() -> Self {
Self {
chat_response: None,
models: None,
rag_upload_response: None,
rag_query_response: None,
fact_check_response: None,
}
}
/// Set the chat completion response returned by `chat()`
///
/// # Example
/// ```rust,no_run
/// # use wauldo::{MockHttpClient, ChatResponse};
/// # let resp: ChatResponse = todo!();
/// let mock = MockHttpClient::new().with_chat_response(resp);
/// ```
pub fn with_chat_response(mut self, response: ChatResponse) -> Self {
self.chat_response = Some(response);
self
}
/// Set the models list returned by `list_models()`
///
/// # Example
/// ```rust,no_run
/// # use wauldo::{MockHttpClient, Model};
/// let mock = MockHttpClient::new().with_models(vec![]);
/// ```
pub fn with_models(mut self, models: Vec<Model>) -> Self {
self.models = Some(ModelList {
object: "list".to_string(),
data: models,
});
self
}
/// Set the RAG upload response returned by `rag_upload()`
///
/// # Example
/// ```rust,no_run
/// # use wauldo::{MockHttpClient, RagUploadResponse};
/// # let resp: RagUploadResponse = todo!();
/// let mock = MockHttpClient::new().with_rag_upload(resp);
/// ```
pub fn with_rag_upload(mut self, response: RagUploadResponse) -> Self {
self.rag_upload_response = Some(response);
self
}
/// Set the RAG query response returned by `rag_query()`
///
/// # Example
/// ```rust,no_run
/// # use wauldo::{MockHttpClient, RagQueryResponse};
/// # let resp: RagQueryResponse = todo!();
/// let mock = MockHttpClient::new().with_rag_query(resp);
/// ```
pub fn with_rag_query(mut self, response: RagQueryResponse) -> Self {
self.rag_query_response = Some(response);
self
}
/// Set the fact-check response returned by `fact_check()`
pub fn with_fact_check(mut self, response: FactCheckResponse) -> Self {
self.fact_check_response = Some(response);
self
}
/// List models (mocked) -- returns the value set via `with_models()`
///
/// # Example
/// ```rust,no_run
/// # async fn run() -> wauldo::Result<()> {
/// # use wauldo::MockHttpClient;
/// let mock = MockHttpClient::new().with_models(vec![]);
/// let models = mock.list_models().await?;
/// # Ok(())
/// # }
/// ```
pub async fn list_models(&self) -> Result<ModelList> {
self.models
.clone()
.ok_or_else(|| crate::error::Error::connection("MockHttpClient: no models configured"))
}
/// Chat completion (mocked, non-streaming) -- ignores the request body
///
/// # Example
/// ```rust,no_run
/// # async fn run() -> wauldo::Result<()> {
/// # use wauldo::{MockHttpClient, ChatRequest, ChatMessage};
/// # let resp: wauldo::ChatResponse = todo!();
/// let mock = MockHttpClient::new().with_chat_response(resp);
/// let req = ChatRequest::new("model", vec![ChatMessage::user("hi")]);
/// let chat = mock.chat(req).await?;
/// # Ok(())
/// # }
/// ```
pub async fn chat(&self, _request: ChatRequest) -> Result<ChatResponse> {
self.chat_response.clone().ok_or_else(|| {
crate::error::Error::connection("MockHttpClient: no chat response configured")
})
}
/// Upload document for RAG (mocked) -- ignores the content
///
/// # Example
/// ```rust,no_run
/// # async fn run() -> wauldo::Result<()> {
/// # use wauldo::MockHttpClient;
/// # let resp: wauldo::RagUploadResponse = todo!();
/// let mock = MockHttpClient::new().with_rag_upload(resp);
/// let upload = mock.rag_upload("text", None).await?;
/// # Ok(())
/// # }
/// ```
pub async fn rag_upload(
&self,
_content: impl Into<String>,
_filename: Option<String>,
) -> Result<RagUploadResponse> {
self.rag_upload_response.clone().ok_or_else(|| {
crate::error::Error::connection("MockHttpClient: no rag_upload response configured")
})
}
/// Query RAG (mocked) -- ignores the query string
///
/// # Example
/// ```rust,no_run
/// # async fn run() -> wauldo::Result<()> {
/// # use wauldo::MockHttpClient;
/// # let resp: wauldo::RagQueryResponse = todo!();
/// let mock = MockHttpClient::new().with_rag_query(resp);
/// let result = mock.rag_query("search term", None).await?;
/// # Ok(())
/// # }
/// ```
pub async fn rag_query(
&self,
_query: impl Into<String>,
_top_k: Option<usize>,
) -> Result<RagQueryResponse> {
self.rag_query_response.clone().ok_or_else(|| {
crate::error::Error::connection("MockHttpClient: no rag_query response configured")
})
}
/// Query RAG with debug mode (mocked) -- same as rag_query
pub async fn rag_query_debug(
&self,
query: impl Into<String>,
top_k: Option<usize>,
) -> Result<RagQueryResponse> {
self.rag_query(query, top_k).await
}
/// Chat completion with per-request timeout (mocked) -- timeout is ignored
pub async fn chat_with_timeout(
&self,
request: ChatRequest,
_timeout_ms: Option<u64>,
) -> Result<ChatResponse> {
self.chat(request).await
}
/// Upload with per-request timeout (mocked) -- timeout is ignored
pub async fn rag_upload_with_timeout(
&self,
content: impl Into<String>,
filename: Option<String>,
_timeout_ms: Option<u64>,
) -> Result<RagUploadResponse> {
self.rag_upload(content, filename).await
}
/// Generate embeddings (mocked) -- returns a dummy embedding vector
pub async fn embeddings(
&self,
_input: EmbeddingInput,
_model: impl Into<String>,
) -> Result<EmbeddingResponse> {
Ok(EmbeddingResponse {
data: vec![EmbeddingData {
embedding: vec![0.1, 0.2, 0.3],
index: 0,
}],
model: "mock-model".to_string(),
usage: EmbeddingUsage {
prompt_tokens: 5,
total_tokens: 5,
},
})
}
/// Execute orchestrator (mocked) -- returns a fixed output
pub async fn orchestrate(&self, _prompt: impl Into<String>) -> Result<OrchestratorResponse> {
Ok(OrchestratorResponse {
final_output: "Mock orchestration result".to_string(),
})
}
/// Execute parallel swarm (mocked) -- returns a fixed output
pub async fn orchestrate_parallel(
&self,
_prompt: impl Into<String>,
) -> Result<OrchestratorResponse> {
Ok(OrchestratorResponse {
final_output: "Mock parallel result".to_string(),
})
}
/// Fact-check (mocked) -- returns the value set via `with_fact_check()`
pub async fn fact_check(&self, _request: FactCheckRequest) -> Result<FactCheckResponse> {
self.fact_check_response.clone().ok_or_else(|| {
crate::error::Error::connection("MockHttpClient: no fact_check response configured")
})
}
/// Upload text into RAG and immediately query it (mocked) -- convenience one-shot
pub async fn rag_ask(
&self,
_question: impl Into<String>,
_text: impl Into<String>,
) -> Result<String> {
let _ = self.rag_upload("mock", None).await?;
Ok(self.rag_query("mock", None).await?.answer)
}
}
impl Default for MockHttpClient {
fn default() -> Self {
Self::new()
}
}