Skip to content

Commit

Permalink
feat(pact_matching): Apply generators for form urlencoded
Browse files Browse the repository at this point in the history
  • Loading branch information
tienvx committed Dec 11, 2024
1 parent c266f8e commit de75bab
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 4 deletions.
7 changes: 4 additions & 3 deletions rust/pact_matching/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions rust/pact_matching/src/form_urlencoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
52 changes: 51 additions & 1 deletion rust/pact_matching/src/generators/bodies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<Vec<(String, String)>, 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")]
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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("<a>100</a>".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));
}
}

0 comments on commit de75bab

Please sign in to comment.