Skip to main content

armature_testing/
test_client.rs

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