fixes gh-1446
This commit is contained in:
committed by
Marcin Grzejszczak
parent
242c8208a9
commit
90be2f0b50
@@ -45,7 +45,9 @@ class XmlBodyVerificationBuilder implements BodyMethodGeneration {
|
||||
private void addXmlProcessingLines(final BlockBuilder blockBuilder,
|
||||
String responseString) {
|
||||
Arrays.asList(
|
||||
"DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()",
|
||||
"DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance()",
|
||||
"builderFactory.setNamespaceAware(true)",
|
||||
"DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder()",
|
||||
"Document parsedXml = documentBuilder.parse(new InputSource(new StringReader("
|
||||
+ responseString + ")))")
|
||||
.forEach(it -> {
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util.xml
|
||||
|
||||
import com.sun.org.apache.xml.internal.security.utils.DOMNamespaceContext
|
||||
|
||||
import javax.xml.namespace.NamespaceContext
|
||||
import java.util.stream.IntStream
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
@@ -64,10 +61,13 @@ class XmlToXPathsConverter {
|
||||
|
||||
static Object removeMatchingXPaths(Object body, BodyMatchers bodyMatchers) {
|
||||
XPath xPath = XPathFactory.newInstance().newXPath()
|
||||
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance()
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance()
|
||||
builderFactory.setNamespaceAware(true)
|
||||
DocumentBuilder documentBuilder = builderFactory
|
||||
.newDocumentBuilder()
|
||||
Document parsedXml = documentBuilder
|
||||
.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)
|
||||
|
||||
@@ -35,8 +35,11 @@ import javax.xml.xpath.XPathFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import org.springframework.cloud.contract.verifier.util.xml.DOMNamespaceContext;
|
||||
|
||||
/**
|
||||
* Helper class for the generated tests.
|
||||
*
|
||||
@@ -81,8 +84,10 @@ public final class ContractVerifierUtil {
|
||||
*/
|
||||
public static String valueFromXPath(Document parsedXml, String path) {
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Element documentElement = parsedXml.getDocumentElement();
|
||||
xPath.setNamespaceContext(new DOMNamespaceContext(documentElement));
|
||||
try {
|
||||
return xPath.evaluate(path, parsedXml.getDocumentElement());
|
||||
return xPath.evaluate(path, documentElement);
|
||||
}
|
||||
catch (XPathExpressionException exception) {
|
||||
LOG.error("Incorrect xpath provided: " + path, exception);
|
||||
@@ -99,9 +104,10 @@ public final class ContractVerifierUtil {
|
||||
*/
|
||||
public static Node nodeFromXPath(Document parsedXml, String path) {
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Element documentElement = parsedXml.getDocumentElement();
|
||||
xPath.setNamespaceContext(new DOMNamespaceContext(documentElement));
|
||||
try {
|
||||
return (Node) xPath.evaluate(path, parsedXml.getDocumentElement(),
|
||||
XPathConstants.NODE);
|
||||
return (Node) xPath.evaluate(path, documentElement, XPathConstants.NODE);
|
||||
}
|
||||
catch (XPathExpressionException exception) {
|
||||
LOG.error("Incorrect xpath provided: " + path, exception);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util.xml;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
public class DOMNamespaceContext implements NamespaceContext {
|
||||
|
||||
private final Map<String, String> namespaceMap = new HashMap<>();
|
||||
|
||||
public DOMNamespaceContext(Node contextNode) {
|
||||
addNamespaces(contextNode);
|
||||
}
|
||||
|
||||
public String getNamespaceURI(String arg0) {
|
||||
return namespaceMap.get(arg0);
|
||||
}
|
||||
|
||||
public String getPrefix(String arg0) {
|
||||
for (Map.Entry<String, String> entry : namespaceMap.entrySet()) {
|
||||
if (entry.getValue().equals(arg0)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<String> getPrefixes(String arg0) {
|
||||
return namespaceMap.keySet().iterator();
|
||||
}
|
||||
|
||||
private void addNamespaces(Node element) {
|
||||
if (element.getParentNode() != null) {
|
||||
addNamespaces(element.getParentNode());
|
||||
}
|
||||
if (element instanceof Element) {
|
||||
Element el = (Element) element;
|
||||
NamedNodeMap map = el.getAttributes();
|
||||
for (int x = 0; x < map.getLength(); x++) {
|
||||
Attr attr = (Attr) map.item(x);
|
||||
if ("xmlns".equals(attr.getPrefix())) {
|
||||
namespaceMap.put(attr.getLocalName(), attr.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,6 +89,9 @@ class YamlContractConverterSpec extends Specification {
|
||||
URL ymlRestXmlFile = YamlContractConverterSpec.
|
||||
getResource("/yml/contract_rest_xml.yml")
|
||||
File ymlRestXml = new File(ymlRestXmlFile.toURI())
|
||||
URL ymlRestNamedXmlFile = YamlContractConverterSpec.
|
||||
getResource("/yml/contract_rest_named_xml.yml")
|
||||
File ymlRestNamedXml = new File(ymlRestNamedXmlFile.toURI())
|
||||
URL oa3SpecUrl = YamlContractConverterSpec.getResource('/yml/oa3/openapi_petstore.yml')
|
||||
File oa3File = new File(oa3SpecUrl.toURI())
|
||||
YamlContractConverter converter = new YamlContractConverter()
|
||||
@@ -110,6 +113,11 @@ class YamlContractConverterSpec extends Specification {
|
||||
<valueWithTypeMatch>string</valueWithTypeMatch>
|
||||
<key><complex>foo</complex></key>
|
||||
</test>
|
||||
'''
|
||||
String xmlContractBodyWithNamespaces = '''
|
||||
<ns1:customer xmlns:ns1="http://demo.com/testns">
|
||||
<email>customer@test.com</email>
|
||||
</ns1:customer>
|
||||
'''
|
||||
|
||||
def "should convert YAML with Cookies to DSL"() {
|
||||
@@ -1331,6 +1339,34 @@ metadata: null
|
||||
.replaceAll("\n", "").replaceAll(' ', '')
|
||||
}
|
||||
|
||||
def "should convert REST YAML with XML with namespace in request and response to DSL"() {
|
||||
given:
|
||||
assert converter.isAccepted(ymlRestNamedXml)
|
||||
when:
|
||||
Collection<Contract> contracts = converter.convertFrom(ymlRestNamedXml)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
Contract contract = contracts.first()
|
||||
contract.request.headers.entries.find({
|
||||
it.name == 'Content-Type' && it.clientValue == "application/xml" && it.serverValue == "application/xml"
|
||||
})
|
||||
contract.request.bodyMatchers.matchers.isEmpty()
|
||||
contract.request.body.clientValue.replaceAll("\n", "").
|
||||
replaceAll(' ', '') == xmlContractBodyWithNamespaces.replaceAll("\n", "").
|
||||
replaceAll(' ', '')
|
||||
contract.request.body.serverValue.replaceAll("\n", "").
|
||||
replaceAll(' ', '') == xmlContractBodyWithNamespaces.replaceAll("\n", "").
|
||||
replaceAll(' ', '')
|
||||
and:
|
||||
contract.response.bodyMatchers.matchers.isEmpty()
|
||||
contract.response.body.clientValue.replaceAll("\n", "")
|
||||
.replaceAll(' ', '') == xmlContractBodyWithNamespaces
|
||||
.replaceAll("\n", "").replaceAll(' ', '')
|
||||
contract.response.body.serverValue.replaceAll("\n", "")
|
||||
.replaceAll(' ', '') == xmlContractBodyWithNamespaces
|
||||
.replaceAll("\n", "").replaceAll(' ', '')
|
||||
}
|
||||
|
||||
def "should accept a yaml file that is a proper scc YAML contract"() {
|
||||
when:
|
||||
def accepted = converter.isAccepted(ymlWithRest3)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.springframework.cloud.contract.verifier.util.xml
|
||||
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatchers
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
@@ -23,18 +24,32 @@ class XmlToXPathsConverterSpec extends Specification {
|
||||
expect:
|
||||
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 inexistent name space"() {
|
||||
def "should throw exception when searching for in existent name space"() {
|
||||
when:
|
||||
XmlToXPathsConverter.retrieveValueFromBody("/ns1:customer/email/text()", unnamedXml)
|
||||
then:
|
||||
def e = thrown(XPathExpressionException)
|
||||
e.message.contains('Prefix must resolve to a namespace: ns1')
|
||||
}
|
||||
|
||||
@Unroll
|
||||
def "should remove elements to [#expectedValue] for xPath [#value]"() {
|
||||
given:
|
||||
BodyMatchers m = new BodyMatchers()
|
||||
m.xPath(xpath, m.byEquality());
|
||||
expect:
|
||||
result == XmlToXPathsConverter.removeMatchingXPaths(xml, m)
|
||||
where:
|
||||
xpath || xml || result
|
||||
"/ns1:customer/email/text()" || namedXml || '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><ns1:customer xmlns:ns1="http://demo.com/testns">\n <email/>\n </ns1:customer>'''
|
||||
"/customer/email/text()" || namedXml || '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><ns1:customer xmlns:ns1="http://demo.com/testns">\n <email>customer@test.com</email>\n </ns1:customer>'''
|
||||
"/customer/email/text()" || unnamedXml || '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><customer>\n <email/>\n </customer>'''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.springframework.cloud.contract.verifier.builder;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.BodyMatchers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThat;
|
||||
|
||||
public class XmlBodyVerificationBuilderTest {
|
||||
|
||||
private static final String xml = "<customer>\r\n"
|
||||
+ " <email>customer@test.com</email>\r\n" + " </customer>";
|
||||
|
||||
@Test
|
||||
public void shouldAddXmlProcessingLines() {
|
||||
// Given
|
||||
XmlBodyVerificationBuilder builder = new XmlBodyVerificationBuilder(
|
||||
new Contract(), Optional.of(";"));
|
||||
BlockBuilder blockBuilder = new BlockBuilder(" ");
|
||||
BodyMatchers matchers = new BodyMatchers();
|
||||
// When
|
||||
builder.addXmlResponseBodyCheck(blockBuilder, xml, matchers, xml, true);
|
||||
// Then
|
||||
String test = blockBuilder.toString();
|
||||
assertThat(test).contains("DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();")
|
||||
.contains("builderFactory.setNamespaceAware(true);")
|
||||
.contains("DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();")
|
||||
.contains("Document parsedXml = documentBuilder.parse(new InputSource(new StringReader(")
|
||||
.contains(xml);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ContractVerifierUtilTest {
|
||||
|
||||
private final String unnamedXml = "<customer>\r\n"
|
||||
+ " <email>customer@test.com</email>\r\n" + " </customer>";
|
||||
|
||||
private final String namedXml = "<ns1:customer xmlns:ns1=\"http://demo.com/testns\">\r\n"
|
||||
+ " <email>customer@test.com</email>\r\n" + " </ns1:customer>";
|
||||
|
||||
@Test
|
||||
public void shouldGetValueFromXPath()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(unnamedXml)));
|
||||
// When
|
||||
String value = ContractVerifierUtil.valueFromXPath(parsedXml,
|
||||
"/customer/email/text()");
|
||||
// Then
|
||||
assertThat(value).isEqualTo("customer@test.com");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldThrowExceptionOnIllegalValueFromXPath()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(unnamedXml)));
|
||||
// When
|
||||
ContractVerifierUtil.valueFromXPath(parsedXml, "/ns1:customer/email/text()");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetValueFromXPathWithNamespace()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(namedXml)));
|
||||
// When
|
||||
String value = ContractVerifierUtil.valueFromXPath(parsedXml,
|
||||
"/ns1:customer/email/text()");
|
||||
// Then
|
||||
assertThat(value).isEqualTo("customer@test.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetEmptyValueFromXPathWithNamespace()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(namedXml)));
|
||||
// When
|
||||
String value = ContractVerifierUtil.valueFromXPath(parsedXml,
|
||||
"/customer/email/text()");
|
||||
// Then
|
||||
assertThat(value).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetNodeFromXPath()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(unnamedXml)));
|
||||
// When
|
||||
Node node = ContractVerifierUtil.nodeFromXPath(parsedXml,
|
||||
"/customer/email/text()");
|
||||
// Then
|
||||
assertThat(node.getTextContent()).isEqualTo("customer@test.com");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldThrowExceptionOnIllegalNodeFromXPath()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(unnamedXml)));
|
||||
// When
|
||||
ContractVerifierUtil.nodeFromXPath(parsedXml, "/ns1:customer/email/text()");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetNodeFromXPathWithNamespace()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(namedXml)));
|
||||
// When
|
||||
Node node = ContractVerifierUtil.nodeFromXPath(parsedXml,
|
||||
"/ns1:customer/email/text()");
|
||||
// Then
|
||||
assertThat(node.getTextContent()).isEqualTo("customer@test.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetEmptyNodeFromXPathWithNamespace()
|
||||
throws ParserConfigurationException, IOException, SAXException {
|
||||
// Given
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
|
||||
Document parsedXml = documentBuilder
|
||||
.parse(new InputSource(new StringReader(namedXml)));
|
||||
// When
|
||||
Node node = ContractVerifierUtil.nodeFromXPath(parsedXml,
|
||||
"/customer/email/text()");
|
||||
// Then
|
||||
assertThat(node).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
request:
|
||||
method: GET
|
||||
url: /getymlResponse
|
||||
headers:
|
||||
Content-Type: application/xml
|
||||
body: |
|
||||
<ns1:customer xmlns:ns1="http://demo.com/testns">
|
||||
<email>customer@test.com</email>
|
||||
</ns1:customer>
|
||||
response:
|
||||
status: 200
|
||||
headers:
|
||||
Content-Type: application/xml
|
||||
body: |
|
||||
<ns1:customer xmlns:ns1="http://demo.com/testns">
|
||||
<email>customer@test.com</email>
|
||||
</ns1:customer>
|
||||
Reference in New Issue
Block a user