From de75bab1f1a14ee23e529e2dd33152ff6af15d9a Mon Sep 17 00:00:00 2001 From: "tien.xuan.vo" Date: Wed, 11 Dec 2024 14:29:49 +0700 Subject: [PATCH] feat(pact_matching): Apply generators for form urlencoded --- rust/pact_matching/Cargo.toml | 7 +-- rust/pact_matching/src/form_urlencoded.rs | 2 + rust/pact_matching/src/generators/bodies.rs | 52 ++++++++++++++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/rust/pact_matching/Cargo.toml b/rust/pact_matching/Cargo.toml index ee5b4654f..5ae8d60d3 100644 --- a/rust/pact_matching/Cargo.toml +++ b/rust/pact_matching/Cargo.toml @@ -15,11 +15,12 @@ exclude = [ ] [features] -default = ["datetime", "xml", "plugins", "multipart"] +default = ["datetime", "xml", "plugins", "multipart", "form_urlencoded"] datetime = ["pact_models/datetime", "pact-plugin-driver?/datetime", "dep:chrono"] # Support for date/time matchers and expressions xml = ["pact_models/xml", "pact-plugin-driver?/xml", "dep:sxd-document"] # support for matching XML documents plugins = ["dep:pact-plugin-driver"] multipart = ["dep:multer"] # suport for MIME multipart bodies +form_urlencoded = ["dep:serde_urlencoded", "pact_models/form_urlencoded"] # suport for matching form urlencoded [dependencies] ansi_term = "0.12.1" @@ -41,14 +42,14 @@ mime = "0.3.17" multer = { version = "3.0.0", features = ["all"], optional = true } nom = "7.1.3" onig = { version = "6.4.0", default-features = false } -pact_models = { version = "~1.2.5", default-features = false } +pact_models = { version = "~1.2.6", default-features = false } pact-plugin-driver = { version = "~0.7.2", optional = true, default-features = false } rand = "0.8.5" reqwest = { version = "0.12.3", default-features = false, features = ["rustls-tls-native-roots", "json"] } semver = "1.0.22" serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -serde_urlencoded = "0.7.1" +serde_urlencoded = { version = "0.7.1", optional = true } sxd-document = { version = "0.3.2", optional = true } tokio = { version = "1.37.0", features = ["full"] } tracing = "0.1.40" diff --git a/rust/pact_matching/src/form_urlencoded.rs b/rust/pact_matching/src/form_urlencoded.rs index 0a61a093f..94416a7cf 100644 --- a/rust/pact_matching/src/form_urlencoded.rs +++ b/rust/pact_matching/src/form_urlencoded.rs @@ -8,6 +8,8 @@ use tracing::debug; use crate::{MatchingContext, Mismatch}; use crate::query::match_query_maps; +#[cfg(feature = "form_urlencoded")] use serde_urlencoded; + /// Matches the bodies using application/x-www-form-urlencoded encoding pub(crate) fn match_form_urlencoded( expected: &(dyn HttpPart + Send + Sync), diff --git a/rust/pact_matching/src/generators/bodies.rs b/rust/pact_matching/src/generators/bodies.rs index 48c3cf19b..72619ee9e 100644 --- a/rust/pact_matching/src/generators/bodies.rs +++ b/rust/pact_matching/src/generators/bodies.rs @@ -15,6 +15,9 @@ use pact_models::plugins::PluginData; #[cfg(feature = "xml")] use crate::generators::XmlHandler; +#[cfg(feature = "form_urlencoded")] use pact_models::generators::form_urlencoded::FormUrlEncodedHandler; +#[cfg(feature = "form_urlencoded")] use serde_urlencoded; + /// Apply the generators to the body, returning a new body #[allow(unused_variables)] pub async fn generators_process_body( @@ -67,6 +70,30 @@ pub async fn generators_process_body( warn!("Generating XML documents requires the xml feature to be enabled"); Ok(body.clone()) } + } else if content_type.is_form_urlencoded() { + debug!("apply_body_generators: FORM URLENCODED content type"); + #[cfg(feature = "form_urlencoded")] + { + let result: Result, serde_urlencoded::de::Error> = serde_urlencoded::from_bytes(&body.value().unwrap_or_default()); + match result { + Ok(val) => { + let mut handler = FormUrlEncodedHandler { params: val }; + Ok(handler.process_body(generators, mode, context, &matcher.boxed()).unwrap_or_else(|err| { + error!("Failed to generate the body: {}", err); + body.clone() + })) + }, + Err(err) => { + error!("Failed to parse the body, so not applying any generators: {}", err); + Ok(body.clone()) + } + } + } + #[cfg(not(feature = "form_urlencoded"))] + { + warn!("Generating FORM URLENCODED query string requires the form_urlencoded feature to be enabled"); + Ok(body.clone()) + } } else { #[cfg(feature = "plugins")] @@ -97,9 +124,11 @@ mod tests { use expectest::prelude::*; use maplit::hashmap; + use pact_models::generators::Generator; use pact_models::bodies::OptionalBody; - use pact_models::content_types::{JSON, TEXT}; + use pact_models::content_types::{JSON, TEXT, XML, FORM_URLENCODED}; use pact_models::generators::GeneratorTestMode; + use pact_models::path_exp::DocPath; use super::generators_process_body; use crate::DefaultVariantMatcher; @@ -131,4 +160,25 @@ mod tests { expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(TEXT.clone()), &hashmap!{}, &hashmap!{}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(be_equal_to(body)); } + + #[tokio::test] + async fn apply_generator_to_json_body_test() { + let body = OptionalBody::Present("{\"a\":100}".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(JSON.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.a") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to_not(be_equal_to(body)); + } + + #[tokio::test] + async fn do_not_apply_generator_to_xml_body_because_unimplemented() { + let body = OptionalBody::Present("100".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(XML.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.name") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to(be_equal_to(body)); + } + + #[tokio::test] + async fn apply_generator_to_form_urlencoded_body_test() { + let body = OptionalBody::Present("a=100".into(), None, None); + expect!(generators_process_body(&GeneratorTestMode::Provider, &body, Some(FORM_URLENCODED.clone()), + &hashmap!{}, &hashmap!{DocPath::new_unwrap("$.a") => Generator::RandomInt(0, 10)}, &DefaultVariantMatcher{}, &vec![], &hashmap!{}).await.unwrap()).to_not(be_equal_to(body)); + } }