From 89dd8c034a32b043f94c956ebfc2e21eb3a9c93f Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 31 May 2016 09:02:04 +0200 Subject: [PATCH] Added array size assertion (#283) Fixes #279 --- .../dsl/WireMockRequestStubStrategy.groovy | 2 +- .../util/DelegatingJsonVerifiable.java | 68 +++++++++++++++---- .../FinishedDelegatingJsonVerifiable.java | 8 +-- .../util/JsonToJsonPathsConverter.groovy | 31 +++++++++ .../util/MethodBufferingJsonVerifiable.java | 4 ++ .../JaxRsClientMethodBuilderSpec.groovy | 1 + .../MockMvcMethodBodyBuilderSpec.groovy | 4 ++ .../util/JsonToJsonPathsConverterSpec.groovy | 38 +++++++++-- gradle.properties | 2 +- 9 files changed, 131 insertions(+), 27 deletions(-) diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WireMockRequestStubStrategy.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WireMockRequestStubStrategy.groovy index 59d252a30c..07955fc699 100755 --- a/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WireMockRequestStubStrategy.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/dsl/WireMockRequestStubStrategy.groovy @@ -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 + requestPattern.bodyPatterns = values.findAll{ !it.assertsSize() }.collect { new ValuePattern(matchesJsonPath: it.jsonPath().replace("\\\\", "\\")) } ?: null as List } } else if (contentType == ContentType.XML) { requestPattern.bodyPatterns = [new ValuePattern(equalToXml: getMatchingStrategy(request.body.clientValue).clientValue.toString())] diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/DelegatingJsonVerifiable.java b/accurest-core/src/main/groovy/io/codearte/accurest/util/DelegatingJsonVerifiable.java index bd41fa1c78..0b927452cb 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/DelegatingJsonVerifiable.java +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/DelegatingJsonVerifiable.java @@ -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 methodsBuffer; + private static final Pattern FIELD_PATTERN = Pattern.compile("\\.field\\((\")?(.)+(\")?\\)"); + private static final Pattern ARRAY_PATTERN = Pattern.compile("\\.array\\((\")?(.)+(\")?\\)"); + + final JsonVerifiable delegate; + final LinkedList methodsBuffer; DelegatingJsonVerifiable(JsonVerifiable delegate, LinkedList methodsBuffer) { this.delegate = delegate; - this.methodsBuffer = new LinkedList(methodsBuffer); + this.methodsBuffer = new LinkedList<>(methodsBuffer); } DelegatingJsonVerifiable(JsonVerifiable delegate) { this.delegate = delegate; - this.methodsBuffer = new LinkedList(); + 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 queue = new LinkedList(methodsBuffer); + LinkedList 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 diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/FinishedDelegatingJsonVerifiable.java b/accurest-core/src/main/groovy/io/codearte/accurest/util/FinishedDelegatingJsonVerifiable.java index abf0678d07..8b89f4ea4b 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/FinishedDelegatingJsonVerifiable.java +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/FinishedDelegatingJsonVerifiable.java @@ -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()); - } - } diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy index ae7558b952..131dde47e1 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/JsonToJsonPathsConverter.groovy @@ -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() diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/util/MethodBufferingJsonVerifiable.java b/accurest-core/src/main/groovy/io/codearte/accurest/util/MethodBufferingJsonVerifiable.java index 49e0234cb0..df22b85bc8 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/util/MethodBufferingJsonVerifiable.java +++ b/accurest-core/src/main/groovy/io/codearte/accurest/util/MethodBufferingJsonVerifiable.java @@ -46,4 +46,8 @@ public interface MethodBufferingJsonVerifiable @Override MethodBufferingJsonVerifiable value(); + + boolean assertsSize(); + + boolean assertsConcreteValue(); } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy index 59a33c47c1..38a6a8b442 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/JaxRsClientMethodBuilderSpec.groovy @@ -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()) diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy index f42b7ef95a..538125006e 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/builder/MockMvcMethodBodyBuilderSpec.groovy @@ -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: diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/util/JsonToJsonPathsConverterSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/util/JsonToJsonPathsConverterSpec.groovy index 1645aa7475..5cfdfab42d 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/util/JsonToJsonPathsConverterSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/util/JsonToJsonPathsConverterSpec.groovy @@ -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()) diff --git a/gradle.properties b/gradle.properties index 45a95579c1..f9a69c8932 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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