From 58b992d99eaf277da7671e53c0a7fd3a65bd9534 Mon Sep 17 00:00:00 2001 From: cschaetzlein Date: Mon, 16 Nov 2015 16:15:04 +0100 Subject: [PATCH 1/2] Allow individual attributes in an XML element to be documented Documenting an XML attribute in the XML payload leads to a NullPointerException since no parent nodes exists for an XML attribute. Rather than always trying to remove a node from its parent, this commit changes the logic to apply special treament to nodes that are attributes and remove the attribute from its owning element instead. Closes gh-167 --- .../restdocs/payload/XmlContentHandler.java | 11 ++++++++++- .../payload/ResponseFieldsSnippetTests.java | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java index a971f049..89b0aecf 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java @@ -34,6 +34,7 @@ import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -115,7 +116,15 @@ class XmlContentHandler implements ContentHandler { } for (int i = 0; i < matchingNodes.getLength(); i++) { Node node = matchingNodes.item(i); - node.getParentNode().removeChild(node); + + if (node.getNodeType() == Node.ATTRIBUTE_NODE) { + Attr attr = (Attr) node; + attr.getOwnerElement().removeAttributeNode(attr); + } + else { + node.getParentNode().removeChild(node); + } + } } if (payload.getChildNodes().getLength() > 0) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java index 337e6e8c..72048301 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java @@ -192,6 +192,21 @@ public class ResponseFieldsSnippetTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); } + + @Test + public void undocumentedXmlWithAttributeResponseField() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("The following parts of the payload were not" + + " documented:\r\n\r\n 5\r\n\r\n")); + new ResponseFieldsSnippet(Arrays.asList(new FieldDescriptor("/a/b/@id").type("").description(""))) + .document(new OperationBuilder("undocumented-xml-response-field", + this.snippet.getOutputDirectory()) + .response() + .content("5") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); + } @Test public void xmlResponseFieldWithNoType() throws IOException { From acb87b98b872caa5c591a6ac5e3930d93ff42229 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 16 Nov 2015 17:41:42 +0000 Subject: [PATCH 2/2] Improve testing of XML attribute handling Closes gh-166 --- .../restdocs/payload/XmlContentHandler.java | 1 - .../payload/ResponseFieldsSnippetTests.java | 76 ++++++++++++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java index 89b0aecf..79801373 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java @@ -116,7 +116,6 @@ class XmlContentHandler implements ContentHandler { } for (int i = 0; i < matchingNodes.getLength(); i++) { Node node = matchingNodes.item(i); - if (node.getNodeType() == Node.ATTRIBUTE_NODE) { Attr attr = (Attr) node; attr.getOwnerElement().removeAttributeNode(attr); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java index 72048301..ad6653d3 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java @@ -192,22 +192,78 @@ public class ResponseFieldsSnippetTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); } - + @Test - public void undocumentedXmlWithAttributeResponseField() throws IOException { - this.thrown.expect(SnippetException.class); - this.thrown - .expectMessage(equalTo("The following parts of the payload were not" - + " documented:\r\n\r\n 5\r\n\r\n")); - new ResponseFieldsSnippet(Arrays.asList(new FieldDescriptor("/a/b/@id").type("").description(""))) - .document(new OperationBuilder("undocumented-xml-response-field", - this.snippet.getOutputDirectory()) + public void xmlAttribute() throws IOException { + this.snippet.expectResponseFields("xml-attribute").withContents( + tableWithHeader("Path", "Type", "Description").row("a", "b", "one").row( + "a/@id", "c", "two")); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type("b"), fieldWithPath("a/@id").description("two").type("c"))) + .document(new OperationBuilder("xml-attribute", this.snippet + .getOutputDirectory()) .response() - .content("5") + .content("foo") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) .build()); } + @Test + public void missingXmlAttribute() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown + .expectMessage(equalTo("Fields with the following paths were not found" + + " in the payload: [a/@id]")); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type("b"), fieldWithPath("a/@id").description("two").type("c"))) + .document(new OperationBuilder("missing-xml-attribute", this.snippet + .getOutputDirectory()) + .response() + .content("foo") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); + } + + @Test + public void missingOptionalXmlAttribute() throws IOException { + this.snippet.expectResponseFields("missing-optional-xml-attribute").withContents( + tableWithHeader("Path", "Type", "Description").row("a", "b", "one").row( + "a/@id", "c", "two")); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type("b"), fieldWithPath("a/@id").description("two").type("c") + .optional())).document(new OperationBuilder( + "missing-optional-xml-attribute", this.snippet.getOutputDirectory()) + .response().content("foo") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); + } + + @Test + public void undocumentedAttributeDoesNotCauseFailure() throws IOException { + this.snippet.expectResponseFields("undocumented-attribute").withContents( + tableWithHeader("Path", "Type", "Description").row("a", "a", "one")); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type("a"))).document(new OperationBuilder("undocumented-attribute", + this.snippet.getOutputDirectory()).response() + .content("bar") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); + } + + @Test + public void documentedXmlAttributesAreRemoved() throws IOException { + this.thrown.expect(SnippetException.class); + this.thrown.expectMessage(equalTo(String + .format("The following parts of the payload were not documented:" + + "%nbar%n"))); + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/@id").description("one") + .type("a"))).document(new OperationBuilder( + "documented-attribute-is-removed", this.snippet.getOutputDirectory()) + .response().content("bar") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build()); + } + @Test public void xmlResponseFieldWithNoType() throws IOException { this.thrown.expect(FieldTypeRequiredException.class);