1use armature_core::Bytes;
4use armature_core::{Error, HttpMethod, HttpRequest, HttpResponse, Router};
5use std::collections::HashMap;
6use std::sync::Arc;
7
8pub struct TestClient {
10 router: Arc<Router>,
11}
12
13impl TestClient {
14 pub fn new(router: Arc<Router>) -> Self {
16 Self { router }
17 }
18
19 pub async fn get(&self, path: &str) -> TestResponse {
21 self.request(HttpMethod::GET, path, None).await
22 }
23
24 pub async fn post(&self, path: &str, body: Vec<u8>) -> TestResponse {
26 self.request(HttpMethod::POST, path, Some(body)).await
27 }
28
29 pub async fn put(&self, path: &str, body: Vec<u8>) -> TestResponse {
31 self.request(HttpMethod::PUT, path, Some(body)).await
32 }
33
34 pub async fn delete(&self, path: &str) -> TestResponse {
36 self.request(HttpMethod::DELETE, path, None).await
37 }
38
39 pub async fn patch(&self, path: &str, body: Vec<u8>) -> TestResponse {
41 self.request(HttpMethod::PATCH, path, Some(body)).await
42 }
43
44 pub async fn query(&self, path: &str, body: Vec<u8>) -> TestResponse {
47 self.request(HttpMethod::QUERY, path, Some(body)).await
48 }
49
50 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 match self.router.route(req).await {
62 Ok(response) => TestResponse::Success(response),
63 Err(error) => TestResponse::Error(error),
64 }
65 }
66}
67
68#[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 #[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 #[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 #[allow(dead_code)]
100 pub fn body(mut self, body: Vec<u8>) -> Self {
101 self.body = body;
102 self
103 }
104
105 #[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 #[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 #[allow(dead_code)]
123 pub fn build(self) -> HttpRequest {
124 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#[derive(Debug)]
149pub enum TestResponse {
150 Success(HttpResponse),
151 Error(Error),
152}
153
154impl TestResponse {
155 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 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 pub fn status(&self) -> Option<u16> {
177 match self {
178 TestResponse::Success(response) => Some(response.status),
179 TestResponse::Error(_) => None,
180 }
181 }
182
183 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 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 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}