From 65d05149cb720fa4b41f661f40e87b6d74dac9e4 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Wed, 17 Aug 2022 16:32:43 +1000 Subject: [PATCH] fix: content type matcher was not being applied if content type was not octet_stream #171 --- rust/pact_ffi/tests/1px.gif | Bin 0 -> 35 bytes rust/pact_ffi/tests/tests.rs | 73 +++++++++++++++++++++++++++++++- rust/pact_matching/src/lib.rs | 20 ++++++--- rust/pact_matching/src/tests.rs | 26 ++++++++++++ 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 rust/pact_ffi/tests/1px.gif diff --git a/rust/pact_ffi/tests/1px.gif b/rust/pact_ffi/tests/1px.gif new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/rust/pact_ffi/tests/tests.rs b/rust/pact_ffi/tests/tests.rs index 1c5d02959..a993f0223 100644 --- a/rust/pact_ffi/tests/tests.rs +++ b/rust/pact_ffi/tests/tests.rs @@ -1,4 +1,8 @@ +use std::env; use std::ffi::{CStr, CString}; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; use bytes::Bytes; use expectest::prelude::*; @@ -23,10 +27,11 @@ use pact_ffi::mock_server::handles::{ pactffi_message_with_metadata, pactffi_new_interaction, pactffi_new_message, - pactffi_new_message_pact, + pactffi_new_message_pact, pactffi_new_pact, pactffi_response_status, pactffi_upon_receiving, + pactffi_with_binary_file, pactffi_with_body, pactffi_with_header, pactffi_with_multipart_file, @@ -340,3 +345,69 @@ fn pactffi_verifier_cli_args_test() { assert!(options_flags.options.len() > 0); assert!(options_flags.flags.len() > 0); } + +/// Get the path to one of our sample *.json files. +fn fixture_path(path: &str) -> PathBuf { + env::current_dir() + .expect("could not find current working directory") + .join("tests") + .join(path) + .to_owned() +} + +#[test_log::test] +fn pactffi_with_binary_file_feature_test() { + let consumer_name = CString::new("http-consumer").unwrap(); + let provider_name = CString::new("image-provider").unwrap(); + let pact_handle = pactffi_new_pact(consumer_name.as_ptr(), provider_name.as_ptr()); + + let description = CString::new("request_with_matchers").unwrap(); + let interaction = pactffi_new_interaction(pact_handle.clone(), description.as_ptr()); + + let content_type = CString::new("image/gif").unwrap(); + let path = CString::new("/upload").unwrap(); + let address = CString::new("127.0.0.1:0").unwrap(); + let file_path = CString::new("/tmp/pact").unwrap(); + let description = CString::new("a request to test the FFI interface").unwrap(); + let method = CString::new("POST").unwrap(); + + let mut buffer = Vec::new(); + let gif_file = fixture_path("1px.gif"); + File::open(gif_file).unwrap().read_to_end(&mut buffer).unwrap(); + + pactffi_upon_receiving(interaction.clone(), description.as_ptr()); + pactffi_with_request(interaction.clone(), method.as_ptr(), path.as_ptr()); + pactffi_with_binary_file(interaction.clone(), InteractionPart::Request, content_type.as_ptr(), + buffer.as_ptr(), buffer.len()); + // will respond with... + pactffi_response_status(interaction.clone(), 201); + + let port = pactffi_create_mock_server_for_pact(pact_handle.clone(), address.as_ptr(), false); + + expect!(port).to(be_greater_than(0)); + + let client = Client::default(); + let result = client.post(format!("http://127.0.0.1:{}/upload", port).as_str()) + .header("Content-Type", "image/gif") + .body(buffer) + .send(); + + let mismatches = unsafe { + CStr::from_ptr(pactffi_mock_server_mismatches(port)).to_string_lossy().into_owned() + }; + + match result { + Ok(res) => { + let status = res.status(); + expect!(status).to(be_eq(201)); + }, + Err(err) => { + panic!("expected 201 response but request failed - {}", err); + } + }; + + pactffi_write_pact_file(port, file_path.as_ptr(), true); + pactffi_cleanup_mock_server(port); + + expect!(mismatches).to(be_equal_to("[]")); +} diff --git a/rust/pact_matching/src/lib.rs b/rust/pact_matching/src/lib.rs index ce581ba76..76705dbe5 100644 --- a/rust/pact_matching/src/lib.rs +++ b/rust/pact_matching/src/lib.rs @@ -635,8 +635,8 @@ lazy_static! { = [ (|content_type| { content_type.is_json() }, json::match_json), (|content_type| { content_type.is_xml() }, xml::match_xml), - (|content_type| { content_type.base_type() == "application/octet-stream" }, binary_utils::match_octet_stream), - (|content_type| { content_type.base_type() == "multipart/form-data" }, binary_utils::match_mime_multipart) + (|content_type| { content_type.base_type() == "multipart/form-data" }, binary_utils::match_mime_multipart), + (|content_type| { content_type.is_binary() || content_type.base_type() == "application/octet-stream" }, binary_utils::match_octet_stream) ]; } @@ -1291,9 +1291,19 @@ fn compare_bodies_core( } }, None => { - debug!("No body matcher defined for content type '{}', using plain text matcher", content_type); - if let Err(m) = match_text(&expected.body().value(), &actual.body().value(), context) { - mismatches.extend_from_slice(&*m); + debug!("No body matcher defined for content type '{}', checking for a content type matcher", content_type); + let path = DocPath::root(); + if context.matcher_is_defined(&path) && context.select_best_matcher(&path).rules + .iter().any(|rule| if let MatchingRule::ContentType(_) = rule { true } else { false }) { + debug!("Found a content type matcher"); + if let Err(m) = binary_utils::match_octet_stream(expected, actual, context) { + mismatches.extend_from_slice(&*m); + } + } else { + debug!("No body matcher defined for content type '{}', using plain text matcher", content_type); + if let Err(m) = match_text(&expected.body().value(), &actual.body().value(), context) { + mismatches.extend_from_slice(&*m); + } } } }; diff --git a/rust/pact_matching/src/tests.rs b/rust/pact_matching/src/tests.rs index 20731e524..61ca09f22 100644 --- a/rust/pact_matching/src/tests.rs +++ b/rust/pact_matching/src/tests.rs @@ -659,3 +659,29 @@ fn values_matcher_defined() { expect!(context.values_matcher_defined(&path_x.join("0").join("z"))).to(be_false()); expect!(context.values_matcher_defined(&path_y.join("0").join("y"))).to(be_false()); } + +const IMAGE_BYTES: [u8; 16] = [ 0o107, 0o111, 0o106, 0o070, 0o067, 0o141, 0o001, 0o000, 0o001, 0o000, 0o200, 0o000, 0o000, 0o377, 0o377, 0o377 ]; + +#[test] +fn compare_bodies_core_should_check_for_content_type_matcher() { + let content_type = ContentType::parse("application/gif").unwrap(); + let matching_rules = matchingrules!{ "body" => { "$" => [ MatchingRule::ContentType("application/gif".to_string()) ] } }; + let expected = Request { + body: OptionalBody::Present(Bytes::from_static(&IMAGE_BYTES), Some(content_type.clone()), None), + matching_rules: matching_rules.clone(), + .. Request::default() + }; + let actual = Request { + body: OptionalBody::Present(Bytes::from_static(&IMAGE_BYTES), Some(content_type.clone()), None), + .. Request::default() + }; + let context = CoreMatchingContext::new(DiffConfig::AllowUnexpectedKeys, + &matching_rules.rules_for_category("body").unwrap(), + &hashmap!{} + ); + + let result = compare_bodies_core(&content_type, &expected, &actual, &context); + + expect!(result.len()).to(be_equal_to(1)); + expect!(result.first().unwrap().description()).to(be_equal_to("$ -> Expected binary contents to have content type 'application/gif' but detected contents was 'image/gif'")); +}