1use bytes::Bytes;
4use http::{HeaderMap, Response, StatusCode, Version};
5use http_body_util::{BodyExt, Full};
6use serde::de::DeserializeOwned;
7use serde_json::Value;
8
9pub struct TestResponse {
11 status: StatusCode,
12 headers: HeaderMap,
13 body: Bytes,
14 version: Version,
15}
16
17impl TestResponse {
18 pub async fn new(response: Response<Full<Bytes>>) -> Self {
38 let (parts, body) = response.into_parts();
39
40 let body_bytes = body
42 .collect()
43 .await
44 .map(|collected| collected.to_bytes())
45 .unwrap_or_else(|_| Bytes::new());
46
47 Self {
48 status: parts.status,
49 headers: parts.headers,
50 body: body_bytes,
51 version: parts.version,
52 }
53 }
54
55 pub fn with_body(status: StatusCode, headers: HeaderMap, body: Bytes) -> Self {
57 Self {
58 status,
59 headers,
60 body,
61 version: Version::HTTP_11,
62 }
63 }
64
65 pub fn with_body_and_version(
67 status: StatusCode,
68 headers: HeaderMap,
69 body: Bytes,
70 version: Version,
71 ) -> Self {
72 Self {
73 status,
74 headers,
75 body,
76 version,
77 }
78 }
79 pub fn status(&self) -> StatusCode {
81 self.status
82 }
83
84 pub fn status_code(&self) -> u16 {
86 self.status.as_u16()
87 }
88
89 pub fn version(&self) -> Version {
107 self.version
108 }
109 pub fn headers(&self) -> &HeaderMap {
111 &self.headers
112 }
113 pub fn body(&self) -> &Bytes {
115 &self.body
116 }
117 pub fn text(&self) -> String {
119 String::from_utf8_lossy(&self.body).to_string()
120 }
121 pub fn json<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
123 serde_json::from_slice(&self.body)
124 }
125 pub fn json_value(&self) -> Result<Value, serde_json::Error> {
127 serde_json::from_slice(&self.body)
128 }
129 pub fn is_success(&self) -> bool {
131 self.status.is_success()
132 }
133 pub fn is_client_error(&self) -> bool {
135 self.status.is_client_error()
136 }
137 pub fn is_server_error(&self) -> bool {
139 self.status.is_server_error()
140 }
141 pub fn content_type(&self) -> Option<&str> {
143 self.headers
144 .get("content-type")
145 .and_then(|v| v.to_str().ok())
146 }
147 pub fn header(&self, name: &str) -> Option<&str> {
149 self.headers.get(name).and_then(|v| v.to_str().ok())
150 }
151}
152
153pub trait ResponseExt {
155 fn assert_status(&self, expected: StatusCode) -> &Self;
157
158 fn assert_success(&self) -> &Self;
160
161 fn assert_client_error(&self) -> &Self;
163
164 fn assert_server_error(&self) -> &Self;
166
167 fn assert_ok(&self) -> &Self;
169 fn assert_created(&self) -> &Self;
171 fn assert_no_content(&self) -> &Self;
173 fn assert_bad_request(&self) -> &Self;
175 fn assert_unauthorized(&self) -> &Self;
177 fn assert_forbidden(&self) -> &Self;
179 fn assert_not_found(&self) -> &Self;
181}
182
183impl ResponseExt for TestResponse {
184 fn assert_status(&self, expected: StatusCode) -> &Self {
185 assert_eq!(
186 self.status,
187 expected,
188 "Expected status {}, got {}. Body: {}",
189 expected,
190 self.status,
191 self.text()
192 );
193 self
194 }
195
196 fn assert_success(&self) -> &Self {
197 assert!(
198 self.is_success(),
199 "Expected success status (2xx), got {}. Body: {}",
200 self.status,
201 self.text()
202 );
203 self
204 }
205
206 fn assert_client_error(&self) -> &Self {
207 assert!(
208 self.is_client_error(),
209 "Expected client error status (4xx), got {}. Body: {}",
210 self.status,
211 self.text()
212 );
213 self
214 }
215
216 fn assert_server_error(&self) -> &Self {
217 assert!(
218 self.is_server_error(),
219 "Expected server error status (5xx), got {}. Body: {}",
220 self.status,
221 self.text()
222 );
223 self
224 }
225
226 fn assert_ok(&self) -> &Self {
227 self.assert_status(StatusCode::OK)
228 }
229
230 fn assert_created(&self) -> &Self {
231 self.assert_status(StatusCode::CREATED)
232 }
233
234 fn assert_no_content(&self) -> &Self {
235 self.assert_status(StatusCode::NO_CONTENT)
236 }
237
238 fn assert_bad_request(&self) -> &Self {
239 self.assert_status(StatusCode::BAD_REQUEST)
240 }
241
242 fn assert_unauthorized(&self) -> &Self {
243 self.assert_status(StatusCode::UNAUTHORIZED)
244 }
245
246 fn assert_forbidden(&self) -> &Self {
247 self.assert_status(StatusCode::FORBIDDEN)
248 }
249
250 fn assert_not_found(&self) -> &Self {
251 self.assert_status(StatusCode::NOT_FOUND)
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use rstest::rstest;
259
260 fn make_response(status: u16, body: &[u8]) -> TestResponse {
265 TestResponse::with_body(
266 StatusCode::from_u16(status).unwrap(),
267 HeaderMap::new(),
268 Bytes::from(body.to_vec()),
269 )
270 }
271
272 #[rstest]
277 fn test_with_body() {
278 let body = Bytes::from("hello");
280
281 let resp = TestResponse::with_body(StatusCode::OK, HeaderMap::new(), body.clone());
283
284 assert_eq!(resp.status(), StatusCode::OK);
286 assert_eq!(resp.body(), &body);
287 assert_eq!(resp.version(), Version::HTTP_11);
288 }
289
290 #[rstest]
291 fn test_with_body_and_version() {
292 let resp = TestResponse::with_body_and_version(
294 StatusCode::CREATED,
295 HeaderMap::new(),
296 Bytes::from("data"),
297 Version::HTTP_2,
298 );
299
300 assert_eq!(resp.status(), StatusCode::CREATED);
302 assert_eq!(resp.version(), Version::HTTP_2);
303 }
304
305 #[rstest]
306 #[tokio::test]
307 async fn test_new_async() {
308 let response = Response::builder()
310 .status(StatusCode::OK)
311 .body(Full::new(Bytes::from("Hello World")))
312 .unwrap();
313
314 let test_resp = TestResponse::new(response).await;
316
317 assert_eq!(test_resp.status(), StatusCode::OK);
319 assert_eq!(test_resp.text(), "Hello World");
320 }
321
322 #[rstest]
327 fn test_status() {
328 let resp = make_response(404, b"");
330
331 assert_eq!(resp.status(), StatusCode::NOT_FOUND);
333 }
334
335 #[rstest]
336 fn test_status_code() {
337 let resp = make_response(201, b"");
339
340 assert_eq!(resp.status_code(), 201);
342 }
343
344 #[rstest]
345 fn test_version() {
346 let resp = TestResponse::with_body(StatusCode::OK, HeaderMap::new(), Bytes::new());
348
349 assert_eq!(resp.version(), Version::HTTP_11);
351 }
352
353 #[rstest]
354 fn test_headers() {
355 let mut headers = HeaderMap::new();
357 headers.insert("x-custom", "value".parse().unwrap());
358 let resp = TestResponse::with_body(StatusCode::OK, headers, Bytes::new());
359
360 assert!(resp.headers().contains_key("x-custom"));
362 }
363
364 #[rstest]
365 fn test_body() {
366 let resp = make_response(200, b"body-content");
368
369 assert_eq!(resp.body().as_ref(), b"body-content");
371 }
372
373 #[rstest]
374 fn test_text() {
375 let resp = make_response(200, b"hello text");
377
378 assert_eq!(resp.text(), "hello text");
380 }
381
382 #[rstest]
383 fn test_text_non_utf8_lossy() {
384 let resp = TestResponse::with_body(
386 StatusCode::OK,
387 HeaderMap::new(),
388 Bytes::from(vec![0xFF, 0xFE, 0x68, 0x69]),
389 );
390
391 let text = resp.text();
393
394 assert!(text.contains("hi"));
396 assert!(text.contains('\u{FFFD}'));
397 }
398
399 #[rstest]
404 fn test_json_valid() {
405 #[derive(serde::Deserialize, PartialEq, Debug)]
407 struct Item {
408 id: i32,
409 }
410 let resp = make_response(200, br#"{"id": 42}"#);
411
412 let item: Item = resp.json().unwrap();
414
415 assert_eq!(item.id, 42);
417 }
418
419 #[rstest]
420 fn test_json_invalid() {
421 #[derive(serde::Deserialize)]
423 struct Item {
424 #[allow(dead_code)] id: i32,
426 }
427 let resp = make_response(200, b"not json");
428
429 let result: Result<Item, _> = resp.json();
431
432 assert!(result.is_err());
434 }
435
436 #[rstest]
437 fn test_json_value_valid() {
438 let resp = make_response(200, br#"{"key": "value"}"#);
440
441 let val = resp.json_value().unwrap();
443
444 assert_eq!(val["key"], "value");
446 }
447
448 #[rstest]
449 fn test_json_value_invalid() {
450 let resp = make_response(200, b"broken");
452
453 let result = resp.json_value();
455
456 assert!(result.is_err());
458 }
459
460 #[rstest]
465 fn test_is_success_200() {
466 assert!(make_response(200, b"").is_success());
468 }
469
470 #[rstest]
471 fn test_is_success_299() {
472 assert!(make_response(299, b"").is_success());
474 }
475
476 #[rstest]
477 fn test_is_success_boundary_199() {
478 assert!(!make_response(199, b"").is_success());
480 }
481
482 #[rstest]
483 fn test_is_success_boundary_300() {
484 assert!(!make_response(300, b"").is_success());
486 }
487
488 #[rstest]
489 fn test_is_client_error_400() {
490 assert!(make_response(400, b"").is_client_error());
492 }
493
494 #[rstest]
495 fn test_is_client_error_boundary_399() {
496 assert!(!make_response(399, b"").is_client_error());
498 }
499
500 #[rstest]
501 fn test_is_client_error_boundary_499() {
502 assert!(make_response(499, b"").is_client_error());
504 }
505
506 #[rstest]
507 fn test_is_server_error_500() {
508 assert!(make_response(500, b"").is_server_error());
510 }
511
512 #[rstest]
513 fn test_is_server_error_boundary_499() {
514 assert!(!make_response(499, b"").is_server_error());
516 }
517
518 #[rstest]
523 fn test_content_type() {
524 let mut headers = HeaderMap::new();
526 headers.insert("content-type", "application/json".parse().unwrap());
527 let resp = TestResponse::with_body(StatusCode::OK, headers, Bytes::new());
528
529 assert_eq!(resp.content_type(), Some("application/json"));
531 }
532
533 #[rstest]
534 fn test_content_type_absent() {
535 let resp = make_response(200, b"");
537
538 assert_eq!(resp.content_type(), None);
540 }
541
542 #[rstest]
543 fn test_header_present() {
544 let mut headers = HeaderMap::new();
546 headers.insert("x-request-id", "abc123".parse().unwrap());
547 let resp = TestResponse::with_body(StatusCode::OK, headers, Bytes::new());
548
549 assert_eq!(resp.header("x-request-id"), Some("abc123"));
551 }
552
553 #[rstest]
554 fn test_header_absent() {
555 let resp = make_response(200, b"");
557
558 assert_eq!(resp.header("x-missing"), None);
560 }
561
562 #[rstest]
567 fn test_assert_ok() {
568 let resp = make_response(200, b"");
570
571 resp.assert_ok();
573 }
574
575 #[rstest]
576 fn test_assert_created() {
577 let resp = make_response(201, b"");
579
580 resp.assert_created();
582 }
583
584 #[rstest]
585 fn test_assert_no_content() {
586 let resp = make_response(204, b"");
588
589 resp.assert_no_content();
591 }
592
593 #[rstest]
594 fn test_assert_bad_request() {
595 let resp = make_response(400, b"");
597
598 resp.assert_bad_request();
600 }
601
602 #[rstest]
603 fn test_assert_unauthorized() {
604 let resp = make_response(401, b"");
606
607 resp.assert_unauthorized();
609 }
610
611 #[rstest]
612 fn test_assert_forbidden() {
613 let resp = make_response(403, b"");
615
616 resp.assert_forbidden();
618 }
619
620 #[rstest]
621 fn test_assert_not_found() {
622 let resp = make_response(404, b"");
624
625 resp.assert_not_found();
627 }
628
629 #[rstest]
630 fn test_assert_success() {
631 let resp = make_response(200, b"");
633
634 resp.assert_success();
636 }
637
638 #[rstest]
639 fn test_assert_client_error() {
640 let resp = make_response(404, b"");
642
643 resp.assert_client_error();
645 }
646
647 #[rstest]
648 fn test_assert_server_error() {
649 let resp = make_response(500, b"");
651
652 resp.assert_server_error();
654 }
655
656 #[rstest]
657 fn test_fluent_chaining() {
658 let resp = make_response(200, b"");
660
661 resp.assert_ok().assert_success();
663 }
664
665 #[rstest]
666 #[should_panic(expected = "Expected status")]
667 fn test_assert_status_mismatch() {
668 let resp = make_response(200, b"");
670
671 resp.assert_status(StatusCode::NOT_FOUND);
673 }
674}