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.into();
94 request
95}
96
97pub fn create_request_with_headers(
99 method: Method,
100 path: &str,
101 headers: HashMap<String, String>,
102 body: Option<Bytes>,
103) -> Request {
104 let mut header_map = HeaderMap::new();
105 for (key, value) in headers {
106 if let (Ok(header_name), Ok(header_value)) = (
107 hyper::header::HeaderName::from_bytes(key.as_bytes()),
108 hyper::header::HeaderValue::from_str(&value),
109 ) {
110 header_map.insert(header_name, header_value);
111 }
112 }
113
114 create_request(method, path, None, Some(header_map), body)
115}
116
117pub fn create_json_request(method: Method, path: &str, json_data: &serde_json::Value) -> Request {
119 let body = Bytes::from(serde_json::to_vec(json_data).unwrap());
120 let mut headers = HeaderMap::new();
121 headers.insert(
122 hyper::header::CONTENT_TYPE,
123 hyper::header::HeaderValue::from_static("application/json"),
124 );
125
126 create_request(method, path, None, Some(headers), Some(body))
127}
128
129pub fn create_test_objects() -> Vec<TestModel> {
135 vec![
136 TestModel {
137 id: Some(1),
138 name: "First Object".to_string(),
139 slug: "first-object".to_string(),
140 created_at: "2023-01-01T00:00:00Z".to_string(),
141 },
142 TestModel {
143 id: Some(2),
144 name: "Second Object".to_string(),
145 slug: "second-object".to_string(),
146 created_at: "2023-01-02T00:00:00Z".to_string(),
147 },
148 TestModel {
149 id: Some(3),
150 name: "Third Object".to_string(),
151 slug: "third-object".to_string(),
152 created_at: "2023-01-03T00:00:00Z".to_string(),
153 },
154 ]
155}
156
157pub fn create_api_test_objects() -> Vec<ApiTestModel> {
159 vec![
160 ApiTestModel {
161 id: Some(1),
162 title: "First Post".to_string(),
163 content: "This is the first post content".to_string(),
164 },
165 ApiTestModel {
166 id: Some(2),
167 title: "Second Post".to_string(),
168 content: "This is the second post content".to_string(),
169 },
170 ApiTestModel {
171 id: Some(3),
172 title: "Third Post".to_string(),
173 content: "This is the third post content".to_string(),
174 },
175 ]
176}
177
178pub fn create_large_test_objects(count: usize) -> Vec<TestModel> {
180 (0..count)
181 .map(|i| TestModel {
182 id: Some(i as i64),
183 name: format!("Object {}", i),
184 slug: format!("object-{}", i),
185 created_at: format!("2023-01-{:02}T00:00:00Z", (i % 30) + 1),
186 })
187 .collect()
188}
189
190pub struct SimpleTestView {
196 pub content: String,
198 pub allowed_methods: Vec<Method>,
200}
201
202impl SimpleTestView {
203 pub fn new(content: &str) -> Self {
205 Self {
206 content: content.to_string(),
207 allowed_methods: vec![Method::GET],
208 }
209 }
210
211 pub fn with_methods(mut self, methods: Vec<Method>) -> Self {
213 self.allowed_methods = methods;
214 self
215 }
216}
217
218#[async_trait::async_trait]
219impl reinhardt_views::View for SimpleTestView {
220 async fn dispatch(&self, request: Request) -> Result<Response> {
221 if !self.allowed_methods.contains(&request.method) {
222 return Err(Error::Validation(format!(
223 "Method {} not allowed",
224 request.method
225 )));
226 }
227
228 Ok(Response::ok().with_body(self.content.clone().into_bytes()))
229 }
230}
231
232pub struct ErrorTestView {
234 pub error_message: String,
236 pub error_kind: ErrorKind,
238}
239
240pub enum ErrorKind {
242 NotFound,
244 Validation,
246 Internal,
248 Authentication,
250 Authorization,
252}
253
254impl ErrorTestView {
255 pub fn new(error_message: String, error_kind: ErrorKind) -> Self {
257 Self {
258 error_message,
259 error_kind,
260 }
261 }
262
263 pub fn not_found(message: impl Into<String>) -> Self {
265 Self::new(message.into(), ErrorKind::NotFound)
266 }
267
268 pub fn validation(message: impl Into<String>) -> Self {
270 Self::new(message.into(), ErrorKind::Validation)
271 }
272}
273
274#[async_trait::async_trait]
275impl reinhardt_views::View for ErrorTestView {
276 async fn dispatch(&self, _request: Request) -> Result<Response> {
277 match self.error_kind {
278 ErrorKind::NotFound => Err(Error::NotFound(self.error_message.clone())),
279 ErrorKind::Validation => Err(Error::Validation(self.error_message.clone())),
280 ErrorKind::Internal => Err(Error::Internal(self.error_message.clone())),
281 ErrorKind::Authentication => Err(Error::Authentication(self.error_message.clone())),
282 ErrorKind::Authorization => Err(Error::Authorization(self.error_message.clone())),
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use rstest::rstest;
291
292 #[rstest]
297 fn test_create_request_basic_get() {
298 let method = Method::GET;
300 let path = "/api/items/";
301
302 let request = create_request(method.clone(), path, None, None, None);
304
305 assert_eq!(request.method, Method::GET);
307 assert_eq!(request.uri.path(), "/api/items/");
308 assert!(request.uri.query().is_none());
309 }
310
311 #[rstest]
312 fn test_create_request_with_query_params() {
313 let method = Method::GET;
315 let path = "/api/items/";
316 let mut params = HashMap::new();
317 params.insert("page".to_string(), "2".to_string());
318 params.insert("limit".to_string(), "10".to_string());
319
320 let request = create_request(method, path, Some(params), None, None);
322
323 let query = request.uri.query().expect("query string should be present");
325 assert!(query.contains("page=2"));
326 assert!(query.contains("limit=10"));
327 }
328
329 #[rstest]
330 fn test_create_request_with_body() {
331 let method = Method::POST;
333 let path = "/api/items/";
334 let body = Bytes::from(b"hello body".to_vec());
335
336 let request = create_request(method, path, None, None, Some(body.clone()));
338
339 assert_eq!(request.method, Method::POST);
341 assert_eq!(request.body(), &body);
342 }
343
344 #[rstest]
345 fn test_create_request_with_headers_param() {
346 let method = Method::GET;
348 let path = "/api/items/";
349 let mut headers = HeaderMap::new();
350 headers.insert(
351 hyper::header::ACCEPT,
352 hyper::header::HeaderValue::from_static("application/json"),
353 );
354
355 let request = create_request(method, path, None, Some(headers), None);
357
358 assert_eq!(
360 request.headers.get(hyper::header::ACCEPT).unwrap(),
361 "application/json"
362 );
363 }
364
365 #[rstest]
366 fn test_create_request_with_path_params() {
367 let method = Method::GET;
369 let path = "/api/items/1/";
370 let mut path_params = HashMap::new();
371 path_params.insert("id".to_string(), "1".to_string());
372
373 let request =
375 create_request_with_path_params(method, path, path_params.clone(), None, None, None);
376
377 assert_eq!(request.path_params.get("id").unwrap(), "1");
379 assert_eq!(request.path_params.len(), 1);
380 }
381
382 #[rstest]
383 fn test_create_request_with_headers_fn() {
384 let method = Method::POST;
386 let path = "/api/items/";
387 let mut headers = HashMap::new();
388 headers.insert("x-custom-header".to_string(), "custom-value".to_string());
389 headers.insert("authorization".to_string(), "Bearer token123".to_string());
390
391 let request = create_request_with_headers(method, path, headers, None);
393
394 assert_eq!(
396 request.headers.get("x-custom-header").unwrap(),
397 "custom-value"
398 );
399 assert_eq!(
400 request.headers.get("authorization").unwrap(),
401 "Bearer token123"
402 );
403 }
404
405 #[rstest]
406 fn test_create_json_request() {
407 let method = Method::POST;
409 let path = "/api/items/";
410 let json_data = serde_json::json!({"name": "test", "value": 42});
411
412 let request = create_json_request(method, path, &json_data);
414
415 assert_eq!(request.method, Method::POST);
417 assert_eq!(
418 request.headers.get(hyper::header::CONTENT_TYPE).unwrap(),
419 "application/json"
420 );
421 let body_bytes = request.body();
422 let parsed: serde_json::Value = serde_json::from_slice(body_bytes).unwrap();
423 assert_eq!(parsed, json_data);
424 }
425
426 #[rstest]
431 fn test_create_test_objects_count() {
432 let objects = create_test_objects();
434
435 assert_eq!(objects.len(), 3);
437 }
438
439 #[rstest]
440 fn test_create_test_objects_fields() {
441 let objects = create_test_objects();
443
444 for (i, obj) in objects.iter().enumerate() {
446 assert_eq!(obj.id, Some((i + 1) as i64));
447 assert!(
448 !obj.name.is_empty(),
449 "name should not be empty for object {}",
450 i
451 );
452 assert!(
453 !obj.slug.is_empty(),
454 "slug should not be empty for object {}",
455 i
456 );
457 assert!(
458 !obj.created_at.is_empty(),
459 "created_at should not be empty for object {}",
460 i
461 );
462 }
463 }
464
465 #[rstest]
466 fn test_create_api_test_objects_count() {
467 let objects = create_api_test_objects();
469
470 assert_eq!(objects.len(), 3);
472 }
473
474 #[rstest]
475 fn test_create_large_test_objects_100() {
476 let count = 100;
478
479 let objects = create_large_test_objects(count);
481
482 assert_eq!(objects.len(), 100);
484 for (i, obj) in objects.iter().enumerate() {
485 assert_eq!(obj.id, Some(i as i64));
486 assert_eq!(obj.name, format!("Object {}", i));
487 assert_eq!(obj.slug, format!("object-{}", i));
488 }
489 }
490
491 #[rstest]
496 #[tokio::test]
497 async fn test_simple_test_view_new_dispatch_get() {
498 let view = SimpleTestView::new("Hello, World!");
500 let request = create_request(Method::GET, "/test/", None, None, None);
501
502 let response = reinhardt_views::View::dispatch(&view, request).await;
504
505 assert!(response.is_ok());
507 let resp = response.unwrap();
508 assert_eq!(resp.body.as_ref(), b"Hello, World!");
509 }
510
511 #[rstest]
512 fn test_simple_test_view_with_methods() {
513 let view = SimpleTestView::new("content").with_methods(vec![
515 Method::GET,
516 Method::POST,
517 Method::PUT,
518 ]);
519
520 assert_eq!(view.allowed_methods.len(), 3);
522 assert!(view.allowed_methods.contains(&Method::GET));
523 assert!(view.allowed_methods.contains(&Method::POST));
524 assert!(view.allowed_methods.contains(&Method::PUT));
525 }
526
527 #[rstest]
528 #[tokio::test]
529 async fn test_simple_test_view_method_not_allowed() {
530 let view = SimpleTestView::new("content");
532 let request = create_request(Method::POST, "/test/", None, None, None);
533
534 let result = reinhardt_views::View::dispatch(&view, request).await;
536
537 assert!(result.is_err());
539 let err = result.unwrap_err();
540 let err_msg = err.to_string();
541 assert!(
542 err_msg.contains("Method POST not allowed"),
543 "Expected method not allowed error, got: {}",
544 err_msg
545 );
546 }
547
548 #[rstest]
553 #[tokio::test]
554 async fn test_error_test_view_not_found() {
555 let view = ErrorTestView::not_found("Resource not found");
557 let request = create_request(Method::GET, "/missing/", None, None, None);
558
559 let result = reinhardt_views::View::dispatch(&view, request).await;
561
562 assert!(result.is_err());
564 let err = result.unwrap_err();
565 assert!(err.to_string().contains("Resource not found"));
566 }
567
568 #[rstest]
569 #[tokio::test]
570 async fn test_error_test_view_validation() {
571 let view = ErrorTestView::validation("Invalid input data");
573 let request = create_request(Method::POST, "/validate/", None, None, None);
574
575 let result = reinhardt_views::View::dispatch(&view, request).await;
577
578 assert!(result.is_err());
580 let err = result.unwrap_err();
581 assert!(err.to_string().contains("Invalid input data"));
582 }
583
584 #[rstest]
585 #[tokio::test]
586 async fn test_error_test_view_internal() {
587 let view = ErrorTestView::new("Server failure".to_string(), ErrorKind::Internal);
589 let request = create_request(Method::GET, "/error/", None, None, None);
590
591 let result = reinhardt_views::View::dispatch(&view, request).await;
593
594 assert!(result.is_err());
596 let err = result.unwrap_err();
597 assert!(err.to_string().contains("Server failure"));
598 }
599
600 #[rstest]
601 #[tokio::test]
602 async fn test_error_test_view_authentication() {
603 let view = ErrorTestView::new("Not authenticated".to_string(), ErrorKind::Authentication);
605 let request = create_request(Method::GET, "/protected/", None, None, None);
606
607 let result = reinhardt_views::View::dispatch(&view, request).await;
609
610 assert!(result.is_err());
612 let err = result.unwrap_err();
613 assert!(err.to_string().contains("Not authenticated"));
614 }
615
616 #[rstest]
617 #[tokio::test]
618 async fn test_error_test_view_authorization() {
619 let view = ErrorTestView::new("Forbidden".to_string(), ErrorKind::Authorization);
621 let request = create_request(Method::GET, "/admin/", None, None, None);
622
623 let result = reinhardt_views::View::dispatch(&view, request).await;
625
626 assert!(result.is_err());
628 let err = result.unwrap_err();
629 assert!(err.to_string().contains("Forbidden"));
630 }
631
632 #[rstest]
637 fn test_create_request_empty_query_params() {
638 let params: HashMap<String, String> = HashMap::new();
640
641 let request = create_request(Method::GET, "/api/items/", Some(params), None, None);
643
644 let uri_str = request.uri.to_string();
647 assert!(
648 uri_str == "/api/items/?" || uri_str == "/api/items/",
649 "URI should be path with empty or no query: {}",
650 uri_str
651 );
652 }
653
654 #[rstest]
655 fn test_create_request_query_special_chars() {
656 let mut params = HashMap::new();
658 params.insert("search".to_string(), "hello world&foo=bar".to_string());
659
660 let request = create_request(Method::GET, "/api/search/", Some(params), None, None);
662
663 let query = request.uri.query().expect("query string should be present");
665 assert!(
667 query.contains("hello%20world%26foo%3Dbar"),
668 "Special characters should be URL-encoded, got: {}",
669 query
670 );
671 }
672
673 #[rstest]
674 fn test_create_large_test_objects_zero() {
675 let objects = create_large_test_objects(0);
677
678 assert!(objects.is_empty());
680 }
681
682 #[rstest]
683 fn test_test_model_serialization() {
684 let model = TestModel {
686 id: Some(42),
687 name: "Test Item".to_string(),
688 slug: "test-item".to_string(),
689 created_at: "2023-06-15T12:00:00Z".to_string(),
690 };
691
692 let json = serde_json::to_string(&model).unwrap();
694 let deserialized: TestModel = serde_json::from_str(&json).unwrap();
695
696 assert_eq!(model, deserialized);
698 }
699}