Skip to main content

armature_testing/
test_client.rs

1// Test HTTP Client
2
3use armature_core::{Error, HttpMethod, HttpRequest, HttpResponse, Router};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7/// Test HTTP client for making requests to the application
8pub struct TestClient {
9    router: Arc<Router>,
10}
11
12impl TestClient {
13    /// Create a new test client
14    pub fn new(router: Arc<Router>) -> Self {
15        Self { router }
16    }
17
18    /// Make a GET request
19    pub async fn get(&self, path: &str) -> TestResponse {
20        self.request(HttpMethod::GET, path, None).await
21    }
22
23    /// Make a POST request
24    pub async fn post(&self, path: &str, body: Vec<u8>) -> TestResponse {
25        self.request(HttpMethod::POST, path, Some(body)).await
26    }
27
28    /// Make a PUT request
29    pub async fn put(&self, path: &str, body: Vec<u8>) -> TestResponse {
30        self.request(HttpMethod::PUT, path, Some(body)).await
31    }
32
33    /// Make a DELETE request
34    pub async fn delete(&self, path: &str) -> TestResponse {
35        self.request(HttpMethod::DELETE, path, None).await
36    }
37
38    /// Make a PATCH request
39    pub async fn patch(&self, path: &str, body: Vec<u8>) -> TestResponse {
40        self.request(HttpMethod::PATCH, path, Some(body)).await
41    }
42
43    /// Make a request with custom method
44    pub async fn request(
45        &self,
46        method: HttpMethod,
47        path: &str,
48        body: Option<Vec<u8>>,
49    ) -> TestResponse {
50        let mut req = HttpRequest::new(method.as_str().to_string(), path.to_string());
51        req.body = body.unwrap_or_default();
52
53        // Route the request
54        match self.router.route(req).await {
55            Ok(response) => TestResponse::Success(response),
56            Err(error) => TestResponse::Error(error),
57        }
58    }
59}
60
61/// Builder for test requests
62#[allow(dead_code)]
63pub struct TestRequestBuilder {
64    method: HttpMethod,
65    path: String,
66    headers: HashMap<String, String>,
67    body: Vec<u8>,
68    query_params: HashMap<String, String>,
69}
70
71impl TestRequestBuilder {
72    /// Create a new request builder
73    #[allow(dead_code)]
74    pub fn new(method: HttpMethod, path: &str) -> Self {
75        Self {
76            method,
77            path: path.to_string(),
78            headers: HashMap::new(),
79            body: Vec::new(),
80            query_params: HashMap::new(),
81        }
82    }
83
84    /// Add a header
85    #[allow(dead_code)]
86    pub fn header(mut self, key: &str, value: &str) -> Self {
87        self.headers.insert(key.to_string(), value.to_string());
88        self
89    }
90
91    /// Set the body
92    #[allow(dead_code)]
93    pub fn body(mut self, body: Vec<u8>) -> Self {
94        self.body = body;
95        self
96    }
97
98    /// Set JSON body
99    #[allow(dead_code)]
100    pub fn json<T: serde::Serialize>(mut self, data: &T) -> Result<Self, Error> {
101        self.body = serde_json::to_vec(data).map_err(|e| Error::Serialization(e.to_string()))?;
102        self.headers
103            .insert("Content-Type".to_string(), "application/json".to_string());
104        Ok(self)
105    }
106
107    /// Add a query parameter
108    #[allow(dead_code)]
109    pub fn query(mut self, key: &str, value: &str) -> Self {
110        self.query_params.insert(key.to_string(), value.to_string());
111        self
112    }
113
114    /// Build the request
115    #[allow(dead_code)]
116    pub fn build(self) -> HttpRequest {
117        // Build query string
118        let query_string = if !self.query_params.is_empty() {
119            let params: Vec<String> = self
120                .query_params
121                .iter()
122                .map(|(k, v)| format!("{}={}", k, v))
123                .collect();
124            format!("?{}", params.join("&"))
125        } else {
126            String::new()
127        };
128
129        HttpRequest::from_parts(
130            self.method.as_str().to_string(),
131            format!("{}{}", self.path, query_string),
132            self.headers,
133            self.body,
134            HashMap::new(),
135            self.query_params,
136        )
137    }
138}
139
140/// Response from a test request
141#[derive(Debug)]
142pub enum TestResponse {
143    Success(HttpResponse),
144    Error(Error),
145}
146
147impl TestResponse {
148    /// Assert the response is successful
149    pub fn assert_success(&self) -> &HttpResponse {
150        match self {
151            TestResponse::Success(response) => response,
152            TestResponse::Error(error) => {
153                panic!("Expected success response, got error: {:?}", error)
154            }
155        }
156    }
157
158    /// Assert the response is an error
159    pub fn assert_error(&self) -> &Error {
160        match self {
161            TestResponse::Error(error) => error,
162            TestResponse::Success(_) => {
163                panic!("Expected error response, got success")
164            }
165        }
166    }
167
168    /// Get the status code
169    pub fn status(&self) -> Option<u16> {
170        match self {
171            TestResponse::Success(response) => Some(response.status),
172            TestResponse::Error(_) => None,
173        }
174    }
175
176    /// Get the response body as string
177    pub fn body_string(&self) -> Option<String> {
178        match self {
179            TestResponse::Success(response) => String::from_utf8(response.body.clone()).ok(),
180            TestResponse::Error(_) => None,
181        }
182    }
183
184    /// Get the response body as JSON
185    pub fn body_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, String> {
186        match self {
187            TestResponse::Success(response) => serde_json::from_slice(&response.body)
188                .map_err(|e| format!("Serialization error: {}", e)),
189            TestResponse::Error(error) => Err(format!("{:?}", error)),
190        }
191    }
192
193    /// Get a header value
194    pub fn header(&self, key: &str) -> Option<&String> {
195        match self {
196            TestResponse::Success(response) => response.headers.get(key),
197            TestResponse::Error(_) => None,
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_request_builder() {
208        let req = TestRequestBuilder::new(HttpMethod::GET, "/test")
209            .header("Authorization", "Bearer token")
210            .query("foo", "bar")
211            .build();
212
213        assert_eq!(req.method, "GET");
214        assert!(req.path.contains("/test"));
215        assert_eq!(
216            req.headers.get("Authorization"),
217            Some(&"Bearer token".to_string())
218        );
219    }
220}