From 2bd95b6edb7b748280aaed62cd49f2354bd86628 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 11 Jan 2017 19:13:49 +0100 Subject: [PATCH] Changed byValue to byEquality --- docs/src/main/asciidoc/verifier/contract.adoc | 68 +++++++++---------- .../spec/internal/BodyMatchers.groovy | 5 +- .../spec/internal/MatchingType.groovy | 29 +++++++- .../camel/StubRunnerCamelPredicate.java | 2 +- .../StubRunnerIntegrationMessageSelector.java | 2 +- .../StubRunnerStreamMessageSelector.java | 2 +- .../DslToWireMockClientConverterSpec.groovy | 8 +-- .../verifier/builder/MethodBodyBuilder.groovy | 28 ++++++-- .../WireMockRequestStubStrategy.groovy | 6 +- .../util/JsonToJsonPathsConverter.groovy | 35 ++++++---- ...vcMethodBodyBuilderWithMatchersSpec.groovy | 8 +-- .../util/JsonToJsonPathsConverterSpec.groovy | 34 ++++++++-- 12 files changed, 151 insertions(+), 76 deletions(-) diff --git a/docs/src/main/asciidoc/verifier/contract.adoc b/docs/src/main/asciidoc/verifier/contract.adoc index 920283e798..eb90d920db 100644 --- a/docs/src/main/asciidoc/verifier/contract.adoc +++ b/docs/src/main/asciidoc/verifier/contract.adoc @@ -255,8 +255,8 @@ part of your contract. The other is called `testMatchers` which is present in th Currently we support only JSON Path based matchers with the following matching possibilities. For `stubMatchers`: -- `byValue(...)` - the value taken from the response via the provided JSON Path needs -to be equal to the provided value +- `byEquality()` - the value taken from the response via the provided JSON Path needs +to be equal to the provided value in the contract - `byRegex(...)` - the value taken from the response via the provided JSON Path needs to match the regex - `byDate()` - the value taken from the response via the provided JSON Path needs to @@ -268,8 +268,8 @@ match the regex for ISO Time For `testMatchers`: -- `byValue(...)` - the value taken from the response via the provided JSON Path needs -to be equal to the provided value +- `byEquality()` - the value taken from the response via the provided JSON Path needs +to be equal to the provided value in the contract - `byRegex(...)` - the value taken from the response via the provided JSON Path needs to match the regex - `byDate()` - the value taken from the response via the provided JSON Path needs to @@ -311,38 +311,38 @@ assertions and the one from matchers with an `and` section): [source,java,indent=0] ---- - // given: - MockMvcRequestSpecification request = given() - .header("Content-Type", "application/json") - .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}"); + // given: + MockMvcRequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\"}"); - // when: - ResponseOptions response = given().spec(request) - .get("/get"); + // when: + ResponseOptions response = given().spec(request) + .get("/get"); - // then: - assertThat(response.statusCode()).isEqualTo(200); - assertThat(response.header("Content-Type")).matches("application/json.*"); - // and: - DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); - assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo"); - // and: - assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}"); - assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123); - assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*"); - assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc"); - assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?"); - assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)"); - assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); - assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); - assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); - assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class); - assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isLessThanOrEqualTo(1); - assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isGreaterThanOrEqualTo(3); - assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class); - assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isStrictlyBetween(1, 3); + // then: + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.header("Content-Type")).matches("application/json.*"); + // and: + DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); + assertThatJson(parsedJson).field("valueWithoutAMatcher").isEqualTo("foo"); + // and: + assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}"); + assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123); + assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*"); + assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc"); + assertThat(parsedJson.read("$.number", String.class)).matches("-?\\d*(\\.\\d+)?"); + assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)"); + assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"); + assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"); + assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class); + assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMin", java.util.Collection.class).size()).isLessThanOrEqualTo(1); + assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMax", java.util.Collection.class).size()).isGreaterThanOrEqualTo(3); + assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class); + assertThat(parsedJson.read("$.valueWithMinMax", java.util.Collection.class).size()).isStrictlyBetween(1, 3); ---- and the WireMock stub like this: diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy index adbf00b873..b865c32d62 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/BodyMatchers.groovy @@ -46,9 +46,8 @@ class BodyMatchers { return new MatchingTypeValue(MatchingType.REGEX, regex) } - MatchingTypeValue byValue(Object value) { - assert value - return new MatchingTypeValue(MatchingType.EQUALITY, value) + MatchingTypeValue byEquality() { + return new MatchingTypeValue(MatchingType.EQUALITY, null) } boolean equals(o) { diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy index 1b2a513362..16395c1aec 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/MatchingType.groovy @@ -11,5 +11,32 @@ import groovy.transform.CompileStatic */ @CompileStatic enum MatchingType { - EQUALITY, TYPE, DATE, TIME, TIMESTAMP, REGEX + /** + * The default approach - ensures that the value received + * in a body for the given path is as presented in the contract body + */ + EQUALITY, + /** + * Verification by type - is the type received in a body + * for the given path is of the same type. If it's a collection + * you can verify number of occurrences + */ + TYPE, + /** + * Special version of regex for date check + */ + DATE, + /** + * Special version of regex for time check + */ + TIME, + /** + * Special version of regex for timestamp check + */ + TIMESTAMP, + /** + * Verification if the value for the given path matches the + * provided regex + */ + REGEX } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java index d710e38536..eee93df601 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/camel/StubRunnerCamelPredicate.java @@ -77,7 +77,7 @@ class StubRunnerCamelPredicate implements Predicate { } if (matchers != null && matchers.hasMatchers()) { for (BodyMatcher matcher : matchers.jsonPathMatchers()) { - String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher); + String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher, dslBody); matches &= matchesJsonPath(parsedJson, jsonPath); } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java index f5103bba68..073cb2cfae 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/integration/StubRunnerIntegrationMessageSelector.java @@ -76,7 +76,7 @@ class StubRunnerIntegrationMessageSelector implements MessageSelector { } if (matchers != null && matchers.hasMatchers()) { for (BodyMatcher matcher : matchers.jsonPathMatchers()) { - String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher); + String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher, dslBody); matches &= matchesJsonPath(parsedJson, jsonPath); } } diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java index c6259190f2..d75a2c4ae3 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/messaging/stream/StubRunnerStreamMessageSelector.java @@ -76,7 +76,7 @@ class StubRunnerStreamMessageSelector implements MessageSelector { } if (matchers != null && matchers.hasMatchers()) { for (BodyMatcher matcher : matchers.jsonPathMatchers()) { - String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher); + String jsonPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher, dslBody); matches &= matchesJsonPath(parsedJson, jsonPath); } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy index 8b67006e3b..165f185d8e 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy @@ -371,9 +371,9 @@ class DslToWireMockClientConverterSpec extends Specification { ]) stubMatchers { jsonPath('$.duck', byRegex("[0-9]{3}")) - jsonPath('$.duck', byValue(123)) + jsonPath('$.duck', byEquality()) jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byValue("abc")) + jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) jsonPath('$.date', byDate()) @@ -410,10 +410,10 @@ class DslToWireMockClientConverterSpec extends Specification { testMatchers { // asserts the jsonpath value against manual regex jsonPath('$.duck', byRegex("[0-9]{3}")) - jsonPath('$.duck', byValue(123)) + jsonPath('$.duck', byEquality()) // asserts the jsonpath value against some default regex jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byValue("abc")) + jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) // asserts vs inbuilt time related regex diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy index e9cf97a10b..53cfbe93b2 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilder.groovy @@ -17,6 +17,7 @@ package org.springframework.cloud.contract.verifier.builder import com.jayway.jsonpath.JsonPath +import com.jayway.jsonpath.PathNotFoundException import groovy.json.JsonOutput import groovy.transform.PackageScope import groovy.transform.TypeChecked @@ -311,19 +312,17 @@ abstract class MethodBodyBuilder { bb.startBlock() // for the rest we'll do JsonPath matching in brute force bodyMatchers.jsonPathMatchers().each { - if (it.value()) { + if (it.value() || it.matchingType() == MatchingType.EQUALITY) { String comparisonMethod = it.matchingType() == MatchingType.EQUALITY ? "isEqualTo" : "matches" - String valueAsParam = it.value() instanceof String ? quotedAndEscaped(it.value().toString()) : it.value().toString() - String classToCastTo = "${it.value().class.simpleName}.class" + Object retrievedValue = value(copiedBody, it) + String valueAsParam = retrievedValue instanceof String ? quotedAndEscaped(retrievedValue.toString()) : retrievedValue.toString() + String classToCastTo = "${retrievedValue.class.simpleName}.class" String path = quotedAndEscaped(it.path()) String method = "assertThat(parsedJson.read(${path}, ${classToCastTo})).${comparisonMethod}(${valueAsParam})" bb.addLine(postProcessJsonPathCall(method)) addColonIfRequired(bb) } else { - Object elementFromBody = JsonPath.parse(copiedBody).read(it.path()) - if (!elementFromBody) { - throw new IllegalStateException("Entry for the provided JSON path [${it.path()}] doesn't exist in the body [${JsonOutput.toJson(copiedBody)}]") - } + Object elementFromBody = value(copiedBody, it) if (it.minTypeOccurrence() || it.maxTypeOccurrence()) { checkType(bb, it, elementFromBody) String method = "assertThat(parsedJson.read(${quotedAndEscaped(it.path())}, java.util.Collection.class).size()).${sizeCheckMethod(it)}" @@ -338,6 +337,21 @@ abstract class MethodBodyBuilder { processBodyElement(bb, "", convertedResponseBody) } + protected Object value(def body, BodyMatcher bodyMatcher) { + if (bodyMatcher.matchingType() == MatchingType.EQUALITY || !bodyMatcher.value()) { + return retrieveObjectByPath(body, bodyMatcher.path()) + } + return bodyMatcher.value() + } + + protected Object retrieveObjectByPath(def body, String path) { + try { + return JsonPath.parse(body).read(path) + } catch (PathNotFoundException e) { + throw new IllegalStateException("Entry for the provided JSON path [${path}] doesn't exist in the body [${JsonOutput.toJson(body)}]", e) + } + } + protected void checkType(BlockBuilder bb, BodyMatcher it, Object elementFromBody) { String method = "assertThat((Object) parsedJson.read(${quotedAndEscaped(it.path())})).isInstanceOf(${classToCheck(elementFromBody).name}.class)" bb.addLine(postProcessJsonPathCall(method)) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy index debe1fc4f5..a2f7e051ad 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockRequestStubStrategy.groovy @@ -75,8 +75,8 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { } ContentType contentType = tryToGetContentType(request.body.clientValue, request.headers) if (contentType == ContentType.JSON) { - def body = getMatchingStrategyFromBody(request.body)?.clientValue - body = JsonToJsonPathsConverter.removeMatchingJsonPaths(body, request.matchers) + def originalBody = getMatchingStrategyFromBody(request.body)?.clientValue + def body = JsonToJsonPathsConverter.removeMatchingJsonPaths(originalBody, request.matchers) JsonPaths values = JsonToJsonPathsConverter.transformToJsonPathWithStubsSideValuesAndNoArraySizeCheck(body) if (values.empty && !request.matchers?.hasMatchers()) { requestPattern.withRequestBody(WireMock.equalToJson(JsonOutput.toJson(getMatchingStrategy(request.body.clientValue).clientValue), false, false)) @@ -87,7 +87,7 @@ class WireMockRequestStubStrategy extends BaseWireMockStubStrategy { } if (request.matchers?.hasMatchers()) { request.matchers.jsonPathMatchers().each { - String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it) + String newPath = JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(it, originalBody) requestPattern.withRequestBody(WireMock.matchingJsonPath(newPath.replace("\\\\", "\\"))) } } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy index 962a9f62b7..3e5fc06487 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverter.groovy @@ -16,19 +16,18 @@ package org.springframework.cloud.contract.verifier.util +import com.jayway.jsonpath.DocumentContext import com.jayway.jsonpath.JsonPath +import com.jayway.jsonpath.PathNotFoundException import com.toomuchcoding.jsonassert.JsonAssertion import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.util.logging.Slf4j -import org.springframework.cloud.contract.spec.internal.BodyMatcher -import org.springframework.cloud.contract.spec.internal.BodyMatchers -import org.springframework.cloud.contract.spec.internal.ExecutionProperty -import org.springframework.cloud.contract.spec.internal.MatchingType -import org.springframework.cloud.contract.spec.internal.OptionalProperty +import org.springframework.cloud.contract.spec.internal.* import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties 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 @@ -71,12 +70,14 @@ class JsonToJsonPathsConverter { * @return json with removed entries */ static def removeMatchingJsonPaths(def json, BodyMatchers bodyMatchers) { + def jsonCopy = json.clone() + DocumentContext context = JsonPath.parse(jsonCopy) if (bodyMatchers?.hasMatchers()) { bodyMatchers.jsonPathMatchers().each { BodyMatcher matcher -> - JsonPath.parse(json).delete(matcher.path()) + context.delete(matcher.path()) } } - return json + return jsonCopy } /** @@ -86,23 +87,31 @@ class JsonToJsonPathsConverter { * @param bodyMatcher * @return JSON path that checks the regex for its last element */ - static String convertJsonPathAndRegexToAJsonPath(BodyMatcher bodyMatcher) { + static String convertJsonPathAndRegexToAJsonPath(BodyMatcher bodyMatcher, def body = null) { String path = bodyMatcher.path() Object value = bodyMatcher.value() - if (!value) { + if (value == null && bodyMatcher.matchingType() != MatchingType.EQUALITY) { return path } int lastIndexOfDot = path.lastIndexOf(".") String toLastDot = path.substring(0, lastIndexOfDot) String fromLastDot = path.substring(lastIndexOfDot + 1) - String comparison = createComparison(bodyMatcher, value) + String comparison = createComparison(bodyMatcher, value, body) return "${toLastDot}[?(@.${fromLastDot} ${comparison})]" } - private static String createComparison(BodyMatcher bodyMatcher, Object value) { + private static String createComparison(BodyMatcher bodyMatcher, Object value, def body) { if (bodyMatcher.matchingType() == MatchingType.EQUALITY) { - String wrappedValue = value instanceof Number ? value : "'${value.toString()}'" - return "== ${wrappedValue}" + if (!body) { + throw new IllegalStateException("Body hasn't been passed") + } + try { + Object retrievedValue = JsonPath.parse(body).read(bodyMatcher.path()) + String wrappedValue = retrievedValue instanceof Number ? retrievedValue : "'${retrievedValue.toString()}'" + return "== ${wrappedValue}" + } catch (PathNotFoundException e) { + throw new IllegalStateException("Value [${bodyMatcher.path()}] not found in JSON [${JsonOutput.toJson(body)}]", e) + } } else { return "=~ /(${value})/" } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy index 762788be9a..4a74f86880 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy @@ -51,9 +51,9 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements ]) stubMatchers { jsonPath('$.duck', byRegex("[0-9]{3}")) - jsonPath('$.duck', byValue(123)) + jsonPath('$.duck', byEquality()) jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byValue("abc")) + jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) jsonPath('$.date', byDate()) @@ -90,10 +90,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements // asserts the jsonpath value against manual regex jsonPath('$.duck', byRegex("[0-9]{3}")) // asserts the jsonpath value against the provided value - jsonPath('$.duck', byValue(123)) + jsonPath('$.duck', byEquality()) // asserts the jsonpath value against some default regex jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byValue("abc")) + jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number())) jsonPath('$.aBoolean', byRegex(anyBoolean())) // asserts vs inbuilt time related regex diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverterSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverterSpec.groovy index e2f74b3bb5..89118cc002 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverterSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/JsonToJsonPathsConverterSpec.groovy @@ -766,17 +766,19 @@ class JsonToJsonPathsConverterSpec extends Specification { def "should convert a json path with value to a equality checking json path without quotes for numbers"() { given: String jsonPath = '$.a.b.c.d' - Integer value = 1234 + and: + def body = [ a: [ b: [ c: [ d: 1234 ] ] ] ] expect: - '$.a.b.c[?(@.d == 1234)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value)) + '$.a.b.c[?(@.d == 1234)]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body) } def "should convert a json path with value to a equality checking json path with quotes for strings"() { given: String jsonPath = '$.a.b.c.d' - String value = "foo" + and: + def body = [ a: [ b: [ c: [ d: "foo" ] ] ] ] expect: - '$.a.b.c[?(@.d == \'foo\')]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, value)) + '$.a.b.c[?(@.d == \'foo\')]' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body) } def "should return the path if no value is provided"() { @@ -786,6 +788,30 @@ class JsonToJsonPathsConverterSpec extends Specification { '$.a.b.c.d' == JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.REGEX, jsonPath, null)) } + def "should throw an exception when null body is passed to check for equality"() { + given: + String jsonPath = '$.a.b.c.d' + and: + def body = null + when: + JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body) + then: + IllegalStateException e = thrown(IllegalStateException) + e.message.contains("Body") + } + + def "should throw an exception when nonexisting jsonpath is passed to check for equality"() { + given: + String jsonPath = '$.a.b.c.d' + and: + def body = [ foo: "bar" ] + when: + JsonToJsonPathsConverter.convertJsonPathAndRegexToAJsonPath(matcher(MatchingType.EQUALITY, jsonPath, null), body) + then: + IllegalStateException e = thrown(IllegalStateException) + e.message.contains("not found") + } + private BodyMatcher matcher(final MatchingType matchingType, final String jsonPath, final Object value) { return new BodyMatcher() { @Override