Added array size assertion (#283)

Fixes #279
This commit is contained in:
Marcin Grzejszczak
2016-05-31 09:02:04 +02:00
parent 0e4070ffc4
commit 89dd8c034a
9 changed files with 131 additions and 27 deletions

View File

@@ -72,7 +72,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy {
requestPattern.bodyPatterns = [new ValuePattern(jsonCompareMode: org.skyscreamer.jsonassert.JSONCompareMode.LENIENT,
equalToJson: JsonOutput.toJson(getMatchingStrategy(request.body.clientValue).clientValue) ) ]
} else {
requestPattern.bodyPatterns = values.collect { new ValuePattern(matchesJsonPath: it.jsonPath().replace("\\\\", "\\")) } ?: null as List<ValuePattern>
requestPattern.bodyPatterns = values.findAll{ !it.assertsSize() }.collect { new ValuePattern(matchesJsonPath: it.jsonPath().replace("\\\\", "\\")) } ?: null as List<ValuePattern>
}
} else if (contentType == ContentType.XML) {
requestPattern.bodyPatterns = [new ValuePattern(equalToXml: getMatchingStrategy(request.body.clientValue).clientValue.toString())]

View File

@@ -1,8 +1,9 @@
package io.codearte.accurest.util;
import com.toomuchcoding.jsonassert.JsonVerifiable;
import java.util.LinkedList;
import java.util.regex.Pattern;
import com.toomuchcoding.jsonassert.JsonVerifiable;
import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;
@@ -11,18 +12,21 @@ import static org.apache.commons.lang3.StringEscapeUtils.escapeJava;
*/
class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
private final JsonVerifiable delegate;
private final LinkedList<String> methodsBuffer;
private static final Pattern FIELD_PATTERN = Pattern.compile("\\.field\\((\")?(.)+(\")?\\)");
private static final Pattern ARRAY_PATTERN = Pattern.compile("\\.array\\((\")?(.)+(\")?\\)");
final JsonVerifiable delegate;
final LinkedList<String> methodsBuffer;
DelegatingJsonVerifiable(JsonVerifiable delegate,
LinkedList<String> methodsBuffer) {
this.delegate = delegate;
this.methodsBuffer = new LinkedList<String>(methodsBuffer);
this.methodsBuffer = new LinkedList<>(methodsBuffer);
}
DelegatingJsonVerifiable(JsonVerifiable delegate) {
this.delegate = delegate;
this.methodsBuffer = new LinkedList<String>();
this.methodsBuffer = new LinkedList<>();
}
private static String stringWithEscapedQuotes(Object object) {
@@ -32,7 +36,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
private static String wrapValueWithQuotes(Object value) {
return value instanceof String ?
"\"" + stringWithEscapedQuotes((String) value) + "\"" :
"\"" + stringWithEscapedQuotes(value) + "\"" :
value.toString();
}
@@ -103,6 +107,13 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
return verifiable;
}
@Override
public JsonVerifiable elementWithIndex(int i) {
DelegatingJsonVerifiable verifiable = new DelegatingJsonVerifiable(delegate.elementWithIndex(i), methodsBuffer);
verifiable.methodsBuffer.offer(".elementWithIndex(" + i + ")");
return verifiable;
}
@Override
public MethodBufferingJsonVerifiable iterationPassingArray() {
return new DelegatingJsonVerifiable(delegate, methodsBuffer);
@@ -183,6 +194,26 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
return new FinishedDelegatingJsonVerifiable(delegate, methodsBuffer);
}
@Override
public boolean assertsSize() {
for (String s : methodsBuffer) {
if (s.contains(".hasSize(")) {
return true;
}
}
return false;
}
@Override
public boolean assertsConcreteValue() {
for (String s : methodsBuffer) {
if (FIELD_PATTERN.matcher(s).matches()|| ARRAY_PATTERN.matcher(s).matches()) {
return true;
}
}
return false;
}
@Override
public JsonVerifiable withoutThrowingException() {
return delegate.withoutThrowingException();
@@ -198,6 +229,13 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
delegate.matchesJsonPath(s);
}
@Override
public JsonVerifiable hasSize(int size) {
FinishedDelegatingJsonVerifiable verifiable = new FinishedDelegatingJsonVerifiable(delegate.hasSize(size), methodsBuffer);
verifiable.methodsBuffer.offer(".hasSize(" + size + ")");
return verifiable;
}
@Override
public boolean isIteratingOverNamelessArray() {
return delegate.isIteratingOverNamelessArray();
@@ -219,7 +257,7 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
}
private String createMethodString() {
LinkedList<String> queue = new LinkedList<String>(methodsBuffer);
LinkedList<String> queue = new LinkedList<>(methodsBuffer);
StringBuilder stringBuffer = new StringBuilder();
while (!queue.isEmpty()) {
stringBuffer.append(queue.remove());
@@ -238,17 +276,19 @@ class DelegatingJsonVerifiable implements MethodBufferingJsonVerifiable {
if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null)
return false;
return methodsBuffer != null ?
methodsBuffer.equals(that.methodsBuffer) :
that.methodsBuffer == null;
if (delegate == null) {
return false;
}
if (delegate.jsonPath() == null && that.delegate.jsonPath() == null)
return true;
return delegate.jsonPath().equals(that.delegate.jsonPath());
}
@Override
public int hashCode() {
int result = delegate != null ? delegate.hashCode() : 0;
result = 31 * result + (methodsBuffer != null ? methodsBuffer.hashCode() : 0);
return result;
int result = delegate != null ? delegate.jsonPath().hashCode() : 0;
return 31 * result;
}
@Override

View File

@@ -1,9 +1,9 @@
package io.codearte.accurest.util;
import com.toomuchcoding.jsonassert.JsonVerifiable;
import java.util.LinkedList;
import com.toomuchcoding.jsonassert.JsonVerifiable;
/**
* @author Marcin Grzejszczak
*/
@@ -14,8 +14,4 @@ class FinishedDelegatingJsonVerifiable extends DelegatingJsonVerifiable {
super(delegate, methodsBuffer);
}
FinishedDelegatingJsonVerifiable(JsonVerifiable delegate) {
super(delegate, new LinkedList<String>());
}
}

View File

@@ -7,11 +7,22 @@ import io.codearte.accurest.dsl.internal.ExecutionProperty
import io.codearte.accurest.dsl.internal.OptionalProperty
import java.util.regex.Pattern
/**
* I would like to apologize to anyone who is reading this class. Since JSON is a hectic structure
* this class is also hectic. The idea is to traverse the JSON structure and build a set of
* JSON Paths together with methods needed to be called to build them.
*
* @author Marcin Grzejszczak
*/
class JsonToJsonPathsConverter {
/**
* In case of issues with size assertion just provide this property as system property
* equal to "false" and then size assertion will be disabled
*/
private static final String SIZE_ASSERTION_SYSTEM_PROP = "accurest.assert.size"
private static final Boolean SERVER_SIDE = false
private static final Boolean CLIENT_SIDE = true
@@ -61,18 +72,21 @@ class JsonToJsonPathsConverter {
return convertWithKey(Map, key, value as Map, closure)
// JSON with a list of primitives ["a", "b", "c"] in root issue #266
} else if (key.isIteratingOverNamelessArray() && value instanceof List && listContainsOnlyPrimitives(value)) {
addSizeVerificationForListWithPrimitives(key, closure, value)
value.each {
traverseRecursively(Object, key.arrayField().contains(ContentUtils.returnParsedObject(it)),
ContentUtils.returnParsedObject(it), closure)
}
// JSON containing list of primitives { "partners":[ { "role":"AGENT", "payment_methods":[ "BANK", "CASH" ] } ]
} else if (value instanceof List && listContainsOnlyPrimitives(value)) {
addSizeVerificationForListWithPrimitives(key, closure, value)
value.each {
traverseRecursively(Object, valueToAsserter(key.arrayField(), ContentUtils.returnParsedObject(it)),
ContentUtils.returnParsedObject(it), closure)
}
} else if (value instanceof List) {
MethodBufferingJsonVerifiable jsonPathVerifiable = createAsserterFromList(key, value)
addSizeVerificationForListWithPrimitives(key, closure, value)
value.each { def element ->
traverseRecursively(List, createAsserterFromListElement(jsonPathVerifiable, ContentUtils.returnParsedObject(element)),
ContentUtils.returnParsedObject(element), closure)
@@ -89,6 +103,23 @@ class JsonToJsonPathsConverter {
}
}
// Size verification: https://github.com/Codearte/accurest/issues/279
private static void addSizeVerificationForListWithPrimitives(MethodBufferingJsonVerifiable key, Closure closure, List value) {
if (System.getProperty(SIZE_ASSERTION_SYSTEM_PROP, "true") == "false") {
return
}
if (isRootElement(key) || key.assertsConcreteValue()) {
if (value.size() > 0) {
closure(key.hasSize(value.size()), value)
}
}
}
private static boolean isRootElement(MethodBufferingJsonVerifiable key) {
return key.jsonPath() == '$'
}
// If you have a list of not-only primitives it can contain different sets of elements (maps, lists, primitives)
private static MethodBufferingJsonVerifiable createAsserterFromList(MethodBufferingJsonVerifiable key, List value) {
if (key.isIteratingOverNamelessArray()) {
return key.array()

View File

@@ -46,4 +46,8 @@ public interface MethodBufferingJsonVerifiable
@Override
MethodBufferingJsonVerifiable value();
boolean assertsSize();
boolean assertsConcreteValue();
}

View File

@@ -98,6 +98,7 @@ class JaxRsClientMethodBuilderSpec extends Specification implements WireMockStub
then:
blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("a").isEqualTo("sth")""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").hasSize(2)""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("b").isEqualTo("sthElse")""")
and:
stubMappingIsValidWireMockStub(new WireMockStubStrategy("Test", new Contract(null, false, 0, null), contractDsl).toWireMockClientStub())

View File

@@ -166,6 +166,7 @@ class MockMvcMethodBodyBuilderSpec extends Specification implements WireMockStub
then:
blockBuilder.toString().contains("""assertThatJson(parsedJson).field("property1").isEqualTo("a")""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("a").isEqualTo("sth")""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").hasSize(2)""")
blockBuilder.toString().contains("""assertThatJson(parsedJson).array("property2").contains("b").isEqualTo("sthElse")""")
and:
stubMappingIsValidWireMockStub(contractDsl)
@@ -1251,6 +1252,7 @@ World.'''"""
builder.then(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('assertThatJson(parsedJson).hasSize(5)')
test.contains('assertThatJson(parsedJson).arrayField().contains("Java8").value()')
test.contains('assertThatJson(parsedJson).arrayField().contains("Spring").value()')
test.contains('assertThatJson(parsedJson).arrayField().contains("Java").value()')
@@ -1284,6 +1286,7 @@ World.'''"""
builder.then(blockBuilder)
def test = blockBuilder.toString()
then:
test.contains('assertThatJson(parsedJson).hasSize(2)')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Programming").value()')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Java").value()')
test.contains('assertThatJson(parsedJson).array().arrayField().isEqualTo("Spring").value()')
@@ -1293,6 +1296,7 @@ World.'''"""
"MockMvcSpockMethodBuilder" | { GroovyDsl dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl) }
"MockMvcJUnitMethodBuilder" | { GroovyDsl dsl -> new MockMvcJUnitMethodBodyBuilder(dsl) }
}
@Issue('47')
def "should generate async body when async flag set in response"() {
given:

View File

@@ -353,12 +353,16 @@ class JsonToJsonPathsConverterSpec extends Specification {
it.method()== """.array("property2").contains("a").isEqualTo("sth")""" &&
it.jsonPath() == """\$.property2[*][?(@.a == 'sth')]"""
}
pathAndValues.find {
it.method()== """.array("property2").hasSize(2)""" &&
it.jsonPath() == """\$.property2[*]"""
}
pathAndValues.find {
it.method()== """.array("property2").contains("b").isEqualTo("sthElse")""" &&
it.jsonPath() == """\$.property2[*][?(@.b == 'sthElse')]"""
}
and:
pathAndValues.size() == 3
pathAndValues.size() == 4
}
def "should generate assertions for a response body containing map with integers as keys"() {
@@ -404,8 +408,12 @@ class JsonToJsonPathsConverterSpec extends Specification {
it.method()== """.array().contains("property2").isEqualTo("b")""" &&
it.jsonPath() == """\$[*][?(@.property2 == 'b')]"""
}
pathAndValues.find {
it.method()== """.hasSize(2)""" &&
it.jsonPath() == """\$"""
}
and:
pathAndValues.size() == 2
pathAndValues.size() == 3
}
def "should generate assertions for array inside response body element"() {
@@ -427,8 +435,12 @@ class JsonToJsonPathsConverterSpec extends Specification {
it.method()== """.array("property1").contains("property3").isEqualTo("test2")""" &&
it.jsonPath() == """\$.property1[*][?(@.property3 == 'test2')]"""
}
pathAndValues.find {
it.method()== """.array("property1").hasSize(2)""" &&
it.jsonPath() == """\$.property1[*]"""
}
and:
pathAndValues.size() == 2
pathAndValues.size() == 3
}
def "should generate assertions for nested objects in response body"() {
@@ -509,8 +521,12 @@ class JsonToJsonPathsConverterSpec extends Specification {
it.method()== """.array("errors").contains("message").isEqualTo("incorrect_format")""" &&
it.jsonPath() == """\$.errors[*][?(@.message == 'incorrect_format')]"""
}
pathAndValues.find {
it.method()== """.array("errors").hasSize(1)""" &&
it.jsonPath() == """\$.errors[*]"""
}
and:
pathAndValues.size() == 2
pathAndValues.size() == 3
}
def "should manage to parse a double array"() {
@@ -549,8 +565,20 @@ class JsonToJsonPathsConverterSpec extends Specification {
it.method()== """.array().field("place").field("bounding_box").array("coordinates").array().arrayField().isEqualTo(38.791645)""" &&
it.jsonPath() == """\$[*].place.bounding_box.coordinates[*][*][?(@ == 38.791645)]"""
}
pathAndValues.find {
it.method()== """.hasSize(1)""" &&
it.jsonPath() == """\$"""
}
pathAndValues.find {
it.method()== """.array().field("place").field("bounding_box").array("coordinates").array().hasSize(2)""" &&
it.jsonPath() == """\$[*].place.bounding_box.coordinates[*][*]"""
}
pathAndValues.find {
it.method()== """.array().field("place").field("bounding_box").array("coordinates").hasSize(1)""" &&
it.jsonPath() == """\$[*].place.bounding_box.coordinates[*]"""
}
and:
pathAndValues.size() == 4
pathAndValues.size() == 7
and:
pathAndValues.each {
JsonAssertion.assertThat(json).matchesJsonPath(it.jsonPath())

View File

@@ -2,7 +2,7 @@ nexusUsername =
nexusPassword =
wiremockVersion = 2.0.10-beta
jsonassertVersion = 0.4.5
jsonassertVersion = 0.4.7
BOM_VERSION=Brixton-1.0.0.RC1
springBootVersion=1.3.3.RELEASE