diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy index 2fc157ef98..d1bd27567a 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/ExecutionProperty.groovy @@ -24,7 +24,7 @@ import groovy.transform.CompileStatic * @since 1.0.0 */ @CompileStatic -class ExecutionProperty { +class ExecutionProperty implements Serializable { private static final String PLACEHOLDER_VALUE = '$it' diff --git a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy index ffa3de4e36..84fcd794b2 100644 --- a/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy +++ b/spring-cloud-contract-spec/src/main/groovy/org/springframework/cloud/contract/spec/internal/OptionalProperty.groovy @@ -24,7 +24,7 @@ import groovy.transform.CompileStatic * @since 1.0.0 */ @CompileStatic -class OptionalProperty { +class OptionalProperty implements Serializable { final Object value OptionalProperty(Object value) { 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 90d305084c..0f544d165b 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 @@ -22,12 +22,22 @@ import groovy.json.JsonOutput import groovy.transform.PackageScope import groovy.transform.TypeChecked import org.apache.commons.lang3.StringEscapeUtils -import org.springframework.cloud.contract.spec.internal.* +import org.springframework.cloud.contract.spec.internal.BodyMatcher +import org.springframework.cloud.contract.spec.internal.BodyMatchers +import org.springframework.cloud.contract.spec.internal.DslProperty +import org.springframework.cloud.contract.spec.internal.ExecutionProperty +import org.springframework.cloud.contract.spec.internal.Header +import org.springframework.cloud.contract.spec.internal.MatchingStrategy +import org.springframework.cloud.contract.spec.internal.MatchingType +import org.springframework.cloud.contract.spec.internal.NamedProperty +import org.springframework.cloud.contract.spec.internal.OptionalProperty +import org.springframework.cloud.contract.spec.internal.QueryParameter import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.util.ContentType import org.springframework.cloud.contract.verifier.util.JsonPaths import org.springframework.cloud.contract.verifier.util.JsonToJsonPathsConverter import org.springframework.cloud.contract.verifier.util.MapConverter +import org.springframework.util.SerializationUtils import java.util.regex.Pattern @@ -386,7 +396,12 @@ abstract class MethodBodyBuilder { bb.endBlock().endBlock() } + // Doing a clone doesn't work for nested lists... private Object cloneBody(Object object) { + if (object instanceof List) { + byte[] serializedObject = SerializationUtils.serialize(object) + return SerializationUtils.deserialize(serializedObject) + } try { return object.clone() } catch (CloneNotSupportedException e) { @@ -405,7 +420,7 @@ abstract class MethodBodyBuilder { 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) + throw new IllegalStateException("Entry for the provided JSON path <${path}> doesn't exist in the body <${JsonOutput.toJson(body)}>", e) } } 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 26cae3741f..5fd357735c 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 @@ -25,6 +25,7 @@ import groovy.json.JsonSlurper import groovy.util.logging.Slf4j import org.springframework.cloud.contract.spec.internal.* import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties +import org.springframework.util.SerializationUtils import java.util.regex.Pattern @@ -86,7 +87,12 @@ class JsonToJsonPathsConverter { return jsonCopy } + // Doing a clone doesn't work for nested lists... private static Object cloneBody(Object object) { + if (object instanceof List) { + byte[] serializedObject = SerializationUtils.serialize(object) + return SerializationUtils.deserialize(serializedObject) + } try { return object.clone() } catch (CloneNotSupportedException e) { 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 9ffbe40ed0..222e6e76b7 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 @@ -353,7 +353,46 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements builder.appendTo(blockBuilder) then: IllegalStateException e = thrown(IllegalStateException) - e.message.contains("Entry for the provided JSON path [\$.nonExistingPhoneNumbers[*].number] doesn't exist in the body") + e.message.contains("Entry for the provided JSON path <\$.nonExistingPhoneNumbers[*].number> doesn't exist in the body") + where: + methodBuilderName | methodBuilder | rootElement + "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | '$' + "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$' + "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | '$' + } + + @Issue("#229") + def "should work for matchers and body with json array[#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'GET' + url '/api/v1/xxxx' + body(12000) + } + response { + status 200 + body ([[ + [ access_token: '123'] + ]]) + headers { + contentType(applicationJson()) + } + testMatchers { + jsonPath('''$[0][0].access_token''', byEquality()) + } + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + and: + builder.appendTo(blockBuilder) + String test = blockBuilder.toString() + when: + SyntaxChecker.tryToCompile(methodBuilderName, test) + then: + test.contains('assertThat(parsedJson.read("' + rootElement + '[0][0].access_token", String.class)).isEqualTo("123")') where: methodBuilderName | methodBuilder | rootElement "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | '\\$'