1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8#![warn(
10 unreachable_pub,
13 rust_2018_idioms
14)]
15
16mod urlencoded;
17mod xml;
18
19use crate::sealed::GetNormalizedHeader;
20use crate::xml::try_xml_equivalent;
21use assert_json_diff::assert_json_matches_no_panic;
22use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
23use aws_smithy_runtime_api::http::Headers;
24use http::{HeaderMap, Uri};
25use pretty_assertions::Comparison;
26use std::borrow::Cow;
27use std::collections::HashSet;
28use std::fmt::{self, Debug};
29use thiserror::Error;
30use urlencoded::try_url_encoded_form_equivalent;
31
32pub trait FloatEquals {
37 fn float_equals(&self, other: &Self) -> bool;
38}
39
40impl FloatEquals for f64 {
41 fn float_equals(&self, other: &Self) -> bool {
42 (self.is_nan() && other.is_nan()) || self.eq(other)
43 }
44}
45
46impl FloatEquals for f32 {
47 fn float_equals(&self, other: &Self) -> bool {
48 (self.is_nan() && other.is_nan()) || self.eq(other)
49 }
50}
51
52impl<T> FloatEquals for Option<T>
53where
54 T: FloatEquals,
55{
56 fn float_equals(&self, other: &Self) -> bool {
57 match (self, other) {
58 (Some(this), Some(other)) => this.float_equals(other),
59 (None, None) => true,
60 _else => false,
61 }
62 }
63}
64
65#[derive(Debug, PartialEq, Eq, Error)]
66pub enum ProtocolTestFailure {
67 #[error("missing query param: expected `{expected}`, found {found:?}")]
68 MissingQueryParam {
69 expected: String,
70 found: Vec<String>,
71 },
72 #[error("forbidden query param present: `{expected}`")]
73 ForbiddenQueryParam { expected: String },
74 #[error("required query param missing: `{expected}`")]
75 RequiredQueryParam { expected: String },
76
77 #[error("invalid header value for key `{key}`: expected `{expected}`, found `{found}`")]
78 InvalidHeader {
79 key: String,
80 expected: String,
81 found: String,
82 },
83 #[error("missing required header: `{expected}`")]
84 MissingHeader { expected: String },
85 #[error("Header `{forbidden}` was forbidden but found: `{found}`")]
86 ForbiddenHeader { forbidden: String, found: String },
87 #[error(
88 "body did not match. left=expected, right=actual\n{comparison:?} \n == hint:\n{hint}."
89 )]
90 BodyDidNotMatch {
91 comparison: PrettyString,
94 hint: String,
95 },
96 #[error("Expected body to be valid {expected} but instead: {found}")]
97 InvalidBodyFormat { expected: String, found: String },
98}
99
100#[track_caller]
106pub fn assert_ok(inp: Result<(), ProtocolTestFailure>) {
107 match inp {
108 Ok(_) => (),
109 Err(e) => {
110 eprintln!("{}", e);
111 panic!("Protocol test failed");
112 }
113 }
114}
115
116#[derive(Eq, PartialEq, Hash)]
117struct QueryParam<'a> {
118 key: &'a str,
119 value: Option<&'a str>,
120}
121
122impl<'a> QueryParam<'a> {
123 fn parse(s: &'a str) -> Self {
124 let mut parsed = s.split('=');
125 QueryParam {
126 key: parsed.next().unwrap(),
127 value: parsed.next(),
128 }
129 }
130}
131
132fn extract_params(uri: &str) -> HashSet<&str> {
133 let query = uri.rsplit_once('?').map(|s| s.1).unwrap_or_default();
134 query.split('&').collect()
135}
136
137#[track_caller]
138pub fn assert_uris_match(left: impl AsRef<str>, right: impl AsRef<str>) {
139 let left = left.as_ref();
140 let right = right.as_ref();
141 if left == right {
142 return;
143 }
144 assert_eq!(
145 extract_params(left),
146 extract_params(right),
147 "Query parameters did not match. left: {}, right: {}",
148 left,
149 right
150 );
151 let left: Uri = left.parse().expect("left is not a valid URI");
152 let right: Uri = right.parse().expect("left is not a valid URI");
153 assert_eq!(left.authority(), right.authority());
154 assert_eq!(left.scheme(), right.scheme());
155 assert_eq!(left.path(), right.path());
156}
157
158pub fn validate_query_string(
159 request: &HttpRequest,
160 expected_params: &[&str],
161) -> Result<(), ProtocolTestFailure> {
162 let actual_params = extract_params(request.uri());
163 for param in expected_params {
164 if !actual_params.contains(param) {
165 return Err(ProtocolTestFailure::MissingQueryParam {
166 expected: param.to_string(),
167 found: actual_params.iter().map(|s| s.to_string()).collect(),
168 });
169 }
170 }
171 Ok(())
172}
173
174pub fn forbid_query_params(
175 request: &HttpRequest,
176 forbid_params: &[&str],
177) -> Result<(), ProtocolTestFailure> {
178 let actual_params: HashSet<QueryParam<'_>> = extract_params(request.uri())
179 .iter()
180 .map(|param| QueryParam::parse(param))
181 .collect();
182 let actual_keys: HashSet<&str> = actual_params.iter().map(|param| param.key).collect();
183 for param in forbid_params {
184 let parsed = QueryParam::parse(param);
185 if actual_params.contains(&parsed) {
187 return Err(ProtocolTestFailure::ForbiddenQueryParam {
188 expected: param.to_string(),
189 });
190 }
191 if parsed.value.is_none() && actual_keys.contains(parsed.key) {
193 return Err(ProtocolTestFailure::ForbiddenQueryParam {
194 expected: param.to_string(),
195 });
196 }
197 }
198 Ok(())
199}
200
201pub fn require_query_params(
202 request: &HttpRequest,
203 require_keys: &[&str],
204) -> Result<(), ProtocolTestFailure> {
205 let actual_keys: HashSet<&str> = extract_params(request.uri())
206 .iter()
207 .map(|param| QueryParam::parse(param).key)
208 .collect();
209 for key in require_keys {
210 if !actual_keys.contains(*key) {
211 return Err(ProtocolTestFailure::RequiredQueryParam {
212 expected: key.to_string(),
213 });
214 }
215 }
216 Ok(())
217}
218
219mod sealed {
220 pub trait GetNormalizedHeader {
221 fn get_header(&self, key: &str) -> Option<String>;
222 }
223}
224
225impl GetNormalizedHeader for &Headers {
226 fn get_header(&self, key: &str) -> Option<String> {
227 if !self.contains_key(key) {
228 None
229 } else {
230 Some(self.get_all(key).collect::<Vec<_>>().join(", "))
231 }
232 }
233}
234
235impl GetNormalizedHeader for &HeaderMap {
236 fn get_header(&self, key: &str) -> Option<String> {
237 if !self.contains_key(key) {
238 None
239 } else {
240 Some(
241 self.get_all(key)
242 .iter()
243 .map(|value| std::str::from_utf8(value.as_bytes()).expect("invalid utf-8"))
244 .collect::<Vec<_>>()
245 .join(", "),
246 )
247 }
248 }
249}
250
251pub fn validate_headers<'a>(
252 actual_headers: impl GetNormalizedHeader,
253 expected_headers: impl IntoIterator<Item = (impl AsRef<str> + 'a, impl AsRef<str> + 'a)>,
254) -> Result<(), ProtocolTestFailure> {
255 for (key, expected_value) in expected_headers {
256 let key = key.as_ref();
257 let expected_value = expected_value.as_ref();
258 match actual_headers.get_header(key) {
259 None => {
260 return Err(ProtocolTestFailure::MissingHeader {
261 expected: key.to_string(),
262 })
263 }
264 Some(actual_value) if actual_value != *expected_value => {
265 return Err(ProtocolTestFailure::InvalidHeader {
266 key: key.to_string(),
267 expected: expected_value.to_string(),
268 found: actual_value,
269 })
270 }
271 _ => (),
272 }
273 }
274 Ok(())
275}
276
277pub fn forbid_headers(
278 headers: impl GetNormalizedHeader,
279 forbidden_headers: &[&str],
280) -> Result<(), ProtocolTestFailure> {
281 for key in forbidden_headers {
282 if let Some(value) = headers.get_header(key) {
284 return Err(ProtocolTestFailure::ForbiddenHeader {
285 forbidden: key.to_string(),
286 found: format!("{}: {}", key, value),
287 });
288 }
289 }
290 Ok(())
291}
292
293pub fn require_headers(
294 headers: impl GetNormalizedHeader,
295 required_headers: &[&str],
296) -> Result<(), ProtocolTestFailure> {
297 for key in required_headers {
298 if headers.get_header(key).is_none() {
300 return Err(ProtocolTestFailure::MissingHeader {
301 expected: key.to_string(),
302 });
303 }
304 }
305 Ok(())
306}
307
308#[derive(Clone)]
309pub enum MediaType {
310 Json,
312 Xml,
314 Cbor,
316 UrlEncodedForm,
318 Other(String),
320}
321
322impl<T: AsRef<str>> From<T> for MediaType {
323 fn from(inp: T) -> Self {
324 match inp.as_ref() {
325 "application/json" => MediaType::Json,
326 "application/x-amz-json-1.1" => MediaType::Json,
327 "application/xml" => MediaType::Xml,
328 "application/cbor" => MediaType::Cbor,
329 "application/x-www-form-urlencoded" => MediaType::UrlEncodedForm,
330 other => MediaType::Other(other.to_string()),
331 }
332 }
333}
334
335pub fn validate_body<T: AsRef<[u8]> + Debug>(
336 actual_body: T,
337 expected_body: &str,
338 media_type: MediaType,
339) -> Result<(), ProtocolTestFailure> {
340 let body_str = std::str::from_utf8(actual_body.as_ref());
341 match (media_type, body_str) {
342 (MediaType::Json, Ok(actual_body)) => try_json_eq(expected_body, actual_body),
343 (MediaType::Json, Err(_)) => Err(ProtocolTestFailure::InvalidBodyFormat {
344 expected: "json".to_owned(),
345 found: "input was not valid UTF-8".to_owned(),
346 }),
347 (MediaType::Xml, Ok(actual_body)) => try_xml_equivalent(actual_body, expected_body),
348 (MediaType::Xml, Err(_)) => Err(ProtocolTestFailure::InvalidBodyFormat {
349 expected: "XML".to_owned(),
350 found: "input was not valid UTF-8".to_owned(),
351 }),
352 (MediaType::UrlEncodedForm, Ok(actual_body)) => {
353 try_url_encoded_form_equivalent(expected_body, actual_body)
354 }
355 (MediaType::UrlEncodedForm, Err(_)) => Err(ProtocolTestFailure::InvalidBodyFormat {
356 expected: "x-www-form-urlencoded".to_owned(),
357 found: "input was not valid UTF-8".to_owned(),
358 }),
359 (MediaType::Cbor, _) => try_cbor_eq(actual_body, expected_body),
360 (MediaType::Other(media_type), Ok(actual_body)) => {
361 if actual_body != expected_body {
362 Err(ProtocolTestFailure::BodyDidNotMatch {
363 comparison: pretty_comparison(expected_body, actual_body),
364 hint: format!("media type: {}", media_type),
365 })
366 } else {
367 Ok(())
368 }
369 }
370 (MediaType::Other(_), Err(_)) => {
373 unimplemented!("binary/non-utf8 formats not yet supported")
374 }
375 }
376}
377
378#[derive(Eq, PartialEq)]
379struct PrettyStr<'a>(&'a str);
380impl Debug for PrettyStr<'_> {
381 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382 f.write_str(self.0)
383 }
384}
385
386#[derive(Eq, PartialEq)]
387pub struct PrettyString(String);
388impl Debug for PrettyString {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 f.write_str(&self.0)
391 }
392}
393
394fn pretty_comparison(expected: &str, actual: &str) -> PrettyString {
395 PrettyString(format!(
396 "{}",
397 Comparison::new(&PrettyStr(expected), &PrettyStr(actual))
398 ))
399}
400
401fn try_json_eq(expected: &str, actual: &str) -> Result<(), ProtocolTestFailure> {
402 let expected_json: serde_json::Value =
403 serde_json::from_str(expected).expect("expected value must be valid JSON");
404 let actual_json: serde_json::Value =
405 serde_json::from_str(actual).map_err(|e| ProtocolTestFailure::InvalidBodyFormat {
406 expected: "json".to_owned(),
407 found: e.to_string() + actual,
408 })?;
409 let config = assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict);
410 match assert_json_matches_no_panic(&actual_json, &expected_json, config) {
411 Ok(()) => Ok(()),
412 Err(message) => Err(ProtocolTestFailure::BodyDidNotMatch {
413 comparison: pretty_comparison(expected, actual),
414 hint: message,
415 }),
416 }
417}
418
419fn cbor_values_equal(
425 a: &ciborium::value::Value,
426 b: &ciborium::value::Value,
427) -> Result<bool, ProtocolTestFailure> {
428 match (a, b) {
429 (ciborium::value::Value::Array(a_array), ciborium::value::Value::Array(b_array)) => {
430 if a_array.len() != b_array.len() {
432 return Ok(false);
433 }
434 for (a_elem, b_elem) in a_array.iter().zip(b_array.iter()) {
436 if !cbor_values_equal(a_elem, b_elem)? {
437 return Ok(false);
438 }
439 }
440 Ok(true)
441 }
442
443 (ciborium::value::Value::Map(a_map), ciborium::value::Value::Map(b_map)) => {
446 if a_map.len() != b_map.len() {
447 return Ok(false);
448 }
449
450 let b_hashmap = ciborium_map_to_hashmap(b_map)?;
451 for a_key_value in a_map.iter() {
453 let (a_key, a_value) = get_text_key_value(a_key_value)?;
454 match b_hashmap.get(a_key) {
455 Some(b_value) => {
456 if !cbor_values_equal(a_value, b_value)? {
457 return Ok(false);
458 }
459 }
460 None => return Ok(false),
461 }
462 }
463 Ok(true)
464 }
465
466 (ciborium::value::Value::Float(a_float), ciborium::value::Value::Float(b_float)) => {
467 Ok(a_float == b_float || (a_float.is_nan() && b_float.is_nan()))
468 }
469
470 _ => Ok(a == b),
471 }
472}
473
474fn ciborium_map_to_hashmap(
482 cbor_map: &[(ciborium::value::Value, ciborium::value::Value)],
483) -> Result<std::collections::HashMap<&String, &ciborium::value::Value>, ProtocolTestFailure> {
484 cbor_map.iter().map(get_text_key_value).collect()
485}
486
487fn get_text_key_value(
490 (key, value): &(ciborium::value::Value, ciborium::value::Value),
491) -> Result<(&String, &ciborium::value::Value), ProtocolTestFailure> {
492 match key {
493 ciborium::value::Value::Text(key_str) => Ok((key_str, value)),
494 _ => Err(ProtocolTestFailure::InvalidBodyFormat {
495 expected: "a text key as map entry".to_string(),
496 found: format!("{:?}", key),
497 }),
498 }
499}
500
501fn try_cbor_eq<T: AsRef<[u8]> + Debug>(
502 actual_body: T,
503 expected_body: &str,
504) -> Result<(), ProtocolTestFailure> {
505 let decoded = base64_simd::STANDARD
506 .decode_to_vec(expected_body)
507 .expect("smithy protocol test `body` property is not properly base64 encoded");
508 let expected_cbor_value: ciborium::value::Value =
509 ciborium::de::from_reader(decoded.as_slice()).expect("expected value must be valid CBOR");
510 let actual_cbor_value: ciborium::value::Value = ciborium::de::from_reader(actual_body.as_ref())
511 .map_err(|e| ProtocolTestFailure::InvalidBodyFormat {
512 expected: "cbor".to_owned(),
513 found: format!("{} {:?}", e, actual_body),
514 })?;
515 let actual_body_base64 = base64_simd::STANDARD.encode_to_string(&actual_body);
516
517 if !cbor_values_equal(&expected_cbor_value, &actual_cbor_value)? {
518 let expected_body_annotated_hex: String = cbor_diag::parse_bytes(&decoded)
519 .expect("smithy protocol test `body` property is not valid CBOR")
520 .to_hex();
521 let expected_body_diag: String = cbor_diag::parse_bytes(&decoded)
522 .expect("smithy protocol test `body` property is not valid CBOR")
523 .to_diag_pretty();
524 let actual_body_annotated_hex: String = cbor_diag::parse_bytes(&actual_body)
525 .expect("actual body is not valid CBOR")
526 .to_hex();
527 let actual_body_diag: String = cbor_diag::parse_bytes(&actual_body)
528 .expect("actual body is not valid CBOR")
529 .to_diag_pretty();
530
531 Err(ProtocolTestFailure::BodyDidNotMatch {
532 comparison: PrettyString(format!(
533 "{}",
534 Comparison::new(&expected_cbor_value, &actual_cbor_value)
535 )),
536 hint: format!(
538 "expected body in diagnostic format:
539{}
540actual body in diagnostic format:
541{}
542expected body in annotated hex:
543{}
544actual body in annotated hex:
545{}
546actual body in base64 (useful to update the protocol test):
547{}
548",
549 expected_body_diag,
550 actual_body_diag,
551 expected_body_annotated_hex,
552 actual_body_annotated_hex,
553 actual_body_base64,
554 ),
555 })
556 } else {
557 Ok(())
558 }
559}
560
561pub fn decode_body_data(body: &[u8], media_type: MediaType) -> Cow<'_, [u8]> {
562 match media_type {
563 MediaType::Cbor => Cow::Owned(
564 base64_simd::STANDARD
565 .decode_to_vec(body)
566 .expect("smithy protocol test `body` property is not properly base64 encoded"),
567 ),
568 _ => Cow::Borrowed(body),
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use crate::{
575 forbid_headers, forbid_query_params, require_headers, require_query_params, validate_body,
576 validate_headers, validate_query_string, FloatEquals, MediaType, ProtocolTestFailure,
577 };
578 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
579 use aws_smithy_runtime_api::http::Headers;
580
581 fn make_request(uri: &str) -> HttpRequest {
582 let mut req = HttpRequest::empty();
583 req.set_uri(uri).unwrap();
584 req
585 }
586
587 #[test]
588 fn test_validate_empty_query_string() {
589 let request = HttpRequest::empty();
590 validate_query_string(&request, &[]).expect("no required params should pass");
591 validate_query_string(&request, &["a"]).expect_err("no params provided");
592 }
593
594 #[test]
595 fn test_validate_query_string() {
596 let request = make_request("/foo?a=b&c&d=efg&hello=a%20b");
597 validate_query_string(&request, &["a=b"]).expect("a=b is in the query string");
598 validate_query_string(&request, &["c", "a=b"])
599 .expect("both params are in the query string");
600 validate_query_string(&request, &["a=b", "c", "d=efg", "hello=a%20b"])
601 .expect("all params are in the query string");
602 validate_query_string(&request, &[]).expect("no required params should pass");
603
604 validate_query_string(&request, &["a"]).expect_err("no parameter should match");
605 validate_query_string(&request, &["a=bc"]).expect_err("no parameter should match");
606 validate_query_string(&request, &["a=bc"]).expect_err("no parameter should match");
607 validate_query_string(&request, &["hell=a%20"]).expect_err("no parameter should match");
608 }
609
610 #[test]
611 fn test_forbid_query_param() {
612 let request = make_request("/foo?a=b&c&d=efg&hello=a%20b");
613 forbid_query_params(&request, &["a"]).expect_err("a is a query param");
614 forbid_query_params(&request, &["not_included"]).expect("query param not included");
615 forbid_query_params(&request, &["a=b"]).expect_err("if there is an `=`, match against KV");
616 forbid_query_params(&request, &["c"]).expect_err("c is a query param");
617 forbid_query_params(&request, &["a=c"]).expect("there is no a=c query param set");
618 }
619
620 #[test]
621 fn test_require_query_param() {
622 let request = make_request("/foo?a=b&c&d=efg&hello=a%20b");
623 require_query_params(&request, &["a"]).expect("a is a query param");
624 require_query_params(&request, &["not_included"]).expect_err("query param not included");
625 require_query_params(&request, &["a=b"]).expect_err("should be matching against keys");
626 require_query_params(&request, &["c"]).expect("c is a query param");
627 }
628
629 #[test]
630 fn test_validate_headers() {
631 let mut headers = Headers::new();
632 headers.append("x-foo", "foo");
633 headers.append("x-foo-list", "foo");
634 headers.append("x-foo-list", "bar");
635 headers.append("x-inline", "inline, other");
636
637 validate_headers(&headers, [("X-Foo", "foo")]).expect("header present");
638 validate_headers(&headers, [("X-Foo", "Foo")]).expect_err("case sensitive");
639 validate_headers(&headers, [("x-foo-list", "foo, bar")]).expect("list concat");
640 validate_headers(&headers, [("X-Foo-List", "foo")])
641 .expect_err("all list members must be specified");
642 validate_headers(&headers, [("X-Inline", "inline, other")])
643 .expect("inline header lists also work");
644 assert_eq!(
645 validate_headers(&headers, [("missing", "value")]),
646 Err(ProtocolTestFailure::MissingHeader {
647 expected: "missing".to_owned()
648 })
649 );
650 }
651
652 #[test]
653 fn test_forbidden_headers() {
654 let mut headers = Headers::new();
655 headers.append("x-foo", "foo");
656 assert_eq!(
657 forbid_headers(&headers, &["X-Foo"]).expect_err("should be error"),
658 ProtocolTestFailure::ForbiddenHeader {
659 forbidden: "X-Foo".to_string(),
660 found: "X-Foo: foo".to_string()
661 }
662 );
663 forbid_headers(&headers, &["X-Bar"]).expect("header not present");
664 }
665
666 #[test]
667 fn test_required_headers() {
668 let mut headers = Headers::new();
669 headers.append("x-foo", "foo");
670 require_headers(&headers, &["X-Foo"]).expect("header present");
671 require_headers(&headers, &["X-Bar"]).expect_err("header not present");
672 }
673
674 #[test]
675 fn test_validate_json_body() {
676 let expected = r#"{"abc": 5 }"#;
677 let actual = r#" {"abc": 5 }"#;
678 validate_body(actual, expected, MediaType::Json).expect("inputs matched as JSON");
679
680 let expected = r#"{"abc": 5 }"#;
681 let actual = r#" {"abc": 6 }"#;
682 validate_body(actual, expected, MediaType::Json).expect_err("bodies do not match");
683 }
684
685 #[test]
686 fn test_validate_cbor_body() {
687 let base64_encode = |v: &[u8]| base64_simd::STANDARD.encode_to_string(v);
688
689 let actual = [0xbf, 0x63, 0x61, 0x62, 0x63, 0x05, 0xff];
691 let expected_base64 = base64_encode(&[0xA1, 0x63, 0x61, 0x62, 0x63, 0x05]);
693
694 validate_body(actual, expected_base64.as_str(), MediaType::Cbor)
695 .expect("unexpected mismatch between CBOR definite and indefinite map encodings");
696
697 let actual = [0xBF, 0x61, 0x61, 0x01, 0x61, 0x62, 0x02, 0xFF];
699 let expected_base64 = base64_encode(&[0xBF, 0x61, 0x62, 0x02, 0x61, 0x61, 0x01, 0xFF]);
701 validate_body(actual, expected_base64.as_str(), MediaType::Cbor)
702 .expect("different ordering in CBOR decoded maps do not match");
703
704 let actual = [
706 0xBF, 0x61, 0x61, 0x9F, 0x01, 0x02, 0xBF, 0x61, 0x62, 0x03, 0x61, 0x63, 0x04, 0xFF,
707 0xFF, 0xFF,
708 ];
709 let expected_base64 = base64_encode(&[
711 0xBF, 0x61, 0x61, 0x9F, 0x01, 0x02, 0xBF, 0x61, 0x63, 0x04, 0x61, 0x62, 0x03, 0xFF,
712 0xFF, 0xFF,
713 ]);
714 validate_body(actual, expected_base64.as_str(), MediaType::Cbor)
715 .expect("different ordering in CBOR decoded maps do not match");
716
717 let actual = [0xBF, 0x61, 0x61, 0x9F, 0x01, 0x02, 0xFF, 0xFF];
719 let expected_base64 = base64_encode(&[0xBF, 0x61, 0x61, 0x9F, 0x02, 0x01, 0xFF, 0xFF]);
721 validate_body(actual, expected_base64.as_str(), MediaType::Cbor)
722 .expect_err("arrays in CBOR should follow strict ordering");
723 }
724
725 #[test]
726 fn test_validate_xml_body() {
727 let expected = r#"<a>
728 hello123
729 </a>"#;
730 let actual = "<a>hello123</a>";
731 validate_body(actual, expected, MediaType::Xml).expect("inputs match as XML");
732 let expected = r#"<a>
733 hello123
734 </a>"#;
735 let actual = "<a>hello124</a>";
736 validate_body(actual, expected, MediaType::Xml).expect_err("inputs are different");
737 }
738
739 #[test]
740 fn test_validate_non_json_body() {
741 let expected = r#"asdf"#;
742 let actual = r#"asdf "#;
743 validate_body(actual, expected, MediaType::from("something/else"))
744 .expect_err("bodies do not match");
745
746 validate_body(expected, expected, MediaType::from("something/else"))
747 .expect("inputs matched exactly")
748 }
749
750 #[test]
751 fn test_validate_headers_http0x() {
752 let request = http::Request::builder().header("a", "b").body(()).unwrap();
753 validate_headers(request.headers(), [("a", "b")]).unwrap()
754 }
755
756 #[test]
757 fn test_float_equals() {
758 let a = f64::NAN;
759 let b = f64::NAN;
760 assert_ne!(a, b);
761 assert!(a.float_equals(&b));
762 assert!(!a.float_equals(&5_f64));
763
764 assert!(5.0.float_equals(&5.0));
765 assert!(!5.0.float_equals(&5.1));
766
767 assert!(f64::INFINITY.float_equals(&f64::INFINITY));
768 assert!(!f64::INFINITY.float_equals(&f64::NEG_INFINITY));
769 assert!(f64::NEG_INFINITY.float_equals(&f64::NEG_INFINITY));
770 }
771}