Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions rust/pact_ffi/src/models/pact_specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub enum PactSpecification {
V3,
/// Version four of the pact specification (<https://github.com/pact-foundation/pact-specification/tree/version-4>)
V4,
/// Version 4.1 of the pact specification (<https://github.com/pact-foundation/pact-specification/tree/version-4.1>)
V4_1,
}

impl From<NonCPactSpecification> for PactSpecification {
Expand All @@ -31,6 +33,7 @@ impl From<NonCPactSpecification> for PactSpecification {
NonCPactSpecification::V2 => PactSpecification::V2,
NonCPactSpecification::V3 => PactSpecification::V3,
NonCPactSpecification::V4 => PactSpecification::V4,
NonCPactSpecification::V4_1 => PactSpecification::V4_1,
}
}
}
Expand All @@ -45,6 +48,7 @@ impl From<PactSpecification> for NonCPactSpecification {
PactSpecification::V2 => NonCPactSpecification::V2,
PactSpecification::V3 => NonCPactSpecification::V3,
PactSpecification::V4 => NonCPactSpecification::V4,
PactSpecification::V4_1 => NonCPactSpecification::V4_1,
}
}
}
344 changes: 344 additions & 0 deletions rust/pact_matching/src/generator_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,347 @@ async fn applies_body_generator_to_the_copy_of_the_message() {
expect!(&body["a"]).to_not(be_equal_to(&json!(100)));
expect!(&body["b"]).to(be_equal_to(&json!("B")));
}

#[tokio::test]
async fn applies_random_array_generator_to_request_body() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"name": "xxx", "price": 12, "count": 2}]}"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$.items" => Generator::RandomArray(2, 4) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(4));
let first_item = &items[0];
for item in items {
expect!(item).to(be_equal_to(first_item));
}
}

#[tokio::test]
async fn applies_random_array_generator_to_response_body() {
let response = HttpResponse {
body: OptionalBody::Present(r#"{"items": [{"name": "yyy", "price": 6, "count": 2}]}"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$.items" => Generator::RandomArray(2, 4) } },
.. HttpResponse::default()
};
let generated_response = generate_response(&response, &GeneratorTestMode::Consumer, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_response.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(4));
let first_item = &items[0];
for item in items {
expect!(item).to(be_equal_to(first_item));
}
}

#[tokio::test]
async fn applies_random_array_generator_with_nested_generators_to_request_body() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"name": "xxx", "price": 12, "count": 2}]}"#.into(), Some(JSON.clone()), None),
generators: generators! {
"BODY" => {
"$.items" => Generator::RandomArray(2, 4),
"$.items[*].name" => Generator::RandomString(5),
"$.items[*].price" => Generator::RandomInt(1, 100),
"$.items[*].count" => Generator::RandomInt(1, 10)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(4));
for i in 1..items.len() {
expect!(&items[i]).not_to(be_equal_to(&items[i - 1]));
}
}

#[tokio::test]
async fn applies_random_array_generator_with_nested_generators_to_response_body() {
let response = HttpResponse {
body: OptionalBody::Present(r#"{"items": [{"name": "yyy", "price": 6, "count": 2}]}"#.into(), Some(JSON.clone()), None),
generators: generators! {
"BODY" => {
"$.items" => Generator::RandomArray(2, 4),
"$.items[*].name" => Generator::RandomString(5),
"$.items[*].price" => Generator::RandomInt(1, 100),
"$.items[*].count" => Generator::RandomInt(1, 10)
}
},
.. HttpResponse::default()
};
let generated_response = generate_response(&response, &GeneratorTestMode::Consumer, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_response.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(4));
for i in 1..items.len() {
expect!(&items[i]).not_to(be_equal_to(&items[i - 1]));
}
}

#[tokio::test]
async fn applies_nested_random_array_generator() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"subitems": [{"value": 1}]}]}"#.into(), Some(JSON.clone()), None),
generators: generators! {
"BODY" => {
"$.items" => Generator::RandomArray(2, 3),
"$.items[*].subitems" => Generator::RandomArray(2, 3),
"$.items[*].subitems[*].value" => Generator::RandomInt(1, 1000)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(3));
for i in 1..items.len() {
expect!(&items[i]).not_to(be_equal_to(&items[i - 1]));
let subitems_curr = items[i]["subitems"].as_array().unwrap();
let subitems_prev = items[i - 1]["subitems"].as_array().unwrap();
expect!(subitems_curr).not_to(be_equal_to(subitems_prev));
for j in 1..subitems_curr.len() {
expect!(&subitems_curr[j]).not_to(be_equal_to(&subitems_curr[j - 1]));
}
}
}

