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 2d58571b5e..2e79766ac3 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 @@ -29,7 +29,7 @@ import groovy.transform.ToString @CompileStatic @EqualsAndHashCode @ToString(includePackage = false, includeNames = true) -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 87da59c000..d9a524eccd 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 @@ -26,7 +26,7 @@ import groovy.transform.ToString */ @CompileStatic @ToString(includePackage = false, includeNames = true) -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 0fb64a36c2..6b896bfb0d 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 @@ -28,6 +28,7 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig 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.util.SerializationUtils import java.util.regex.Pattern @@ -369,7 +370,12 @@ abstract class MethodBodyBuilder { bb.addLine(postProcessJsonPathCall(method)) } + // 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) { @@ -388,7 +394,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 915e404201..a61ce93f31 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 @@ -29,6 +29,7 @@ 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.verifier.config.ContractVerifierConfigProperties +import org.springframework.util.SerializationUtils import java.util.regex.Pattern @@ -90,7 +91,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 82dadcea0e..ba8eb2053b 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 @@ -366,7 +366,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) } | '\\$'