Skip to main content

reinhardt_test/
factory.rs

1//! Request factory for creating test requests
2//!
3//! Similar to DRF's APIRequestFactory
4
5use bytes::Bytes;
6use http::{HeaderMap, HeaderValue, Method, Request};
7use http_body_util::Full;
8use serde::Serialize;
9use serde_json::Value;
10use std::collections::HashMap;
11
12use crate::client::ClientError;
13
14/// Factory for creating test requests
15pub struct APIRequestFactory {
16	default_format: String,
17	default_headers: HeaderMap,
18}
19
20impl APIRequestFactory {
21	/// Create a new request factory
22	///
23	/// # Examples
24	///
25	/// ```
26	/// use reinhardt_test::factory::APIRequestFactory;
27	///
28	/// let factory = APIRequestFactory::new();
29	/// let request = factory.get("/api/users/").build();
30	/// ```
31	pub fn new() -> Self {
32		Self {
33			default_format: "json".to_string(),
34			default_headers: HeaderMap::new(),
35		}
36	}
37	pub fn with_format(mut self, format: impl Into<String>) -> Self {
38		self.default_format = format.into();
39		self
40	}
41	pub fn with_header(
42		mut self,
43		name: impl AsRef<str>,
44		value: impl AsRef<str>,
45	) -> Result<Self, ClientError> {
46		let header_name: http::header::HeaderName = name.as_ref().parse().map_err(|_| {
47			ClientError::RequestFailed(format!("Invalid header name: {}", name.as_ref()))
48		})?;
49		self.default_headers
50			.insert(header_name, HeaderValue::from_str(value.as_ref())?);
51		Ok(self)
52	}
53	/// Create a GET request
54	///
55	/// # Examples
56	///
57	/// ```
58	/// use reinhardt_test::factory::APIRequestFactory;
59	///
60	/// let factory = APIRequestFactory::new();
61	/// let request = factory.get("/api/users/").build().unwrap();
62	/// assert_eq!(request.method(), "GET");
63	/// ```
64	pub fn get(&self, path: &str) -> RequestBuilder {
65		RequestBuilder::new(Method::GET, path, &self.default_headers)
66	}
67	/// Create a POST request
68	///
69	/// # Examples
70	///
71	/// ```
72	/// use reinhardt_test::factory::APIRequestFactory;
73	/// use serde_json::json;
74	///
75	/// let factory = APIRequestFactory::new();
76	/// let data = json!({"name": "test"});
77	/// let request = factory.post("/api/users/").json(&data).unwrap().build().unwrap();
78	/// assert_eq!(request.method(), "POST");
79	/// ```
80	pub fn post(&self, path: &str) -> RequestBuilder {
81		RequestBuilder::new(Method::POST, path, &self.default_headers)
82			.with_format(&self.default_format)
83	}
84	/// Create a PUT request
85	///
86	/// # Examples
87	///
88	/// ```
89	/// use reinhardt_test::factory::APIRequestFactory;
90	/// use serde_json::json;
91	///
92	/// let factory = APIRequestFactory::new();
93	/// let data = json!({"name": "updated"});
94	/// let request = factory.put("/api/users/1/").json(&data).unwrap().build().unwrap();
95	/// assert_eq!(request.method(), "PUT");
96	/// ```
97	pub fn put(&self, path: &str) -> RequestBuilder {
98		RequestBuilder::new(Method::PUT, path, &self.default_headers)
99			.with_format(&self.default_format)
100	}
101	/// Create a PATCH request
102	///
103	/// # Examples
104	///
105	/// ```
106	/// use reinhardt_test::factory::APIRequestFactory;
107	/// use serde_json::json;
108	///
109	/// let factory = APIRequestFactory::new();
110	/// let data = json!({"name": "partial_update"});
111	/// let request = factory.patch("/api/users/1/").json(&data).unwrap().build().unwrap();
112	/// assert_eq!(request.method(), "PATCH");
113	/// ```
114	pub fn patch(&self, path: &str) -> RequestBuilder {
115		RequestBuilder::new(Method::PATCH, path, &self.default_headers)
116			.with_format(&self.default_format)
117	}
118	/// Create a DELETE request
119	///
120	/// # Examples
121	///
122	/// ```
123	/// use reinhardt_test::factory::APIRequestFactory;
124	///
125	/// let factory = APIRequestFactory::new();
126	/// let request = factory.delete("/api/users/1/").build().unwrap();
127	/// assert_eq!(request.method(), "DELETE");
128	/// ```
129	pub fn delete(&self, path: &str) -> RequestBuilder {
130		RequestBuilder::new(Method::DELETE, path, &self.default_headers)
131	}
132	/// Create a HEAD request
133	///
134	/// # Examples
135	///
136	/// ```
137	/// use reinhardt_test::factory::APIRequestFactory;
138	///
139	/// let factory = APIRequestFactory::new();
140	/// let request = factory.head("/api/users/").build().unwrap();
141	/// assert_eq!(request.method(), "HEAD");
142	/// ```
143	pub fn head(&self, path: &str) -> RequestBuilder {
144		RequestBuilder::new(Method::HEAD, path, &self.default_headers)
145	}
146	/// Create an OPTIONS request
147	///
148	/// # Examples
149	///
150	/// ```
151	/// use reinhardt_test::factory::APIRequestFactory;
152	///
153	/// let factory = APIRequestFactory::new();
154	/// let request = factory.options("/api/users/").build().unwrap();
155	/// assert_eq!(request.method(), "OPTIONS");
156	/// ```
157	pub fn options(&self, path: &str) -> RequestBuilder {
158		RequestBuilder::new(Method::OPTIONS, path, &self.default_headers)
159	}
160	/// Create a generic request with custom method
161	///
162	/// # Examples
163	///
164	/// ```
165	/// use reinhardt_test::factory::APIRequestFactory;
166	/// use http::Method;
167	///
168	/// let factory = APIRequestFactory::new();
169	/// let request = factory.request(Method::TRACE, "/api/trace/").build().unwrap();
170	/// assert_eq!(request.method(), "TRACE");
171	/// ```
172	pub fn request(&self, method: Method, path: &str) -> RequestBuilder {
173		RequestBuilder::new(method, path, &self.default_headers)
174	}
175}
176
177impl Default for APIRequestFactory {
178	fn default() -> Self {
179		Self::new()
180	}
181}
182
183/// Builder for constructing test requests
184pub struct RequestBuilder {
185	method: Method,
186	path: String,
187	headers: HeaderMap,
188	query_params: HashMap<String, String>,
189	body: Option<Bytes>,
190	format: String,
191	user: Option<Value>,
192}
193
194impl RequestBuilder {
195	pub fn new(method: Method, path: &str, default_headers: &HeaderMap) -> Self {
196		Self {
197			method,
198			path: path.to_string(),
199			headers: default_headers.clone(),
200			query_params: HashMap::new(),
201			body: None,
202			format: "json".to_string(),
203			user: None,
204		}
205	}
206	pub fn method(&self) -> Method {
207		self.method.clone()
208	}
209	pub fn path(&self) -> &str {
210		&self.path
211	}
212	pub fn with_format(mut self, format: &str) -> Self {
213		self.format = format.to_string();
214		self
215	}
216	// Fixes #865
217	pub fn header(mut self, name: &str, value: &str) -> Result<Self, ClientError> {
218		let header_name: http::header::HeaderName = name
219			.parse()
220			.map_err(|_| ClientError::RequestFailed(format!("Invalid header name: {}", name)))?;
221		self.headers
222			.insert(header_name, HeaderValue::from_str(value)?);
223		Ok(self)
224	}
225	pub fn query(mut self, key: &str, value: &str) -> Self {
226		self.query_params.insert(key.to_string(), value.to_string());
227		self
228	}
229	pub fn query_param(self, key: &str, value: &str) -> Self {
230		self.query(key, value)
231	}
232	/// Set request body as JSON
233	///
234	/// # Examples
235	///
236	/// ```
237	/// use reinhardt_test::factory::APIRequestFactory;
238	/// use serde_json::json;
239	///
240	/// let factory = APIRequestFactory::new();
241	/// let data = json!({"name": "test"});
242	/// let request = factory.post("/api/users/").json(&data).unwrap().build();
243	/// ```
244	pub fn json<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
245		let json = serde_json::to_vec(data)?;
246		self.body = Some(Bytes::from(json));
247		self.format = "json".to_string();
248		Ok(self)
249	}
250	/// Set request body as form data
251	///
252	/// # Examples
253	///
254	/// ```
255	/// use reinhardt_test::factory::APIRequestFactory;
256	/// use serde_json::json;
257	///
258	/// let factory = APIRequestFactory::new();
259	/// let data = json!({"name": "test", "age": 30});
260	/// let request = factory.post("/api/users/").form(&data).unwrap().build();
261	/// ```
262	pub fn form<T: Serialize>(mut self, data: &T) -> Result<Self, ClientError> {
263		let json_value = serde_json::to_value(data)?;
264		if let Value::Object(map) = json_value {
265			let form_data = map
266				.iter()
267				.map(|(k, v)| {
268					let value_str = match v {
269						Value::String(s) => s.clone(),
270						_ => v.to_string(),
271					};
272					format!(
273						"{}={}",
274						url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
275						url::form_urlencoded::byte_serialize(value_str.as_bytes())
276							.collect::<String>()
277					)
278				})
279				.collect::<Vec<_>>()
280				.join("&");
281			self.body = Some(Bytes::from(form_data));
282			self.format = "form".to_string();
283			Ok(self)
284		} else {
285			Err(ClientError::RequestFailed(
286				"Expected object for form data".to_string(),
287			))
288		}
289	}
290	/// Set raw body
291	///
292	/// # Examples
293	///
294	/// ```
295	/// use reinhardt_test::factory::APIRequestFactory;
296	///
297	/// let factory = APIRequestFactory::new();
298	/// let request = factory.post("/api/upload/").body("raw data").build().unwrap();
299	/// ```
300	pub fn body(mut self, body: impl Into<Bytes>) -> Self {
301		self.body = Some(body.into());
302		self
303	}
304	/// Force authenticate as user (for testing)
305	///
306	/// # Examples
307	///
308	/// ```
309	/// use reinhardt_test::factory::APIRequestFactory;
310	/// use serde_json::json;
311	///
312	/// let factory = APIRequestFactory::new();
313	/// let user = json!({"id": 1, "username": "testuser"});
314	/// let request = factory.get("/api/profile/").force_authenticate(user).build().unwrap();
315	/// ```
316	pub fn force_authenticate(mut self, user: Value) -> Self {
317		self.user = Some(user);
318		self
319	}
320	/// Build the request
321	///
322	/// # Examples
323	///
324	/// ```
325	/// use reinhardt_test::factory::APIRequestFactory;
326	///
327	/// let factory = APIRequestFactory::new();
328	/// let request = factory.get("/api/users/").build().unwrap();
329	/// assert_eq!(request.method(), "GET");
330	/// ```
331	pub fn build(self) -> Result<Request<Full<Bytes>>, ClientError> {
332		let mut url = self.path.clone();
333
334		// Add query parameters
335		if !self.query_params.is_empty() {
336			let query_string = self
337				.query_params
338				.iter()
339				.map(|(k, v)| {
340					format!(
341						"{}={}",
342						url::form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
343						url::form_urlencoded::byte_serialize(v.as_bytes()).collect::<String>()
344					)
345				})
346				.collect::<Vec<_>>()
347				.join("&");
348			url = format!("{}?{}", url, query_string);
349		}
350
351		let mut request = Request::builder().method(self.method).uri(url);
352
353		// Add headers
354		for (name, value) in self.headers.iter() {
355			request = request.header(name, value);
356		}
357
358		// Add content type based on format
359		if self.body.is_some() {
360			let content_type = match self.format.as_str() {
361				"json" => "application/json",
362				"form" => "application/x-www-form-urlencoded",
363				_ => "application/octet-stream",
364			};
365			request = request.header("Content-Type", content_type);
366		}
367
368		// Add authentication marker if user is set
369		if self.user.is_some() {
370			request = request.header("X-Test-User", "authenticated");
371		}
372
373		// Build request with body
374		let body = self.body.unwrap_or_default();
375		let req = request.body(Full::new(body))?;
376
377		Ok(req)
378	}
379}