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

@@ -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);
}
}