diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16e231698..4a8ac62f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,13 +18,13 @@ jobs: strategy: matrix: include: - - operating-system: ubuntu-20.04 + - operating-system: ubuntu-22.04 targets: x86_64-unknown-linux-gnu - - operating-system: ubuntu-20.04 + - operating-system: ubuntu-22.04 targets: x86_64-unknown-linux-musl - - operating-system: ubuntu-20.04 + - operating-system: ubuntu-22.04 targets: aarch64-unknown-linux-gnu - - operating-system: ubuntu-20.04 + - operating-system: ubuntu-22.04 targets: aarch64-unknown-linux-musl - operating-system: windows-2019 targets: x86_64-pc-windows-msvc @@ -108,6 +108,7 @@ jobs: working-directory: rust/pact_ffi - name: Upload Release Assets + if: ${{ github.event_name == 'release' }} uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/rust/pact_models/src/generators/mod.rs b/rust/pact_models/src/generators/mod.rs index 7abba1ede..f9efc9bd9 100644 --- a/rust/pact_models/src/generators/mod.rs +++ b/rust/pact_models/src/generators/mod.rs @@ -34,6 +34,7 @@ use crate::path_exp::DocPath; #[cfg(feature = "datetime")] mod date_expression_parser; #[cfg(feature = "datetime")] mod time_expression_parser; #[cfg(feature = "form_urlencoded")] pub mod form_urlencoded; +#[cfg(feature = "xml")] pub mod xml; /// Trait to represent matching logic to find a matching variant for the Array Contains generator pub trait VariantMatcher: Debug { diff --git a/rust/pact_models/src/generators/xml.rs b/rust/pact_models/src/generators/xml.rs new file mode 100644 index 000000000..6241df32d --- /dev/null +++ b/rust/pact_models/src/generators/xml.rs @@ -0,0 +1,866 @@ + +use std::collections::HashMap; + +use serde_json::Value; +use sxd_document::dom::{Document, Element, Attribute, ChildOfRoot, ChildOfElement}; +use sxd_document::writer::format_document; +use tracing::{debug, error, trace}; +use anyhow::{anyhow, Result}; +use itertools::Itertools; + +use crate::generators::{ContentTypeHandler, Generator, GeneratorTestMode, VariantMatcher, GenerateValue}; +use crate::path_exp::DocPath; +use crate::bodies::OptionalBody; + +/// Implementation of a content type handler for XML. +pub struct XmlHandler<'a> { + /// XML document to apply the generators to. + pub value: Document<'a> +} + +impl <'a> ContentTypeHandler for XmlHandler<'a> { + fn process_body( + &mut self, + generators: &HashMap, + mode: &GeneratorTestMode, + context: &HashMap<&str, Value>, + matcher: &Box + ) -> Result { + for (key, generator) in generators { + if generator.corresponds_to_mode(mode) { + debug!("Applying generator {:?} to key {}", generator, key); + self.apply_key(key, generator, context, matcher); + } + }; + + let mut w = Vec::new(); + match format_document(&self.value, &mut w) { + Ok(()) => Ok(OptionalBody::Present(w.into(), Some("application/xml".into()), None)), + Err(err) => Err(anyhow!("Failed to format xml document: {}", err).to_string()) + } + } + + fn apply_key( + &mut self, + key: &DocPath, + generator: &dyn GenerateValue, + context: &HashMap<&str, Value>, + matcher: &Box + ) { + for child in self.value.root().children() { + if let ChildOfRoot::Element(el) = child { + generate_values_for_xml_element(&el, key, generator, context, matcher, vec!["$".to_string()]) + } + } + } +} + +fn generate_values_for_xml_element<'a>( + el: &Element<'a>, + key: &DocPath, + generator: &dyn GenerateValue, + context: &HashMap<&str, Value>, + matcher: &Box, + parent_path: Vec +) { + trace!("generate_values_for_xml_element(parent_path: '{:?}')", parent_path); + + if key.len() < parent_path.len() + 2 { + return + } + + let mut path = parent_path.clone(); + path.push(xml_element_name(el)); + + if generate_values_for_xml_attribute(&el, key, generator, context, matcher, path.clone()) { + return + } + + if generate_values_for_xml_text(&el, key, generator, context, matcher, path.clone()) { + return + } + + if key.len() < path.len() + 2 { + return + } + + for child in el.children() { + if let ChildOfElement::Element(child_el) = child { + generate_values_for_xml_element(&child_el, key, generator, context, matcher, path.clone()) + } + } +} + +fn generate_values_for_xml_attribute<'a>( + el: &Element<'a>, + key: &DocPath, + generator: &dyn GenerateValue, + context: &HashMap<&str, Value>, + matcher: &Box, + path: Vec +) -> bool { + trace!("generate_values_for_xml_attribute(path: '{:?}')", path); + + if let Some(v) = key.last_field() { + if v.starts_with("@") { + for attr in el.attributes() { + let mut attr_path = path.clone(); + attr_path.push(format!("@{}", xml_attribute_name(attr))); + if key.matches_path_exactly(attr_path.iter().map(|p| p.as_str()).collect_vec().as_slice()) { + debug!("Generating xml attribute value at '{:?}'", attr_path); + match generator.generate_value(&attr.value().to_string(), context, matcher) { + Ok(new_value) => { + let new_attr = el.set_attribute_value(attr.name(), new_value.as_str()); + new_attr.set_preferred_prefix(attr.preferred_prefix()); + debug!("Generated value for attribute '{}' of xml element '{}'", xml_attribute_name(attr), xml_element_name(el)); + } + Err(err) => { + error!("Failed to generate the attribute, will use the original: {}", err); + } + } + return true + } + } + } + }; + false +} + + +fn generate_values_for_xml_text<'a>( + el: &Element<'a>, + key: &DocPath, + generator: &dyn GenerateValue, + context: &HashMap<&str, Value>, + matcher: &Box, + path: Vec +) -> bool { + trace!("generate_values_for_xml_text(path: '{:?}')", path); + + let mut txt_path = path.clone(); + txt_path.push("#text".to_string()); + + if !key.matches_path_exactly(txt_path.iter().map(|p| p.as_str()).collect_vec().as_slice()) { + return false + } + + let mut has_txt = false; + for child in el.children() { + if let ChildOfElement::Text(txt) = child { + has_txt = true; + debug!("Generating xml text at '{:?}'", txt_path); + match generator.generate_value(&txt.text().to_string(), context, matcher) { + Ok(new_value) => { + txt.set_text(new_value.as_str()); + debug!("Generated value for text of xml element '{}'", xml_element_name(el)); + } + Err(err) => { + error!("Failed to generate the text, will use the original: {}", err); + } + } + } + } + if !has_txt { + debug!("Generating xml text at '{:?}'", txt_path); + match generator.generate_value(&"".to_string(), context, matcher) { + Ok(new_value) => { + let text = el.document().create_text(new_value.as_str()); + el.append_child(text); + debug!("Generated value for text of xml element '{}'", xml_element_name(el)); + } + Err(err) => { + error!("Failed to generate the text, will use the original: {}", err); + } + } + } + true +} + +fn xml_element_name(el: &Element) -> String { + if let Some(ns) = el.preferred_prefix() { + format!("{}:{}", ns, el.name().local_part()) + } else { + el.name().local_part().to_string() + } +} + +fn xml_attribute_name(attr: Attribute) -> String { + if let Some(ns) = attr.preferred_prefix() { + format!("{}:{}", ns, attr.name().local_part()) + } else { + attr.name().local_part().to_string() + } +} + +#[cfg(test)] +mod tests { + use expectest::expect; + use expectest::prelude::*; + use test_log::test; + use maplit::hashmap; + use sxd_document::Package; + + use crate::generators::NoopVariantMatcher; + + use super::*; + use super::Generator; + + #[test] + fn applies_no_generator() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{}, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_non_existing_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.b['#text']") => Generator::RandomInt(0, 10), + DocPath::new_unwrap("$.b['@att']") => Generator::RandomInt(0, 10) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_empty_text() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_empty_text_beside_an_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_element("b")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_non_empty_text_before_an_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + e.append_child(d.create_element("b")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_non_empty_text_after_an_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_element("b")); + e.append_child(d.create_text("1")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_non_empty_text() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_multiple_non_empty_texts() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + e.append_child(d.create_element("b")); + e.append_child(d.create_text("2")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_multiple_elements() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + r.append_child(e); + let e = d.create_element("a"); + e.append_child(d.create_text("2")); + r.append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_an_element_with_namespace() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element(("http://example.com/namespace", "a")); + e.set_preferred_prefix(Some("n")); + e.append_child(d.create_text("1")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.n:a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_multiple_elements_with_namespace() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let e = d.create_element(("http://example.com/namespace1", "a")); + e.set_preferred_prefix(Some("n1")); + e.append_child(d.create_text("1")); + r.append_child(e); + let e = d.create_element(("http://example.com/namespace2", "a")); + e.set_preferred_prefix(Some("n2")); + e.append_child(d.create_text("2")); + r.append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.n1:a['#text']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.root.n2:a['#text']") => Generator::RandomInt(222, 222) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("111222".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_an_element_with_mixed_namespace() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let e = d.create_element(("http://example.com/namespace", "a")); + e.set_preferred_prefix(Some("n")); + e.append_child(d.create_text("1")); + r.append_child(e); + let e = d.create_element("a"); + e.append_child(d.create_text("2")); + r.append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.n:a['#text']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.root.a['#text']") => Generator::RandomInt(222, 222), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("111222".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_an_attribute() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.set_attribute_value("attr", "1"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['@attr']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_multiple_attributes() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.set_attribute_value("attr1", "1"); + e.set_attribute_value("attr2", "2"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let _ = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['@attr1']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.a['@attr2']") => Generator::RandomInt(222, 222) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(e.attribute("attr1").unwrap().value()).to(be_equal_to("111")); + expect!(e.attribute("attr2").unwrap().value()).to(be_equal_to("222")); + } + + #[test] + fn applies_the_generator_to_multiple_attributes_with_namespace() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + let a = e.set_attribute_value(("http://example.com/namespace1", "attr"), "1"); + a.set_preferred_prefix(Some("n1")); + let a = e.set_attribute_value(("http://example.com/namespace2", "attr"), "2"); + a.set_preferred_prefix(Some("n2")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let _ = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['@n1:attr']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.a['@n2:attr']") => Generator::RandomInt(222, 222) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(e.attribute(("http://example.com/namespace1", "attr")).unwrap().value()).to(be_equal_to("111")); + expect!(e.attribute(("http://example.com/namespace2", "attr")).unwrap().value()).to(be_equal_to("222")); + } + + #[test] + fn applies_the_generator_to_multiple_attributes_with_mixed_namespace() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + let a = e.set_attribute_value(("http://example.com/namespace", "attr"), "1"); + a.set_preferred_prefix(Some("n")); + e.set_attribute_value("attr", "2"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let _ = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['@n:attr']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.a['@attr']") => Generator::RandomInt(222, 222) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(e.attribute(("http://example.com/namespace", "attr")).unwrap().value()).to(be_equal_to("111")); + expect!(e.attribute("attr").unwrap().value()).to(be_equal_to("222")); + } + + #[test] + fn applies_the_generator_to_text_and_attribute() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + e.set_attribute_value("attr", "2"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.a['@attr']") => Generator::RandomInt(222, 222), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("111".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_and_attribute_of_nested_elements() { + let p = Package::new(); + let d = p.as_document(); + let ea = d.create_element("a"); + ea.append_child(d.create_text("1")); + d.root().append_child(ea); + let eb = d.create_element("b"); + eb.set_attribute_value("attr", "2"); + ea.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(111, 111), + DocPath::new_unwrap("$.a.b['@attr']") => Generator::RandomInt(222, 222), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("111".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_attribute_of_multiple_elements() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let e = d.create_element("a"); + e.set_attribute_value("attr", "1"); + r.append_child(e); + let e = d.create_element("a"); + e.set_attribute_value("attr", "2"); + r.append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.a['@attr']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_multiple_elements_in_different_path() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + let e = d.create_element("d"); + e.append_child(d.create_text("1")); + ec.append_child(e); + let e = d.create_element("d"); + e.append_child(d.create_text("2")); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + let e = d.create_element("e"); + e.append_child(d.create_text("3")); + ec.append_child(e); + let e = d.create_element("e"); + e.append_child(d.create_text("4")); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.*.c.*['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999999999999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_attribute_of_multiple_elements_in_different_path() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + let e = d.create_element("d"); + e.set_attribute_value("attr", "1"); + ec.append_child(e); + let e = d.create_element("d"); + e.set_attribute_value("attr", "2"); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + let e = d.create_element("e"); + e.set_attribute_value("attr", "3"); + ec.append_child(e); + let e = d.create_element("e"); + e.set_attribute_value("attr", "4"); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.*.c.*['@attr']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_unicode_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("俄语"); + e.append_child(d.create_text("данные")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.俄语['#text']") => Generator::Regex("语言".to_string()), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("<俄语>语言".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_attribute_of_unicode_element() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("俄语"); + e.set_attribute_value("լեզու", "ռուսերեն"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.俄语['@լեզու']") => Generator::Regex("😊".to_string()), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("<俄语 լեզու='😊'/>".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_beside_comment() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + e.append_child(d.create_comment("some explanation")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_and_escaping() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.append_child(d.create_text("1")); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['#text']") => Generator::Regex("".to_string()), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("<foo/>".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_attribute_and_escaping() { + let p = Package::new(); + let d = p.as_document(); + let e = d.create_element("a"); + e.set_attribute_value("attr", "1"); + d.root().append_child(e); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.a['@attr']") => Generator::Regex("' new-attr='\"val".to_string()), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_attribute_of_elements_in_middle() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + ec.set_attribute_value("attr", "1"); + let e = d.create_element("d"); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + ec.set_attribute_value("attr", "2"); + let e = d.create_element("e"); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.*.c['@attr']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } + + #[test] + fn applies_the_generator_to_text_of_elements_in_middle() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + ec.append_child(d.create_text("1")); + let e = d.create_element("d"); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + ec.append_child(d.create_text("2")); + let e = d.create_element("e"); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.root.*.c['#text']") => Generator::RandomInt(999, 999) + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("999999".into(), Some("application/xml".into()), None))); + } + + #[test] + fn not_apply_generator_to_text_of_elements_located_too_deep() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + let e = d.create_element("d"); + e.append_child(d.create_text("1")); + ec.append_child(e); + let e = d.create_element("d"); + e.append_child(d.create_text("2")); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + let e = d.create_element("e"); + e.append_child(d.create_text("3")); + ec.append_child(e); + let e = d.create_element("e"); + e.append_child(d.create_text("4")); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.*.d['#text']") => Generator::RandomInt(999, 999), + DocPath::new_unwrap("$.*.e['#text']") => Generator::RandomInt(999, 999), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("1234".into(), Some("application/xml".into()), None))); + } + + #[test] + fn not_apply_generator_to_attribute_of_elements_located_too_deep() { + let p = Package::new(); + let d = p.as_document(); + let r = d.create_element("root"); + d.root().append_child(r); + let ea = d.create_element("a"); + let ec = d.create_element("c"); + let e = d.create_element("d"); + e.set_attribute_value("attr", "1"); + ec.append_child(e); + let e = d.create_element("d"); + e.set_attribute_value("attr", "2"); + ec.append_child(e); + ea.append_child(ec); + r.append_child(ea); + let eb = d.create_element("b"); + let ec = d.create_element("c"); + let e = d.create_element("e"); + e.set_attribute_value("attr", "3"); + ec.append_child(e); + let e = d.create_element("e"); + e.set_attribute_value("attr", "4"); + ec.append_child(e); + eb.append_child(ec); + r.append_child(eb); + + let mut xml_handler = XmlHandler { value: d }; + + let result = xml_handler.process_body(&hashmap!{ + DocPath::new_unwrap("$.*.d['@attr']") => Generator::RandomInt(999, 999), + DocPath::new_unwrap("$.*.e['@attr']") => Generator::RandomInt(999, 999), + }, &GeneratorTestMode::Consumer, &hashmap!{}, &NoopVariantMatcher.boxed()); + + expect!(result.unwrap()).to(be_equal_to(OptionalBody::Present("".into(), Some("application/xml".into()), None))); + } +}