1use std::collections::HashMap;
7use serde_json::{Value as JsonValue, json};
8use crate::{TestError, TestResult};
9
10#[derive(Clone)]
12pub struct TestClient {
13 base_url: String,
14 headers: HashMap<String, String>,
15 auth_token: Option<String>,
16}
17
18impl TestClient {
19 pub fn new() -> Self {
21 Self {
22 base_url: "http://localhost:3000".to_string(),
23 headers: HashMap::new(),
24 auth_token: None,
25 }
26 }
27
28 pub fn with_base_url(base_url: impl Into<String>) -> Self {
30 Self {
31 base_url: base_url.into(),
32 headers: HashMap::new(),
33 auth_token: None,
34 }
35 }
36
37 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
39 self.headers.insert(name.into(), value.into());
40 self
41 }
42
43 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
45 self.headers.extend(headers);
46 self
47 }
48
49 pub fn authenticated_with_token(mut self, token: impl Into<String>) -> Self {
51 let token = token.into();
52 self.auth_token = Some(token.clone());
53 self.headers.insert("Authorization".to_string(), format!("Bearer {}", token));
54 self
55 }
56
57 pub fn authenticated_as<T>(self, _user: &T) -> Self
59 where
60 T: AuthenticatedUser,
61 {
62 let token = "test_jwt_token"; self.authenticated_with_token(token)
65 }
66
67 pub fn get(self, path: impl Into<String>) -> RequestBuilder {
69 RequestBuilder::new(self, "GET".to_string(), path.into())
70 }
71
72 pub fn post(self, path: impl Into<String>) -> RequestBuilder {
74 RequestBuilder::new(self, "POST".to_string(), path.into())
75 }
76
77 pub fn put(self, path: impl Into<String>) -> RequestBuilder {
79 RequestBuilder::new(self, "PUT".to_string(), path.into())
80 }
81
82 pub fn patch(self, path: impl Into<String>) -> RequestBuilder {
84 RequestBuilder::new(self, "PATCH".to_string(), path.into())
85 }
86
87 pub fn delete(self, path: impl Into<String>) -> RequestBuilder {
89 RequestBuilder::new(self, "DELETE".to_string(), path.into())
90 }
91}
92
93impl Default for TestClient {
94 fn default() -> Self {
95 Self::new()
96 }
97}
98
99pub trait AuthenticatedUser {
101 fn id(&self) -> String;
103
104 fn roles(&self) -> Vec<String> {
106 vec![]
107 }
108
109 fn permissions(&self) -> Vec<String> {
111 vec![]
112 }
113}
114
115pub struct RequestBuilder {
117 client: TestClient,
118 method: String,
119 path: String,
120 headers: HashMap<String, String>,
121 body: Option<String>,
122 query_params: HashMap<String, String>,
123}
124
125impl RequestBuilder {
126 fn new(client: TestClient, method: String, path: String) -> Self {
127 Self {
128 client,
129 method,
130 path,
131 headers: HashMap::new(),
132 body: None,
133 query_params: HashMap::new(),
134 }
135 }
136
137 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
139 self.headers.insert(name.into(), value.into());
140 self
141 }
142
143 pub fn json<T: serde::Serialize>(mut self, data: &T) -> Self {
145 match serde_json::to_string(data) {
146 Ok(json_str) => {
147 self.body = Some(json_str);
148 self.headers.insert("Content-Type".to_string(), "application/json".to_string());
149 },
150 Err(_) => {
151 }
153 }
154 self
155 }
156
157 pub fn form(mut self, data: HashMap<String, String>) -> Self {
159 let form_data = data.iter()
160 .map(|(k, v)| format!("{}={}", k, v))
161 .collect::<Vec<_>>()
162 .join("&");
163 self.body = Some(form_data);
164 self.headers.insert("Content-Type".to_string(), "application/x-www-form-urlencoded".to_string());
165 self
166 }
167
168 pub fn body(mut self, body: impl Into<String>) -> Self {
170 self.body = Some(body.into());
171 self
172 }
173
174 pub fn query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
176 self.query_params.insert(key.into(), value.into());
177 self
178 }
179
180 pub fn queries(mut self, params: HashMap<String, String>) -> Self {
182 self.query_params.extend(params);
183 self
184 }
185
186 pub async fn send(self) -> TestResult<TestResponse> {
188 let mut url = format!("{}{}", self.client.base_url, self.path);
190 if !self.query_params.is_empty() {
191 let query_string = self.query_params.iter()
192 .map(|(k, v)| format!("{}={}", k, v))
193 .collect::<Vec<_>>()
194 .join("&");
195 url.push_str(&format!("?{}", query_string));
196 }
197
198 let response = TestResponse {
201 status_code: 200,
202 headers: {
203 let mut headers = HashMap::new();
204 headers.insert("Content-Type".to_string(), "application/json".to_string());
205 headers
206 },
207 body: json!({"message": "Test response", "method": self.method, "path": self.path}).to_string(),
208 };
209
210 Ok(response)
211 }
212}
213
214pub struct TestResponse {
216 status_code: u16,
217 headers: HashMap<String, String>,
218 body: String,
219}
220
221impl TestResponse {
222 pub fn status(&self) -> u16 {
224 self.status_code
225 }
226
227 pub fn headers(&self) -> &HashMap<String, String> {
229 &self.headers
230 }
231
232 pub fn body(&self) -> &str {
234 &self.body
235 }
236
237 pub fn json(&self) -> TestResult<JsonValue> {
239 let json_value: JsonValue = serde_json::from_str(&self.body)?;
240 Ok(json_value)
241 }
242
243 pub fn assert_status(self, expected_status: u16) -> Self {
245 if self.status_code != expected_status {
246 panic!("Expected status {}, got {}", expected_status, self.status_code);
247 }
248 self
249 }
250
251 pub fn assert_success(self) -> Self {
253 if self.status_code < 200 || self.status_code >= 300 {
254 panic!("Expected successful status, got {}", self.status_code);
255 }
256 self
257 }
258
259 pub fn assert_header(self, name: &str, expected_value: &str) -> Self {
261 if let Some(value) = self.headers.get(name) {
262 if value != expected_value {
263 panic!("Expected header '{}' to be '{}', got '{}'", name, expected_value, value);
264 }
265 } else {
266 panic!("Expected header '{}' not found", name);
267 }
268 self
269 }
270
271 pub fn assert_header_exists(self, name: &str) -> Self {
273 if !self.headers.contains_key(name) {
274 panic!("Expected header '{}' to exist", name);
275 }
276 self
277 }
278
279 pub fn assert_json_contains(self, expected: JsonValue) -> TestResult<Self> {
281 let actual_json = self.json()?;
282
283 if !json_contains(&actual_json, &expected) {
284 return Err(TestError::Assertion {
285 message: format!("Expected JSON to contain: {}, got: {}", expected, actual_json),
286 });
287 }
288
289 Ok(self)
290 }
291
292 pub fn assert_json_equals(self, expected: JsonValue) -> TestResult<Self> {
294 let actual_json = self.json()?;
295
296 if actual_json != expected {
297 return Err(TestError::Assertion {
298 message: format!("Expected JSON: {}, got: {}", expected, actual_json),
299 });
300 }
301
302 Ok(self)
303 }
304
305 pub fn assert_body_contains(self, expected_text: &str) -> TestResult<Self> {
307 let body = self.body();
308
309 if !body.contains(expected_text) {
310 return Err(TestError::Assertion {
311 message: format!("Expected body to contain '{}', got: {}", expected_text, body),
312 });
313 }
314
315 Ok(self)
316 }
317
318 pub fn assert_validation_error(self, field: &str, _error_type: &str) -> TestResult<Self> {
320 let json = self.json()?;
321
322 if let Some(errors) = json.get("errors") {
324 if let Some(field_errors) = errors.get(field) {
325 if field_errors.as_array().map_or(false, |arr| !arr.is_empty()) {
326 return Ok(self);
327 }
328 }
329 }
330
331 Err(TestError::Assertion {
332 message: format!("Expected validation error for field '{}', got: {}", field, json),
333 })
334 }
335}
336
337fn json_contains(actual: &JsonValue, expected: &JsonValue) -> bool {
339 match (actual, expected) {
340 (JsonValue::Object(actual_map), JsonValue::Object(expected_map)) => {
341 for (key, expected_value) in expected_map {
342 if let Some(actual_value) = actual_map.get(key) {
343 if !json_contains(actual_value, expected_value) {
344 return false;
345 }
346 } else {
347 return false;
348 }
349 }
350 true
351 },
352 (JsonValue::Array(actual_arr), JsonValue::Array(expected_arr)) => {
353 expected_arr.iter().all(|expected_item| {
355 actual_arr.iter().any(|actual_item| json_contains(actual_item, expected_item))
356 })
357 },
358 _ => actual == expected,
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use serde_json::json;
366
367 #[test]
368 fn test_client_creation() {
369 let client = TestClient::new();
370 assert_eq!(client.base_url, "http://localhost:3000");
371 assert!(client.headers.is_empty());
372 }
373
374 #[test]
375 fn test_client_with_custom_url() {
376 let client = TestClient::with_base_url("http://example.com");
377 assert_eq!(client.base_url, "http://example.com");
378 }
379
380 #[test]
381 fn test_client_headers() {
382 let client = TestClient::new()
383 .header("X-Test", "value");
384 assert_eq!(client.headers.get("X-Test"), Some(&"value".to_string()));
385 }
386
387 #[test]
388 fn test_json_contains() {
389 let actual = json!({"name": "John", "age": 30, "active": true});
390 let expected = json!({"name": "John"});
391
392 assert!(json_contains(&actual, &expected));
393
394 let expected_false = json!({"name": "Jane"});
395 assert!(!json_contains(&actual, &expected_false));
396 }
397
398 #[test]
399 fn test_json_contains_nested() {
400 let actual = json!({
401 "user": {
402 "name": "John",
403 "profile": {
404 "email": "john@example.com"
405 }
406 }
407 });
408 let expected = json!({
409 "user": {
410 "name": "John"
411 }
412 });
413
414 assert!(json_contains(&actual, &expected));
415 }
416
417 #[test]
418 fn test_json_contains_array() {
419 let actual = json!({"items": ["a", "b", "c"]});
420 let expected = json!({"items": ["a", "c"]});
421
422 assert!(json_contains(&actual, &expected));
423 }
424}