Skip to content

Commit b6cba6d

Browse files
committed
chore(v2-matching-engine): Fix for XML with type matching rules
1 parent 1aaea53 commit b6cba6d

1 file changed

Lines changed: 249 additions & 10 deletions

File tree

  • rust/pact_matching/src/engine/bodies

rust/pact_matching/src/engine/bodies/xml.rs

Lines changed: 249 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ impl XMLPlanBuilder {
130130
}
131131

132132
let mut for_each_node = ExecutionPlanNode::action("for-each");
133-
for_each_node.add(ExecutionPlanNode::value_node(format!("{}*", child_name)));
133+
let marker = format!("{}*", child_name);
134+
for_each_node.add(ExecutionPlanNode::value_node(marker.as_str()));
134135
for_each_node.add(ExecutionPlanNode::resolve_current_value(&p));
135-
let item_path = p.join("[*]");
136+
let item_path = path.join(marker.as_str());
136137

137138
self.process_element(context, elements[0], Some(0), &item_path, &mut for_each_node);
138139

@@ -151,7 +152,7 @@ impl XMLPlanBuilder {
151152
let text_nodes = text_nodes(element);
152153
let p = path.join("#text");
153154
let no_markers = remove_marker(&p);
154-
let no_indices = drop_indices(&p);
155+
let no_indices = drop_indices(&no_markers);
155156
let matchers = context.select_best_matcher_from(&no_markers, &no_indices)
156157
.filter(|matcher| !matcher.is_type_matcher())
157158
.remove_duplicates();
@@ -288,12 +289,12 @@ impl PlanBodyBuilder for XMLPlanBuilder {
288289
mod tests {
289290
use bytes::Bytes;
290291
use pretty_assertions::assert_eq;
291-
292+
use pact_models::matchingrules;
293+
use pact_models::matchingrules::MatchingRule;
292294
use crate::engine::bodies::{PlanBodyBuilder, XMLPlanBuilder};
293295
use crate::engine::context::{MatchingConfiguration, PlanMatchingContext};
294296

295-
#[test]
296-
#[cfg(feature = "xml")]
297+
#[test_log::test]
297298
fn xml_plan_builder_with_very_simple_xml() {
298299
let builder = XMLPlanBuilder::new();
299300
let context = PlanMatchingContext::default();
@@ -331,8 +332,7 @@ mod tests {
331332
)"#, buffer);
332333
}
333334

334-
#[test]
335-
#[cfg(feature = "xml")]
335+
#[test_log::test]
336336
fn xml_plan_builder_with_allowed_unexpected_values() {
337337
let builder = XMLPlanBuilder::new();
338338
let context = PlanMatchingContext {
@@ -373,8 +373,7 @@ mod tests {
373373
)"#, buffer);
374374
}
375375

376-
#[test]
377-
#[cfg(feature = "xml")]
376+
#[test_log::test]
378377
fn xml_plan_builder_with_simple_xml() {
379378
let builder = XMLPlanBuilder::new();
380379
let context = PlanMatchingContext::default();
@@ -637,6 +636,246 @@ mod tests {
637636
)
638637
)
639638
)
639+
)"#, buffer);
640+
}
641+
642+
#[test_log::test]
643+
fn matching_rule_on_element_text() {
644+
let builder = XMLPlanBuilder::new();
645+
let matching_rules = matchingrules! {
646+
"body" => { "$.values.value" => [ MatchingRule::Regex("\\d+".to_string()) ] }
647+
};
648+
let context = PlanMatchingContext {
649+
matching_rules: matching_rules.rules_for_category("body").unwrap_or_default(),
650+
.. PlanMatchingContext::default()
651+
};
652+
let xml = r#"<?xml version="1.0" encoding="UTF-8"?> <values><value>100</value></values>"#;
653+
let content = Bytes::copy_from_slice(xml.as_bytes());
654+
let node = builder.build_plan(&content, &context).unwrap();
655+
let mut buffer = String::new();
656+
node.pretty_form(&mut buffer, 0);
657+
658+
assert_eq!(r#"%tee (
659+
%xml:parse (
660+
$.body
661+
),
662+
:$ (
663+
%if (
664+
%check:exists (
665+
~>$.values
666+
),
667+
:$.values (
668+
:#text (
669+
%expect:empty (
670+
%to-string (
671+
~>$.values['#text']
672+
)
673+
)
674+
),
675+
%expect:only-entries (
676+
['value'],
677+
~>$.values
678+
),
679+
%expect:count (
680+
UINT(1),
681+
~>$.values.value,
682+
%join (
683+
'Expected 1 <value> child element but there were ',
684+
%length (
685+
~>$.values.value
686+
)
687+
)
688+
),
689+
%if (
690+
%check:exists (
691+
~>$.values.value[0]
692+
),
693+
:$.values.value[0] (
694+
:#text (
695+
#{'#text must match the regular expression /\\d+/'},
696+
%match:regex (
697+
'100',
698+
%to-string (
699+
~>$.values.value[0]['#text']
700+
),
701+
json:{"regex":"\\d+"}
702+
)
703+
),
704+
%expect:empty (
705+
~>$.values.value[0]
706+
)
707+
),
708+
%error (
709+
'Was expecting an XML element /values/value/0 but it was missing'
710+
)
711+
)
712+
),
713+
%error (
714+
'Was expecting an XML element /values but it was missing'
715+
)
716+
)
717+
)
718+
)"#, buffer);
719+
}
720+
721+
#[test_log::test]
722+
fn matching_rule_on_attribute() {
723+
let builder = XMLPlanBuilder::new();
724+
let matching_rules = matchingrules! {
725+
"body" => { "$.value.@id" => [ MatchingRule::Regex("\\d+".to_string()) ] }
726+
};
727+
let context = PlanMatchingContext {
728+
matching_rules: matching_rules.rules_for_category("body").unwrap_or_default(),
729+
.. PlanMatchingContext::default()
730+
};
731+
let xml = r#"<?xml version="1.0" encoding="UTF-8"?> <value id="100"/>"#;
732+
let content = Bytes::copy_from_slice(xml.as_bytes());
733+
let node = builder.build_plan(&content, &context).unwrap();
734+
let mut buffer = String::new();
735+
node.pretty_form(&mut buffer, 0);
736+
737+
assert_eq!(r#"%tee (
738+
%xml:parse (
739+
$.body
740+
),
741+
:$ (
742+
%if (
743+
%check:exists (
744+
~>$.value
745+
),
746+
:$.value (
747+
:attributes (
748+
:$.value['@id'] (
749+
#{'@id must match the regular expression /\\d+/'},
750+
%if (
751+
%check:exists (
752+
~>$.value['@id']
753+
),
754+
%match:regex (
755+
'100',
756+
%xml:value (
757+
~>$.value['@id']
758+
),
759+
json:{"regex":"\\d+"}
760+
)
761+
)
762+
),
763+
%expect:entries (
764+
['id'],
765+
%xml:attributes (
766+
~>$.value
767+
),
768+
%join (
769+
'The following expected attributes were missing: ',
770+
%join-with (
771+
', ',
772+
** (
773+
%apply ()
774+
)
775+
)
776+
)
777+
),
778+
%expect:only-entries (
779+
['id'],
780+
%xml:attributes (
781+
~>$.value
782+
)
783+
)
784+
),
785+
:#text (
786+
%expect:empty (
787+
%to-string (
788+
~>$.value['#text']
789+
)
790+
)
791+
),
792+
%expect:empty (
793+
~>$.value
794+
)
795+
),
796+
%error (
797+
'Was expecting an XML element /value but it was missing'
798+
)
799+
)
800+
)
801+
)"#, buffer);
802+
}
803+
804+
#[test_log::test]
805+
fn type_matching_rule_on_element() {
806+
let builder = XMLPlanBuilder::new();
807+
let matching_rules = matchingrules! {
808+
"body" => { "$.values" => [ MatchingRule::MinType(2) ] }
809+
};
810+
let context = PlanMatchingContext {
811+
matching_rules: matching_rules.rules_for_category("body").unwrap_or_default(),
812+
.. PlanMatchingContext::default()
813+
};
814+
let xml = r#"<?xml version="1.0" encoding="UTF-8"?> <values><value>100</value><value>300</value></values>"#;
815+
let content = Bytes::copy_from_slice(xml.as_bytes());
816+
let node = builder.build_plan(&content, &context).unwrap();
817+
let mut buffer = String::new();
818+
node.pretty_form(&mut buffer, 0);
819+
820+
assert_eq!(r#"%tee (
821+
%xml:parse (
822+
$.body
823+
),
824+
:$ (
825+
%if (
826+
%check:exists (
827+
~>$.values
828+
),
829+
:$.values (
830+
:#text (
831+
%expect:empty (
832+
%to-string (
833+
~>$.values['#text']
834+
)
835+
)
836+
),
837+
%expect:only-entries (
838+
['value'],
839+
~>$.values
840+
),
841+
#{'value must match by type and have at least 2 items'},
842+
%match:min-type (
843+
xml:'<value>100</value>',
844+
~>$.values.value,
845+
json:{"min":2}
846+
),
847+
%for-each (
848+
'value*',
849+
~>$.values.value,
850+
%if (
851+
%check:exists (
852+
~>$.values['value*']
853+
),
854+
:$.values['value*'] (
855+
:#text (
856+
%match:equality (
857+
'100',
858+
%to-string (
859+
~>$.values['value*']['#text']
860+
),
861+
NULL
862+
)
863+
),
864+
%expect:empty (
865+
~>$.values['value*']
866+
)
867+
),
868+
%error (
869+
'Was expecting an XML element /values/value* but it was missing'
870+
)
871+
)
872+
)
873+
),
874+
%error (
875+
'Was expecting an XML element /values but it was missing'
876+
)
877+
)
878+
)
640879
)"#, buffer);
641880
}
642881
}

0 commit comments

Comments
 (0)