Improve field type resolution for fields with optional ancestors

Fixes gh-567
This commit is contained in:
Andy Wilkinson
2019-09-18 11:53:42 +01:00
parent 3a5cc58fab
commit 3f66b066f7
8 changed files with 176 additions and 97 deletions

View File

@@ -153,7 +153,8 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
if (this.subsectionExtractor != null) {
content = verifyContent(this.subsectionExtractor.extractSubsection(content, contentType));
}
ContentHandler contentHandler = ContentHandler.forContent(content, contentType);
ContentHandler contentHandler = ContentHandler.forContentWithDescriptors(content, contentType,
this.fieldDescriptors);
validateFieldDocumentation(contentHandler);
@@ -193,10 +194,9 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
}
private void validateFieldDocumentation(ContentHandler payloadHandler) {
List<FieldDescriptor> missingFields = payloadHandler.findMissingFields(this.fieldDescriptors);
List<FieldDescriptor> missingFields = payloadHandler.findMissingFields();
String undocumentedPayload = this.ignoreUndocumentedFields ? null
: payloadHandler.getUndocumentedContent(this.fieldDescriptors);
String undocumentedPayload = this.ignoreUndocumentedFields ? null : payloadHandler.getUndocumentedContent();
if (!missingFields.isEmpty() || StringUtils.hasText(undocumentedPayload)) {
String message = "";

View File

@@ -30,41 +30,40 @@ interface ContentHandler extends FieldTypeResolver {
/**
* Finds the fields that are missing from the handler's payload. A field is missing if
* it is described by one of the {@code fieldDescriptors} but is not present in the
* payload.
* @param fieldDescriptors the descriptors
* it is described but is not present in the payload.
* @return descriptors for the fields that are missing from the payload
* @throws PayloadHandlingException if a failure occurs
*/
List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors);
List<FieldDescriptor> findMissingFields();
/**
* Returns modified content, formatted as a String, that only contains the fields that
* are undocumented. A field is undocumented if it is present in the handler's content
* but is not described by the given {@code fieldDescriptors}. If the content is
* completely documented, {@code null} is returned
* @param fieldDescriptors the descriptors
* but is not described. If the content is completely documented, {@code null} is
* returned
* @return the undocumented content, or {@code null} if all of the content is
* documented
* @throws PayloadHandlingException if a failure occurs
*/
String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors);
String getUndocumentedContent();
/**
* Create a {@link ContentHandler} for the given content type and payload.
* Create a {@link ContentHandler} for the given content type and payload, described
* by the given descriptors.
* @param content the payload
* @param contentType the content type
* @param descriptors descriptors of the content
* @return the ContentHandler
* @throws PayloadHandlingException if no known ContentHandler can handle the content
*/
static ContentHandler forContent(byte[] content, MediaType contentType) {
static ContentHandler forContentWithDescriptors(byte[] content, MediaType contentType,
List<FieldDescriptor> descriptors) {
try {
return new JsonContentHandler(content);
return new JsonContentHandler(content, descriptors);
}
catch (Exception je) {
try {
return new XmlContentHandler(content);
return new XmlContentHandler(content, descriptors);
}
catch (Exception xe) {
throw new PayloadHandlingException(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2018 the original author or authors.
* Copyright 2014-2019 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.
@@ -16,6 +16,9 @@
package org.springframework.restdocs.payload;
import java.util.Collections;
import java.util.List;
import org.springframework.http.MediaType;
/**
@@ -33,9 +36,25 @@ public interface FieldTypeResolver {
* @param content the payload that the {@code FieldTypeResolver} should handle
* @param contentType the content type of the payload
* @return the {@code FieldTypeResolver}
* @deprecated since 2.0.4 in favor of
* {@link #forContentWithDescriptors(byte[], MediaType, List)}
*/
@Deprecated
static FieldTypeResolver forContent(byte[] content, MediaType contentType) {
return ContentHandler.forContent(content, contentType);
return forContentWithDescriptors(content, contentType, Collections.emptyList());
}
/**
* Create a {@code FieldTypeResolver} for the given {@code content} and
* {@code contentType}, described by the given {@code descriptors}.
* @param content the payload that the {@code FieldTypeResolver} should handle
* @param contentType the content type of the payload
* @param descriptors the descriptors of the content
* @return the {@code FieldTypeResolver}
*/
static FieldTypeResolver forContentWithDescriptors(byte[] content, MediaType contentType,
List<FieldDescriptor> descriptors) {
return ContentHandler.forContentWithDescriptors(content, contentType, descriptors);
}
/**

View File

@@ -44,18 +44,21 @@ class JsonContentHandler implements ContentHandler {
private final byte[] rawContent;
JsonContentHandler(byte[] content) {
private final List<FieldDescriptor> fieldDescriptors;
JsonContentHandler(byte[] content, List<FieldDescriptor> fieldDescriptors) {
this.rawContent = content;
this.fieldDescriptors = fieldDescriptors;
readContent();
}
@Override
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
public List<FieldDescriptor> findMissingFields() {
List<FieldDescriptor> missingFields = new ArrayList<>();
Object payload = readContent();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
if (!fieldDescriptor.isOptional() && !this.fieldProcessor.hasField(fieldDescriptor.getPath(), payload)
&& !isNestedBeneathMissingOptionalField(fieldDescriptor, fieldDescriptors, payload)) {
&& !isNestedBeneathMissingOptionalField(fieldDescriptor, payload)) {
missingFields.add(fieldDescriptor);
}
}
@@ -63,9 +66,8 @@ class JsonContentHandler implements ContentHandler {
return missingFields;
}
private boolean isNestedBeneathMissingOptionalField(FieldDescriptor missing, List<FieldDescriptor> fieldDescriptors,
Object payload) {
List<FieldDescriptor> candidates = new ArrayList<>(fieldDescriptors);
private boolean isNestedBeneathMissingOptionalField(FieldDescriptor missing, Object payload) {
List<FieldDescriptor> candidates = new ArrayList<>(this.fieldDescriptors);
candidates.remove(missing);
for (FieldDescriptor candidate : candidates) {
if (candidate.isOptional() && missing.getPath().startsWith(candidate.getPath())
@@ -98,9 +100,9 @@ class JsonContentHandler implements ContentHandler {
}
@Override
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
public String getUndocumentedContent() {
Object content = readContent();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
if (describesSubsection(fieldDescriptor)) {
this.fieldProcessor.removeSubsection(fieldDescriptor.getPath(), content);
}
@@ -154,7 +156,9 @@ class JsonContentHandler implements ContentHandler {
.discoverFieldTypes(fieldDescriptor.getPath(), readContent())
.coalesce(fieldDescriptor.isOptional());
if (descriptorFieldType == JsonFieldType.VARIES || descriptorFieldType == actualFieldType
|| (fieldDescriptor.isOptional() && actualFieldType == JsonFieldType.NULL)) {
|| (fieldDescriptor.isOptional() && actualFieldType == JsonFieldType.NULL)
|| (isNestedBeneathMissingOptionalField(fieldDescriptor, readContent())
&& actualFieldType == JsonFieldType.VARIES)) {
return descriptorFieldType;
}
throw new FieldTypesDoNotMatchException(fieldDescriptor, actualFieldType);

View File

@@ -53,7 +53,9 @@ class XmlContentHandler implements ContentHandler {
private final byte[] rawContent;
XmlContentHandler(byte[] rawContent) {
private final List<FieldDescriptor> fieldDescriptors;
XmlContentHandler(byte[] rawContent, List<FieldDescriptor> fieldDescriptors) {
try {
this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
@@ -61,14 +63,15 @@ class XmlContentHandler implements ContentHandler {
throw new IllegalStateException("Failed to create document builder", ex);
}
this.rawContent = rawContent;
this.fieldDescriptors = fieldDescriptors;
readPayload();
}
@Override
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
public List<FieldDescriptor> findMissingFields() {
List<FieldDescriptor> missingFields = new ArrayList<>();
Document payload = readPayload();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
if (!fieldDescriptor.isOptional()) {
NodeList matchingNodes = findMatchingNodes(fieldDescriptor, payload);
if (matchingNodes.getLength() == 0) {
@@ -103,10 +106,10 @@ class XmlContentHandler implements ContentHandler {
}
@Override
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
public String getUndocumentedContent() {
Document payload = readPayload();
List<Node> matchedButNotRemoved = new ArrayList<>();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
NodeList matchingNodes;
try {
matchingNodes = (NodeList) createXPath(fieldDescriptor.getPath()).evaluate(payload,

View File

@@ -16,6 +16,8 @@
package org.springframework.restdocs.payload;
import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -35,21 +37,43 @@ public class FieldTypeResolverTests {
public ExpectedException thrownException = ExpectedException.none();
@Test
public void returnJsonFieldTypeResolver() {
@Deprecated
public void whenForContentCalledWithJsonContentThenReturnsJsonFieldTypeResolver() {
assertThat(FieldTypeResolver.forContent("{\"field\": \"value\"}".getBytes(), MediaType.APPLICATION_JSON))
.isInstanceOf(JsonContentHandler.class);
}
@Test
public void returnXmlContentHandler() {
@Deprecated
public void whenForContentCalledWithXmlContentThenReturnsXmlContentHandler() {
assertThat(FieldTypeResolver.forContent("<a><b>5</b></a>".getBytes(), MediaType.APPLICATION_XML))
.isInstanceOf(XmlContentHandler.class);
}
@Test
public void throwOnInvalidContent() {
@Deprecated
public void whenForContentIsCalledWithInvalidContentThenExceptionIsThrown() {
this.thrownException.expect(PayloadHandlingException.class);
FieldTypeResolver.forContent("some".getBytes(), MediaType.APPLICATION_XML);
}
@Test
public void whenForContentWithDescriptorsCalledWithJsonContentThenReturnsJsonFieldTypeResolver() {
assertThat(FieldTypeResolver.forContentWithDescriptors("{\"field\": \"value\"}".getBytes(),
MediaType.APPLICATION_JSON, Collections.emptyList())).isInstanceOf(JsonContentHandler.class);
}
@Test
public void whenForContentWithDescriptorsCalledWithXmlContentThenReturnsXmlContentHandler() {
assertThat(FieldTypeResolver.forContentWithDescriptors("<a><b>5</b></a>".getBytes(), MediaType.APPLICATION_XML,
Collections.emptyList())).isInstanceOf(XmlContentHandler.class);
}
@Test
public void whenForContentWithDescriptorsIsCalledWithInvalidContentThenExceptionIsThrown() {
this.thrownException.expect(PayloadHandlingException.class);
FieldTypeResolver.forContentWithDescriptors("some".getBytes(), MediaType.APPLICATION_XML,
Collections.emptyList());
}
}

View File

@@ -16,7 +16,9 @@
package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Rule;
@@ -39,137 +41,163 @@ public class JsonContentHandlerTests {
@Test
public void typeForFieldWithNullValueMustMatch() {
this.thrown.expect(FieldTypesDoNotMatchException.class);
new JsonContentHandler("{\"a\": null}".getBytes())
.resolveFieldType(new FieldDescriptor("a").type(JsonFieldType.STRING));
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING);
new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor)).resolveFieldType(descriptor);
}
@Test
public void typeForFieldWithNotNullAndThenNullValueMustMatch() {
this.thrown.expect(FieldTypesDoNotMatchException.class);
new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes())
.resolveFieldType(new FieldDescriptor("a[].id").type(JsonFieldType.STRING));
FieldDescriptor descriptor = new FieldDescriptor("a[].id").type(JsonFieldType.STRING);
new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes(), Arrays.asList(descriptor))
.resolveFieldType(descriptor);
}
@Test
public void typeForFieldWithNullAndThenNotNullValueMustMatch() {
this.thrown.expect(FieldTypesDoNotMatchException.class);
new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes())
.resolveFieldType(new FieldDescriptor("a.[].id").type(JsonFieldType.STRING));
FieldDescriptor descriptor = new FieldDescriptor("a.[].id").type(JsonFieldType.STRING);
new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(), Arrays.asList(descriptor))
.resolveFieldType(descriptor);
}
@Test
public void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() {
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes())
.resolveFieldType(new FieldDescriptor("a[].id").optional());
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor)).resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void typeForOptionalFieldWithNullAndThenNumberIsNumber() {
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes())
.resolveFieldType(new FieldDescriptor("a[].id").optional());
FieldDescriptor descriptor = new FieldDescriptor("a[].id").optional();
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor)).resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void typeForFieldWithNumberAndThenNullValueIsVaries() {
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes())
.resolveFieldType(new FieldDescriptor("a[].id"));
FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes(),
Arrays.asList(descriptor)).resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.VARIES);
}
@Test
public void typeForFieldWithNullAndThenNumberIsVaries() {
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes())
.resolveFieldType(new FieldDescriptor("a[].id"));
FieldDescriptor descriptor = new FieldDescriptor("a[].id");
Object fieldType = new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes(),
Arrays.asList(descriptor)).resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.VARIES);
}
@Test
public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() {
Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes())
.resolveFieldType(new FieldDescriptor("a").type(JsonFieldType.STRING).optional());
FieldDescriptor descriptor = new FieldDescriptor("a").type(JsonFieldType.STRING).optional();
Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes(), Arrays.asList(descriptor))
.resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.STRING);
}
@Test
public void typeForFieldWithSometimesPresentOptionalAncestorCanBeProvidedExplicitly() throws IOException {
FieldDescriptor descriptor = new FieldDescriptor("a.[].b.c").type(JsonFieldType.NUMBER);
FieldDescriptor ancestor = new FieldDescriptor("a.[].b").optional();
Object fieldType = new JsonContentHandler("{\"a\":[ { \"d\": 4}, {\"b\":{\"c\":5}, \"d\": 4}]}".getBytes(),
Arrays.asList(descriptor, ancestor)).resolveFieldType(descriptor);
assertThat((JsonFieldType) fieldType).isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void failsFastWithNonJsonContent() {
this.thrown.expect(PayloadHandlingException.class);
new JsonContentHandler("Non-JSON content".getBytes());
new JsonContentHandler("Non-JSON content".getBytes(), Collections.emptyList());
}
@Test
public void describedFieldThatIsNotPresentIsConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes())
.findMissingFields(
Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"), new FieldDescriptor("c")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
descriptors).findMissingFields();
assertThat(missingFields.size()).isEqualTo(1);
assertThat(missingFields.get(0).getPath()).isEqualTo("c");
}
@Test
public void describedOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c").optional()));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a"), new FieldDescriptor("b"),
new FieldDescriptor("c").optional());
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\": \"alpha\", \"b\":\"bravo\"}".getBytes(),
descriptors).findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsPresentIsConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":\"alpha\",\"b\":\"bravo\"}".getBytes(),
descriptors).findMissingFields();
assertThat(missingFields.size()).isEqualTo(1);
assertThat(missingFields.get(0).getPath()).isEqualTo("a.c");
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalFieldThatIsNotPresentIsNotConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("b"),
new FieldDescriptor("a.c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"b\":\"bravo\"}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}
@Test
public void describedFieldThatIsNotPresentNestedBeneathOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("outer"),
new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("outer"),
new FieldDescriptor("outer[]").optional(), new FieldDescriptor("outer[].inner"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"outer\":[]}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}
@Test
public void describedSometimesPresentFieldThatIsChildOfSometimesPresentOptionalArrayIsNotConsideredMissing() {
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].c").optional(),
new FieldDescriptor("a.[].c.d"));
List<FieldDescriptor> missingFields = new JsonContentHandler(
"{\"a\":[ {\"b\": \"bravo\"}, {\"b\": \"bravo\", \"c\": { \"d\": \"delta\"}}]}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a.[].c").optional(),
new FieldDescriptor("a.[].c.d")));
"{\"a\":[ {\"b\": \"bravo\"}, {\"b\": \"bravo\", \"c\": { \"d\": \"delta\"}}]}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}
@Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatIsEmptyIsNotConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[]}]}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}
@Test
public void describedMissingFieldThatIsChildOfNestedOptionalArrayThatContainsAnObjectIsConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a.[].b").optional(),
new FieldDescriptor("a.[].b.[]").optional(), new FieldDescriptor("a.[].b.[].c"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":[{\"b\":[{}]}]}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(1);
assertThat(missingFields.get(0).getPath()).isEqualTo("a.[].b.[].c");
}
@Test
public void describedMissingFieldThatIsChildOfOptionalObjectThatIsNullIsNotConsideredMissing() {
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":null}".getBytes())
.findMissingFields(Arrays.asList(new FieldDescriptor("a").optional(), new FieldDescriptor("a.b")));
List<FieldDescriptor> descriptors = Arrays.asList(new FieldDescriptor("a").optional(),
new FieldDescriptor("a.b"));
List<FieldDescriptor> missingFields = new JsonContentHandler("{\"a\":null}".getBytes(), descriptors)
.findMissingFields();
assertThat(missingFields.size()).isEqualTo(0);
}

View File

@@ -17,6 +17,8 @@
package org.springframework.restdocs.payload;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
@@ -38,56 +40,56 @@ public class XmlContentHandlerTests {
@Test
public void topLevelElementCanBeDocumented() {
String undocumentedContent = createHandler("<a>5</a>")
.getUndocumentedContent(Arrays.asList(fieldWithPath("a").type("a").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(fieldWithPath("a").type("a").description("description"));
String undocumentedContent = createHandler("<a>5</a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isNull();
}
@Test
public void nestedElementCanBeDocumentedLeavingAncestors() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(fieldWithPath("a/b").type("b").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(fieldWithPath("a/b").type("b").description("description"));
String undocumentedContent = createHandler("<a><b>5</b></a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isEqualTo(String.format("<a/>%n"));
}
@Test
public void fieldDescriptorDoesNotDocumentEntireSubsection() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(fieldWithPath("a").type("a").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(fieldWithPath("a").type("a").description("description"));
String undocumentedContent = createHandler("<a><b>5</b></a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isEqualTo(String.format("<a>%n <b>5</b>%n</a>%n"));
}
@Test
public void subsectionDescriptorDocumentsEntireSubsection() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(subsectionWithPath("a").type("a").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(subsectionWithPath("a").type("a").description("description"));
String undocumentedContent = createHandler("<a><b>5</b></a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isNull();
}
@Test
public void multipleElementsCanBeInDescendingOrderDocumented() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(fieldWithPath("a").type("a").description("description"),
fieldWithPath("a/b").type("b").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(fieldWithPath("a").type("a").description("description"),
fieldWithPath("a/b").type("b").description("description"));
String undocumentedContent = createHandler("<a><b>5</b></a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isNull();
}
@Test
public void multipleElementsCanBeInAscendingOrderDocumented() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(fieldWithPath("a/b").type("b").description("description"),
fieldWithPath("a").type("a").description("description")));
List<FieldDescriptor> descriptors = Arrays.asList(fieldWithPath("a/b").type("b").description("description"),
fieldWithPath("a").type("a").description("description"));
String undocumentedContent = createHandler("<a><b>5</b></a>", descriptors).getUndocumentedContent();
assertThat(undocumentedContent).isNull();
}
@Test
public void failsFastWithNonXmlContent() {
this.thrown.expect(PayloadHandlingException.class);
createHandler("non-XML content");
createHandler("non-XML content", Collections.emptyList());
}
private XmlContentHandler createHandler(String xml) {
return new XmlContentHandler(xml.getBytes());
private XmlContentHandler createHandler(String xml, List<FieldDescriptor> descriptors) {
return new XmlContentHandler(xml.getBytes(), descriptors);
}
}