#[tokio::test]
async fn applies_random_array_generator_with_same_min_max() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"value": 1}]}"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$.items" => Generator::RandomArray(3, 3) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
expect!(items.len()).to(be_equal_to(3));
}

#[tokio::test]
async fn cant_applies_random_array_generator_with_min_max_zero() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"value": 1}]}"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$.items" => Generator::RandomArray(0, 0) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
expect!(generated_request.body).to(be_equal_to(OptionalBody::Present(r#"{"items":[{"value":1}]}"#.into(), Some("application/json".into()), None)));
}

#[tokio::test]
async fn applies_random_array_generator_to_root_level_array() {
let request = HttpRequest {
body: OptionalBody::Present(r#"[{"value": 1}]"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$" => Generator::RandomArray(2, 4) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body.as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(4));
}

#[tokio::test]
async fn cant_applies_random_array_generator_to_empty_array() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": []}"#.into(), Some(JSON.clone()), None),
generators: generators! { "BODY" => { "$.items" => Generator::RandomArray(2, 4) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
expect!(generated_request.body).to(be_equal_to(OptionalBody::Present(r#"{"items":[]}"#.into(), Some("application/json".into()), None)));
}

#[tokio::test]
async fn applies_multiple_independent_array_generators() {
let request = HttpRequest {
body: OptionalBody::Present(r#"{"items": [{"value": 1}], "other": [{"x": 2}]}"#.into(), Some(JSON.clone()), None),
generators: generators! {
"BODY" => {
"$.items" => Generator::RandomArray(2, 3),
"$.other" => Generator::RandomArray(3, 4)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body: Value = serde_json::from_str(generated_request.body.display_string().as_str()).unwrap();
let items = body["items"].as_array().unwrap();
let other = body["other"].as_array().unwrap();
expect!(items.len()).to(be_ge(2));
expect!(items.len()).to(be_le(3));
expect!(other.len()).to(be_ge(3));
expect!(other.len()).to(be_le(4));
let first_item = &items[0];
let first_other = &other[0];
for item in items {
expect!(item).to(be_equal_to(first_item));
}
for item in other {
expect!(item).to(be_equal_to(first_other));
}
}

#[cfg(feature = "xml")]
mod xml_tests {
use super::*;
use pact_models::content_types::XML;

fn count_xml_elements(body: &str, tag: &str) -> usize {
let open_tag_with_space = format!("<{} ", tag);
let open_tag_with_bracket = format!("<{}>", tag);
let self_close_tag = format!("<{}/", tag);
body.split(&open_tag_with_space).count() - 1
+ body.split(&open_tag_with_bracket).count() - 1
+ body.split(&self_close_tag).count() - 1
}

fn count_substring(body: &str, pattern: &str) -> usize {
body.split(pattern).count() - 1
}

#[tokio::test]
async fn applies_random_array_generator_to_xml_request_body() {
let request = HttpRequest {
body: OptionalBody::Present("<items><item name='xxx' price='12'/></items>".into(), Some(XML.clone()), None),
generators: generators! { "BODY" => { "$.items.item" => Generator::RandomArray(2, 4) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
expect!(item_count).to(be_ge(2));
expect!(item_count).to(be_le(4));
}

#[tokio::test]
async fn applies_random_array_generator_to_xml_response_body() {
let response = HttpResponse {
body: OptionalBody::Present("<items><item name='yyy' price='6'/></items>".into(), Some(XML.clone()), None),
generators: generators! { "BODY" => { "$.items.item" => Generator::RandomArray(2, 4) } },
.. HttpResponse::default()
};
let generated_response = generate_response(&response, &GeneratorTestMode::Consumer, &hashmap!{}).await;
let body_str = generated_response.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
expect!(item_count).to(be_ge(2));
expect!(item_count).to(be_le(4));
}

#[tokio::test]
async fn applies_random_array_generator_with_same_min_max_to_xml() {
let request = HttpRequest {
body: OptionalBody::Present("<items><item value='1'/></items>".into(), Some(XML.clone()), None),
generators: generators! { "BODY" => { "$.items.item" => Generator::RandomArray(3, 3) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
expect!(item_count).to(be_equal_to(3));
}

#[tokio::test]
async fn applies_random_array_generator_with_nested_generators_to_xml() {
let request = HttpRequest {
body: OptionalBody::Present("<items><item name='xxx' price='12'/></items>".into(), Some(XML.clone()), None),
generators: generators! {
"BODY" => {
"$.items.item" => Generator::RandomArray(2, 4),
"$.items.item['@name']" => Generator::RandomString(5)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
expect!(item_count).to(be_ge(2));
expect!(item_count).to(be_le(4));

let name_count = count_substring(&body_str, "name='");
expect!(name_count).to(be_ge(2));
}

#[tokio::test]
async fn applies_random_array_generator_with_nested_elements_to_xml() {
let request = HttpRequest {
body: OptionalBody::Present("<people><person id='1'><address city='NYC'/></person></people>".into(), Some(XML.clone()), None),
generators: generators! { "BODY" => { "$.people.person" => Generator::RandomArray(2, 2) } },
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let person_count = count_xml_elements(&body_str, "person");
let address_count = count_xml_elements(&body_str, "address");

expect!(person_count).to(be_equal_to(2));
expect!(address_count).to(be_equal_to(2));
}

#[tokio::test]
async fn applies_multiple_independent_array_generators_to_xml() {
let request = HttpRequest {
body: OptionalBody::Present("<root><items><item value='1'/></items><other><entry x='2'/></other></root>".into(), Some(XML.clone()), None),
generators: generators! {
"BODY" => {
"$.root.items.item" => Generator::RandomArray(2, 3),
"$.root.other.entry" => Generator::RandomArray(3, 4)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
let entry_count = count_xml_elements(&body_str, "entry");

expect!(item_count).to(be_ge(2));
expect!(item_count).to(be_le(3));
expect!(entry_count).to(be_ge(3));
expect!(entry_count).to(be_le(4));
}

#[tokio::test]
async fn applies_nested_random_array_generator_to_xml() {
let request = HttpRequest {
body: OptionalBody::Present("<items><item><subitems><subitem value='1'/></subitems></item></items>".into(), Some(XML.clone()), None),
generators: generators! {
"BODY" => {
"$.items.item" => Generator::RandomArray(2, 3),
"$.items.item.subitems.subitem" => Generator::RandomArray(2, 3),
"$.items.item.subitems.subitem['@value']" => Generator::RandomInt(1, 1000)
}
},
.. HttpRequest::default()
};
let generated_request = generate_request(&request, &GeneratorTestMode::Provider, &hashmap!{}).await;
let body_str = generated_request.body.display_string();

let item_count = count_xml_elements(&body_str, "item");
expect!(item_count).to(be_ge(2));
expect!(item_count).to(be_le(3));

let subitem_count = count_xml_elements(&body_str, "subitem");
expect!(subitem_count).to(be_ge(4));
expect!(subitem_count).to(be_le(9));
}
}
4 changes: 2 additions & 2 deletions rust/pact_models/src/generators/form_urlencoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ impl ContentTypeHandler<String> for FormUrlEncodedHandler {
fn apply_key(
&mut self,
key: &DocPath,
generator: &dyn GenerateValue<String>,
generator: &Generator,
context: &HashMap<&str, Value>,
matcher: &Box<dyn VariantMatcher + Send + Sync>,
) {
let mut map: HashMap<String, usize> = HashMap::new();
for (param_key, param_value) in self.params.iter_mut() {
let index = map.entry(param_key.clone()).or_insert(0);
if key.eq(&DocPath::root().join(param_key.clone())) || key.eq(&DocPath::root().join(param_key.clone()).join_index(*index)) {
return match generator.generate_value(&param_value, context, matcher) {
return match generator.generate_value(&*param_value, context, matcher) {
Ok(new_value) => *param_value = new_value,
Err(_) => ()
}
Expand Down
Loading
Loading