From 346ecc48a8d459f22555875a8399578bb225912e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 10 Dec 2020 14:23:45 +0100 Subject: [PATCH] Added support for XPath for multiple elements; fixes gh-1546 --- .../util/xml/XmlToXPathsConverter.groovy | 7 +- .../util/xml/XmlToXPathsConverterSpec.groovy | 67 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy index 9b6bb1da9e..17d692c09e 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverter.groovy @@ -42,7 +42,7 @@ import org.springframework.cloud.contract.spec.internal.MatchingTypeValue import org.springframework.cloud.contract.spec.internal.PathBodyMatcher import static java.util.stream.Collectors.toList -import static javax.xml.xpath.XPathConstants.NODE +import static javax.xml.xpath.XPathConstants.NODESET import static org.apache.commons.lang3.StringUtils.isBlank import static org.w3c.dom.Node.ATTRIBUTE_NODE import static org.w3c.dom.Node.CDATA_SECTION_NODE @@ -52,7 +52,6 @@ import static org.w3c.dom.Node.DOCUMENT_TYPE_NODE import static org.w3c.dom.Node.NOTATION_NODE import static org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE import static org.w3c.dom.Node.TEXT_NODE - /** * @author Olga Maciaszek-Sharma * @since 2.1.0 @@ -69,8 +68,8 @@ class XmlToXPathsConverter { .parse(new InputSource(new StringReader(body as String))) xPath.setNamespaceContext(new DOMNamespaceContext(parsedXml.documentElement)) bodyMatchers?.matchers()?.each({ - Node node = xPath.evaluate(it.path(), parsedXml.documentElement, NODE) as Node - removeNode(node) + NodeList nodes = xPath.evaluate(it.path(), parsedXml.documentElement, NODESET) as NodeList + nodes.each {removeNode(it)} }) parsedXml.normalizeDocument() return xmlToString(parsedXml) diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy index d481e9de65..22ca5ea8e2 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/xml/XmlToXPathsConverterSpec.groovy @@ -1,11 +1,13 @@ package org.springframework.cloud.contract.verifier.util.xml -import org.springframework.cloud.contract.spec.internal.BodyMatchers +import javax.xml.xpath.XPathExpressionException + +import spock.lang.Issue import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll -import javax.xml.xpath.XPathExpressionException +import org.springframework.cloud.contract.spec.internal.BodyMatchers class XmlToXPathsConverterSpec extends Specification { @Shared @@ -28,46 +30,65 @@ class XmlToXPathsConverterSpec extends Specification { @Unroll def "should generate [#expectedValue] for xPath [#value]"() { expect: - value == expectedValue + value == expectedValue where: - value || expectedValue - XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", namedXml) || '''customer@test.com''' - XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", namedXml) || '''''' - XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", unnamedXml) || '''customer@test.com''' + value || expectedValue + XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", namedXml) || '''customer@test.com''' + XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", namedXml) || '''''' + XmlToXPathsConverter.retrieveValueFromBody("/customer/email/text()", unnamedXml) || '''customer@test.com''' } @Unroll def "should throw exception when searching for in existent name space"() { when: - XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", unnamedXml) + XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", unnamedXml) then: - def e = thrown(XPathExpressionException) - e.message.contains('Prefix must resolve to a namespace: ns1') + def e = thrown(XPathExpressionException) + e.message.contains('Prefix must resolve to a namespace: ns1') } @Unroll def "should generate matched path [#expectedValue] for xPath [#value]"() { expect: - value == expectedValue + value == expectedValue where: - value || expectedValue - XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(0).path() || '''/customer/email/text()''' - XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(1).path() || '''/customer/@first_custom_attribute''' - XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(2).path() || '''/customer/email/@second_custom_attribute''' - XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(3).path() || '''/customer/address/@third_custom_attribute''' + value || expectedValue + XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(0).path() || '''/customer/email/text()''' + XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(1).path() || '''/customer/@first_custom_attribute''' + XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(2).path() || '''/customer/email/@second_custom_attribute''' + XmlToXPathsConverter.mapToMatchers(attributesInChildXml).get(3).path() || '''/customer/address/@third_custom_attribute''' } @Unroll def "should remove elements to [#expectedValue] for xPath [#value]"() { given: - BodyMatchers m = new BodyMatchers() - m.xPath(xpath, m.byEquality()); + BodyMatchers m = new BodyMatchers() + m.xPath(xpath, m.byEquality()); expect: - result == XmlToXPathsConverter.removeMatchingXPaths(xml, m) + result == XmlToXPathsConverter.removeMatchingXPaths(xml, m) where: - xpath || xml || result - "/ns1:customer/email/text()" || namedXml || '''\n \n ''' - "/customer/email/text()" || namedXml || '''\n customer@test.com\n ''' - "/customer/email/text()" || unnamedXml || '''\n \n ''' + xpath || xml || result + "/ns1:customer/email/text()" || namedXml || '''\n \n ''' + "/customer/email/text()" || namedXml || '''\n customer@test.com\n ''' + "/customer/email/text()" || unnamedXml || '''\n \n ''' + } + + @Issue("#1546") + def "should remove multiple elements when xpath matches them"() { + given: + String test = '''\ + + + 123 + + + 234 + + +''' + BodyMatchers m = new BodyMatchers() + m.xPath("/root/*/id/text()", m.byEquality()); + expect: + XmlToXPathsConverter.removeMatchingXPaths(test, m) == '''\n \n \n \n \n \n \n''' } }