1use bytes::Bytes;
6use hyper::{HeaderMap, Method, Uri, Version};
7use reinhardt_http::{Error, Request, Response, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct TestModel {
18 pub id: Option<i64>,
20 pub name: String,
22 pub slug: String,
24 pub created_at: String,
26}
27
28crate::impl_test_model!(TestModel, i64, "test_models");
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
32pub struct ApiTestModel {
33 pub id: Option<i64>,
35 pub title: String,
37 pub content: String,
39}
40
41crate::impl_test_model!(ApiTestModel, i64, "api_test_models");
42
43pub fn create_request(
49 method: Method,
50 path: &str,
51 query_params: Option<HashMap<String, String>>,
52 headers: Option<HeaderMap>,
53 body: Option<Bytes>,
54) -> Request {
55 let uri_str = if let Some(ref params) = query_params {
57 let query = params
58 .iter()
59 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
60 .collect::<Vec<_>>()
61 .join("&");
62 format!("{}?{}", path, query)
63 } else {
64 path.to_string()
65 };
66
67 let uri = uri_str.parse::<Uri>().unwrap();
68 Request::builder()
69 .method(method)
70 .uri(uri)
71 .version(Version::HTTP_11)
72 .headers(headers.unwrap_or_default())
73 .body(body.unwrap_or_default())
74 .build()
75 .expect("Failed to build request")
76}
77
78pub fn create_request_with_path_params(
80 method: Method,
81 path: &str,
82 path_params: HashMap<String, String>,
83 query_params: Option<HashMap<String, String>>,
84 headers: Option<HeaderMap>,
85 body: Option<Bytes>,
86) -> Request {
87 let mut request = create_request(method, path, query_params, headers, body);
88 request.path_params = path_params;
89 request
90}
91
92pub fn create_request_with_headers(
94 method: Method,
95 path: &str,
96 headers: HashMap<String, String>,
97 body: Option<Bytes>,
98) -> Request {
99 let mut header_map = HeaderMap::new();
100 for (key, value) in headers {
101 if let (Ok(header_name), Ok(header_value)) = (
102 hyper::header::HeaderName::from_bytes(key.as_bytes()),
103 hyper::header::HeaderValue::from_str(&value),
104 ) {
105 header_map.insert(header_name, header_value);
106 }
107 }
108
109 create_request(method, path, None, Some(header_map), body)
110}
111
112pub fn create_json_request(method: Method, path: &str, json_data: &serde_json::Value) -> Request {
114 let body = Bytes::from(serde_json::to_vec(json_data).unwrap());
115 let mut headers = HeaderMap::new();
116 headers.insert(
117 hyper::header::CONTENT_TYPE,
118 hyper::header::HeaderValue::from_static("application/json"),
119 );
120
121 create_request(method, path, None, Some(headers), Some(body))
122}
123
124pub fn create_test_objects() -> Vec<TestModel> {
130 vec![
131 TestModel {
132 id: Some(1),
133 name: "First Object".to_string(),
134 slug: "first-object".to_string(),
135 created_at: "2023-01-01T00:00:00Z".to_string(),
136 },
137 TestModel {
138 id: Some(2),
139 name: "Second Object".to_string(),
140 slug: "second-object".to_string(),
141 created_at: "2023-01-02T00:00:00Z".to_string(),
142 },
143 TestModel {
144 id: Some(3),
145 name: "Third Object".to_string(),
146 slug: "third-object".to_string(),
147 created_at: "2023-01-03T00:00:00Z".to_string(),
148 },
149 ]
150}
151
152pub fn create_api_test_objects() -> Vec<ApiTestModel> {
154 vec![
155 ApiTestModel {
156 id: Some(1),
157 title: "First Post".to_string(),
158 content: "This is the first post content".to_string(),
159 },
160 ApiTestModel {
161 id: Some(2),
162 title: "Second Post".to_string(),
163 content: "This is the second post content".to_string(),
164 },
165 ApiTestModel {
166 id: Some(3),
167 title: "Third Post".to_string(),
168 content: "This is the third post content".to_string(),
169 },
170 ]
171}
172
173pub fn create_large_test_objects(count: usize) -> Vec<TestModel> {
175 (0..count)
176 .map(|i| TestModel {
177 id: Some(i as i64),
178 name: format!("Object {}", i),
179 slug: format!("object-{}", i),
180 created_at: format!("2023-01-{:02}T00:00:00Z", (i % 30) + 1),
181 })
182 .collect()
183}
184
185pub struct SimpleTestView {
191 pub content: String,
193 pub allowed_methods: Vec<Method>,
195}
196
197impl SimpleTestView {
198 pub fn new(content: &str) -> Self {
200 Self {
201 content: content.to_string(),
202 allowed_methods: vec![Method::GET],
203 }
204 }
205
206 pub fn with_methods(mut self, methods: Vec<Method>) -> Self {
208 self.allowed_methods = methods;
209 self
210 }
211}
212
213#[async_trait::async_trait]
214impl reinhardt_views::View for SimpleTestView {
215 async fn dispatch(&self, request: Request) -> Result<Response> {
216 if !self.allowed_methods.contains(&request.method) {
217 return Err(Error::Validation(format!(
218 "Method {} not allowed",
219 request.method
220 )));
221 }
222
223 Ok(Response::ok().with_body(self.content.clone().into_bytes()))
224 }
225}
226
227pub struct ErrorTestView {
229 pub error_message: String,
231 pub error_kind: ErrorKind,
233}
234
235pub enum ErrorKind {
237 NotFound,
239 Validation,
241 Internal,
243 Authentication,
245 Authorization,
247}
248
249impl ErrorTestView {
250 pub fn new(error_message: String, error_kind: ErrorKind) -> Self {
252 Self {
253 error_message,
254 error_kind,
255 }
256 }
257
258 pub fn not_found(message: impl Into<String>) -> Self {
260 Self::new(message.into(), ErrorKind::NotFound)
261 }
262
263 pub fn validation(message: impl Into<String>) -> Self {
265 Self::new(message.into(), ErrorKind::Validation)
266 }
267}
268
269#[async_trait::async_trait]
270impl reinhardt_views::View for ErrorTestView {
271 async fn dispatch(&self, _request: Request) -> Result<Response> {
272 match self.error_kind {
273 ErrorKind::NotFound => Err(Error::NotFound(self.error_message.clone())),
274 ErrorKind::Validation => Err(Error::Validation(self.error_message.clone())),
275 ErrorKind::Internal => Err(Error::Internal(self.error_message.clone())),
276 ErrorKind::Authentication => Err(Error::Authentication(self.error_message.clone())),
277 ErrorKind::Authorization => Err(Error::Authorization(self.error_message.clone())),
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use rstest::rstest;
286
287 #[rstest]
292 fn test_create_request_basic_get() {
293 let method = Method::GET;
295 let path = "/api/items/";
296
297 let request = create_request(method.clone(), path, None, None, None);
299
300 assert_eq!(request.method, Method::GET);
302 assert_eq!(request.uri.path(), "/api/items/");
303 assert!(request.uri.query().is_none());
304 }
305
306 #[rstest]
307 fn test_create_request_with_query_params() {
308 let method = Method::GET;
310 let path = "/api/items/";
311 let mut params = HashMap::new();
312 params.insert("page".to_string(), "2".to_string());
313 params.insert("limit".to_string(), "10".to_string());
314
315 let request = create_request(method, path, Some(params), None, None);
317
318 let query = request.uri.query().expect("query string should be present");
320 assert!(query.contains("page=2"));
321 assert!(query.contains("limit=10"));
322 }
323
324 #[rstest]
325 fn test_create_request_with_body() {
326 let method = Method::POST;
328 let path = "/api/items/";
329 let body = Bytes::from(b"hello body".to_vec());
330
331 let request = create_request(method, path, None, None, Some(body.clone()));
333
334 assert_eq!(request.method, Method::POST);
336 assert_eq!(request.body(), &body);
337 }
338
339 #[rstest]
340 fn test_create_request_with_headers_param() {
341 let method = Method::GET;
343 let path = "/api/items/";
344 let mut headers = HeaderMap::new();
345 headers.insert(
346 hyper::header::ACCEPT,
347 hyper::header::HeaderValue::from_static("application/json"),
348 );
349
350 let request = create_request(method, path, None, Some(headers), None);
352
353 assert_eq!(
355 request.headers.get(hyper::header::ACCEPT).unwrap(),
356 "application/json"
357 );
358 }
359
360 #[rstest]
361 fn test_create_request_with_path_params() {
362 let method = Method::GET;
364 let path = "/api/items/1/";
365 let mut path_params = HashMap::new();
366 path_params.insert("id".to_string(), "1".to_string());
367
368 let request =
370 create_request_with_path_params(method, path, path_params.clone(), None, None, None);
371
372 assert_eq!(request.path_params.get("id").unwrap(), "1");
374 assert_eq!(request.path_params.len(), 1);
375 }
376
377 #[rstest]
378 fn test_create_request_with_headers_fn() {
379 let method = Method::POST;
381 let path = "/api/items/";
382 let mut headers = HashMap::new();
383 headers.insert("x-custom-header".to_string(), "custom-value".to_string());
384 headers.insert("authorization".to_string(), "Bearer token123".to_string());
385
386 let request = create_request_with_headers(method, path, headers, None);
388
389 assert_eq!(
391 request.headers.get("x-custom-header").unwrap(),
392 "custom-value"
393 );
394 assert_eq!(
395 request.headers.get("authorization").unwrap(),
396 "Bearer token123"
397 );
398 }
399
400 #[rstest]
401 fn test_create_json_request() {
402 let method = Method::POST;
404 let path = "/api/items/";
405 let json_data = serde_json::json!({"name": "test", "value": 42});
406
407 let request = create_json_request(method, path, &json_data);
409
410 assert_eq!(request.method, Method::POST);
412 assert_eq!(
413 request.headers.get(hyper::header::CONTENT_TYPE).unwrap(),
414 "application/json"
415 );
416 let body_bytes = request.body();
417 let parsed: serde_json::Value = serde_json::from_slice(body_bytes).unwrap();
418 assert_eq!(parsed, json_data);
419 }
420
421 #[rstest]
426 fn test_create_test_objects_count() {
427 let objects = create_test_objects();
429
430 assert_eq!(objects.len(), 3);
432 }
433
434 #[rstest]
435 fn test_create_test_objects_fields() {
436 let objects = create_test_objects();
438
439 for (i, obj) in objects.iter().enumerate() {
441 assert_eq!(obj.id, Some((i + 1) as i64));
442 assert!(
443 !obj.name.is_empty(),
444 "name should not be empty for object {}",
445 i
446 );
447 assert!(
448 !obj.slug.is_empty(),
449 "slug should not be empty for object {}",
450 i
451 );
452 assert!(
453 !obj.created_at.is_empty(),
454 "created_at should not be empty for object {}",
455 i
456 );
457 }
458 }
459
460 #[rstest]
461 fn test_create_api_test_objects_count() {
462 let objects = create_api_test_objects();
464
465 assert_eq!(objects.len(), 3);
467 }
468
469 #[rstest]
470 fn test_create_large_test_objects_100() {
471 let count = 100;
473
474 let objects = create_large_test_objects(count);
476
477 assert_eq!(objects.len(), 100);
479 for (i, obj) in objects.iter().enumerate() {
480 assert_eq!(obj.id, Some(i as i64));
481 assert_eq!(obj.name, format!("Object {}", i));
482 assert_eq!(obj.slug, format!("object-{}", i));
483 }
484 }
485
486 #[rstest]
491 #[tokio::test]
492 async fn test_simple_test_view_new_dispatch_get() {
493 let view = SimpleTestView::new("Hello, World!");
495 let request = create_request(Method::GET, "/test/", None, None, None);
496
497 let response = reinhardt_views::View::dispatch(&view, request).await;
499
500 assert!(response.is_ok());
502 let resp = response.unwrap();
503 assert_eq!(resp.body.as_ref(), b"Hello, World!");
504 }
505
506 #[rstest]
507 fn test_simple_test_view_with_methods() {
508 let view = SimpleTestView::new("content").with_methods(vec![
510 Method::GET,
511 Method::POST,
512 Method::PUT,
513 ]);
514
515 assert_eq!(view.allowed_methods.len(), 3);
517 assert!(view.allowed_methods.contains(&Method::GET));
518 assert!(view.allowed_methods.contains(&Method::POST));
519 assert!(view.allowed_methods.contains(&Method::PUT));
520 }
521
522 #[rstest]
523 #[tokio::test]
524 async fn test_simple_test_view_method_not_allowed() {
525 let view = SimpleTestView::new("content");
527 let request = create_request(Method::POST, "/test/", None, None, None);
528
529 let result = reinhardt_views::View::dispatch(&view, request).await;
531
532 assert!(result.is_err());
534 let err = result.unwrap_err();
535 let err_msg = err.to_string();
536 assert!(
537 err_msg.contains("Method POST not allowed"),
538 "Expected method not allowed error, got: {}",
539 err_msg
540 );
541 }
542
543 #[rstest]
548 #[tokio::test]
549 async fn test_error_test_view_not_found() {
550 let view = ErrorTestView::not_found("Resource not found");
552 let request = create_request(Method::GET, "/missing/", None, None, None);
553
554 let result = reinhardt_views::View::dispatch(&view, request).await;
556
557 assert!(result.is_err());
559 let err = result.unwrap_err();
560 assert!(err.to_string().contains("Resource not found"));
561 }
562
563 #[rstest]
564 #[tokio::test]
565 async fn test_error_test_view_validation() {
566 let view = ErrorTestView::validation("Invalid input data");
568 let request = create_request(Method::POST, "/validate/", None, None, None);
569
570 let result = reinhardt_views::View::dispatch(&view, request).await;
572
573 assert!(result.is_err());
575 let err = result.unwrap_err();
576 assert!(err.to_string().contains("Invalid input data"));
577 }
578
579 #[rstest]
580 #[tokio::test]
581 async fn test_error_test_view_internal() {
582 let view = ErrorTestView::new("Server failure".to_string(), ErrorKind::Internal);
584 let request = create_request(Method::GET, "/error/", None, None, None);
585
586 let result = reinhardt_views::View::dispatch(&view, request).await;
588
589 assert!(result.is_err());
591 let err = result.unwrap_err();
592 assert!(err.to_string().contains("Server failure"));
593 }
594
595 #[rstest]
596 #[tokio::test]
597 async fn test_error_test_view_authentication() {
598 let view = ErrorTestView::new("Not authenticated".to_string(), ErrorKind::Authentication);
600 let request = create_request(Method::GET, "/protected/", None, None, None);
601
602 let result = reinhardt_views::View::dispatch(&view, request).await;
604
605 assert!(result.is_err());
607 let err = result.unwrap_err();
608 assert!(err.to_string().contains("Not authenticated"));
609 }
610
611 #[rstest]
612 #[tokio::test]
613 async fn test_error_test_view_authorization() {
614 let view = ErrorTestView::new("Forbidden".to_string(), ErrorKind::Authorization);
616 let request = create_request(Method::GET, "/admin/", None, None, None);
617
618 let result = reinhardt_views::View::dispatch(&view, request).await;
620
621 assert!(result.is_err());
623 let err = result.unwrap_err();
624 assert!(err.to_string().contains("Forbidden"));
625 }
626
627 #[rstest]
632 fn test_create_request_empty_query_params() {
633 let params: HashMap<String, String> = HashMap::new();
635
636 let request = create_request(Method::GET, "/api/items/", Some(params), None, None);
638
639 let uri_str = request.uri.to_string();
642 assert!(
643 uri_str == "/api/items/?" || uri_str == "/api/items/",
644 "URI should be path with empty or no query: {}",
645 uri_str
646 );
647 }
648
649 #[rstest]
650 fn test_create_request_query_special_chars() {
651 let mut params = HashMap::new();
653 params.insert("search".to_string(), "hello world&foo=bar".to_string());
654
655 let request = create_request(Method::GET, "/api/search/", Some(params), None, None);
657
658 let query = request.uri.query().expect("query string should be present");
660 assert!(
662 query.contains("hello%20world%26foo%3Dbar"),
663 "Special characters should be URL-encoded, got: {}",
664 query
665 );
666 }
667
668 #[rstest]
669 fn test_create_large_test_objects_zero() {
670 let objects = create_large_test_objects(0);
672
673 assert!(objects.is_empty());
675 }
676
677 #[rstest]
678 fn test_test_model_serialization() {
679 let model = TestModel {
681 id: Some(42),
682 name: "Test Item".to_string(),
683 slug: "test-item".to_string(),
684 created_at: "2023-06-15T12:00:00Z".to_string(),
685 };
686
687 let json = serde_json::to_string(&model).unwrap();
689 let deserialized: TestModel = serde_json::from_str(&json).unwrap();
690
691 assert_eq!(model, deserialized);
693 }
694}