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