From 85406875e1358256931caaf0790e9cf9827bbd60 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 1 Aug 2018 11:43:46 +0200 Subject: [PATCH 1/3] When referencing from request a number adds "L" for longs fixes gh-702 --- .../HandlebarsJsonPathHelper.groovy | 6 ++- .../builder/MethodBodyBuilderSpec.groovy | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/handlebars/HandlebarsJsonPathHelper.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/handlebars/HandlebarsJsonPathHelper.groovy index ff29a72e22..35ac89d92e 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/handlebars/HandlebarsJsonPathHelper.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/handlebars/HandlebarsJsonPathHelper.groovy @@ -40,7 +40,11 @@ class HandlebarsJsonPathHelper implements Helper> { private Object returnObjectForTest(Object model, String jsonPath) { String body = removeSurroundingQuotes(((TestSideRequestTemplateModel) model).rawBody).replace('\\"', '"') DocumentContext documentContext = JsonPath.parse(body) - return documentContext.read(jsonPath) + Object value = documentContext.read(jsonPath) + if (value instanceof Long) { + return String.valueOf(value) + "L" + } + return value } private String removeSurroundingQuotes(String body) { diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy index 154f7c33de..db7561fbd8 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy @@ -491,6 +491,58 @@ DocumentContext parsedJson = JsonPath.parse(json); "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } } + @Issue("#702") + def "should generate proper type for large numbers [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'PUT' + urlPath '/example/create' + headers { + contentType applicationJson() + } + body( + [ + "name" : $(consumer(~/.+/), producer("string-1")), + "updatedTs" : $(consumer(~/\d{13}/), producer(1531916906000L)), + "isDisabled": $(consumer(regex(anyBoolean())), producer(true)) + ] + ) + } + + response { + status 200 + headers { + contentType applicationJsonUtf8() + } + body( + [ + "id" : $(consumer(2222L), producer(~/\d+/)), + "name" : fromRequest().body("name"), + "updatedTs" : fromRequest().body("updatedTs"), + "isDisabled": fromRequest().body("isDisabled") + ] + ) + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + then: + String test = blockBuilder.toString() + SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) + test.contains('''assertThatJson(parsedJson).field("['updatedTs']").isEqualTo(1531916906000L)''') + and: + stubMappingIsValidWireMockStub(contractDsl) + where: + methodBuilderName | methodBuilder + "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("#465") def "should work for '/' url for [#methodBuilderName]"() { given: From 46963e3da58996355d607f92ab2729e5838adc42 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 1 Aug 2018 12:12:37 +0200 Subject: [PATCH 2/3] Fixed invalid contract package string without this change we were matching the package for contracts with _REPLACEME_ text in it with this change we're checking it against the package for contracts in a proper way fixes gh-701 --- .../cloud/contract/verifier/TestGenerator.groovy | 13 +++++++++++-- .../contract/verifier/builder/ClassBuilder.groovy | 11 ++++++++++- .../verifier/builder/ClassBuilderSpec.groovy | 12 ++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy index 1ddc638d68..62424fae42 100755 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/TestGenerator.groovy @@ -20,7 +20,10 @@ import java.nio.charset.StandardCharsets import java.nio.file.Path import java.util.concurrent.atomic.AtomicInteger +import groovy.transform.CompileStatic import groovy.transform.PackageScope +import org.apache.commons.logging.Log +import org.apache.commons.logging.LogFactory import wiremock.com.google.common.collect.ListMultimap import org.springframework.cloud.contract.spec.ContractVerifierException @@ -37,13 +40,16 @@ import static org.springframework.cloud.contract.verifier.util.NamesUtil.beforeL import static org.springframework.cloud.contract.verifier.util.NamesUtil.convertIllegalPackageChars import static org.springframework.cloud.contract.verifier.util.NamesUtil.directoryToPackage import static org.springframework.cloud.contract.verifier.util.NamesUtil.toLastDot + /** * @author Jakub Kubrynski, codearte.io */ +@CompileStatic class TestGenerator { private static final String DEFAULT_CLASS_PREFIX = "ContractVerifier" private static final String DEFAULT_TEST_PACKAGE = "org.springframework.cloud.contract.verifier.tests" + private static final Log log = LogFactory.getLog(TestGenerator) private final ContractVerifierConfigProperties configProperties private AtomicInteger counter = new AtomicInteger() @@ -101,7 +107,7 @@ class TestGenerator { } } - private String relativizeContractPath(Map.Entry> entry) { + private String relativizeContractPath(Map.Entry> entry) { Path relativePath = configProperties.contractsDslDir.toPath().relativize(entry.getKey()) if (StringUtils.isEmpty(relativePath.toString())) { return DEFAULT_CLASS_PREFIX @@ -111,8 +117,11 @@ class TestGenerator { private void processIncludedDirectory( final String includedDirectoryRelativePath, Collection contracts, final String basePackageNameForClass) { + if (log.isDebugEnabled()) { + log.debug("Included directory relative path is [${includedDirectoryRelativePath}]") + } if (contracts.size()) { - def className = afterLast(includedDirectoryRelativePath.toString(), File.separator) + resolveNameSuffix() + def className = afterLast(includedDirectoryRelativePath, File.separator) + resolveNameSuffix() def convertedClassName = convertIllegalPackageChars(className) def packageName = buildPackage(basePackageNameForClass, includedDirectoryRelativePath) def classBytes = generator.buildClass(configProperties, contracts, convertedClassName, packageName, includedDirectoryRelativePath).getBytes(StandardCharsets.UTF_8) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilder.groovy index 12a6f66799..5e25c6bab4 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilder.groovy @@ -18,6 +18,9 @@ package org.springframework.cloud.contract.verifier.builder import groovy.transform.CompileStatic import groovy.transform.PackageScope +import org.apache.commons.logging.Log +import org.apache.commons.logging.LogFactory + import org.springframework.cloud.contract.verifier.config.TestFramework import org.springframework.cloud.contract.verifier.util.NamesUtil import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties @@ -33,6 +36,8 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig @PackageScope class ClassBuilder { + private static final Log log = LogFactory.getLog(ClassBuilder) + private static final String SEPARATOR = "_REPLACEME_" private final String className @@ -72,11 +77,15 @@ class ClassBuilder { } protected static String retrieveBaseClass(ContractVerifierConfigProperties properties, String includedDirectoryRelativePath) { + String contractPathAsPackage = includedDirectoryRelativePath.replace(File.separator, ".") String contractPackage = includedDirectoryRelativePath.replace(File.separator, SEPARATOR) // package mapping takes super precedence if (properties.baseClassMappings) { Map.Entry mapping = properties.baseClassMappings.find { String pattern, String fqn -> - return contractPackage.matches(pattern) + return contractPathAsPackage.matches(pattern) + } + if (log.isDebugEnabled()) { + log.debug("Matching pattern for contract package [${contractPathAsPackage}] with setup ${properties.baseClassMappings} is [${mapping}]") } if (mapping) { return mapping.value diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy index 5c7020876e..7181fe45c3 100644 --- a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ClassBuilderSpec.groovy @@ -1,5 +1,7 @@ package org.springframework.cloud.contract.verifier.builder +import spock.lang.Issue + import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import spock.lang.Specification @@ -49,6 +51,16 @@ class ClassBuilderSpec extends Specification { ClassBuilder.retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperClass' } + @Issue("701") + def "should match base class when mapping regex has multiple folders"() { + given: + ContractVerifierConfigProperties props = new ContractVerifierConfigProperties( + baseClassMappings: ['.*bar.baz.some.*' : 'com.example.base.SuperClass']) + String contractRelativeFolder = 'foo/bar/baz/some/package'.split("/").join(File.separator) + expect: + ClassBuilder.retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperClass' + } + def "should return the first matching base class when provided mapping doesn't match"() { given: ContractVerifierConfigProperties props = new ContractVerifierConfigProperties( From e65aa5d6d5db70fc56f5f38f4c192ca6cf802084 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 1 Aug 2018 12:54:23 +0200 Subject: [PATCH 3/3] Ensures that the proper channel will be picked for the same destination fixes gh-694 --- .../messaging/stream/StreamStubMessages.java | 31 ++++- .../stream/StreamStubMessagesSpec.groovy | 109 ++++++++++++++++++ .../StreamMessagingApplicationSpec.groovy | 2 +- 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessagesSpec.groovy diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessages.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessages.java index 00cebd3016..4122cac3f9 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessages.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessages.java @@ -16,6 +16,7 @@ package org.springframework.cloud.contract.verifier.messaging.stream; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -29,6 +30,7 @@ import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.context.ApplicationContext; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; +import org.springframework.util.StringUtils; /** * @author Marcin Grzejszczak @@ -56,7 +58,7 @@ public class StreamStubMessages implements MessageVerifier> { public void send(Message message, String destination) { try { MessageChannel messageChannel = this.context - .getBean(resolvedDestination(destination), MessageChannel.class); + .getBean(resolvedDestination(destination, DefaultChannels.OUTPUT), MessageChannel.class); messageChannel.send(message); } catch (Exception e) { @@ -70,7 +72,7 @@ public class StreamStubMessages implements MessageVerifier> { public Message receive(String destination, long timeout, TimeUnit timeUnit) { try { MessageChannel messageChannel = this.context - .getBean(resolvedDestination(destination), MessageChannel.class); + .getBean(resolvedDestination(destination, DefaultChannels.INPUT), MessageChannel.class); return this.messageCollector.forChannel(messageChannel).poll(timeout, timeUnit); } catch (Exception e) { @@ -80,10 +82,11 @@ public class StreamStubMessages implements MessageVerifier> { } } - private String resolvedDestination(String destination) { + private String resolvedDestination(String destination, DefaultChannels defaultChannel) { try { BindingServiceProperties channelBindingServiceProperties = this.context .getBean(BindingServiceProperties.class); + Map channels = new HashMap<>(); for (Map.Entry entry : channelBindingServiceProperties .getBindings().entrySet()) { if (destination.equals(entry.getValue().getDestination())) { @@ -91,9 +94,24 @@ public class StreamStubMessages implements MessageVerifier> { log.debug("Found a channel named [{}] with destination [{}]", entry.getKey(), destination); } - return entry.getKey(); + channels.put(entry.getKey().toLowerCase(), destination); } } + if (channels.size() == 1) { + return channels.keySet().iterator().next(); + } else if (channels.size() > 0) { + if (log.isDebugEnabled()) { + log.debug("Found following channels [{}] for destination [{}]. " + + "Will pick the one that matches the default channel name or the first one if none is matching", + channels, destination); + } + String defaultChannelName = channels.get(defaultChannel.name().toLowerCase()); + String matchingChannelName = StringUtils.hasText(defaultChannelName) ? defaultChannel.name().toLowerCase() : channels.keySet().iterator().next(); + if (log.isDebugEnabled()) { + log.debug("Picked channel name is [{}]", matchingChannelName); + } + return matchingChannelName; + } } catch (Exception e) { log.error("Exception took place while trying to resolve the destination. Will assume the name [" + destination + "]", e); } @@ -111,3 +129,8 @@ public class StreamStubMessages implements MessageVerifier> { } } + + +enum DefaultChannels { + INPUT, OUTPUT +} \ No newline at end of file diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessagesSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessagesSpec.groovy new file mode 100644 index 0000000000..8cfcd09210 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/messaging/stream/StreamStubMessagesSpec.groovy @@ -0,0 +1,109 @@ +package org.springframework.cloud.contract.verifier.messaging.stream + +import spock.lang.Issue +import spock.lang.Specification + +import org.springframework.cloud.stream.config.BindingProperties +import org.springframework.cloud.stream.config.BindingServiceProperties +import org.springframework.cloud.stream.test.binder.MessageCollector +import org.springframework.context.ApplicationContext +import org.springframework.messaging.MessageChannel + +/** + * @author Marcin Grzejszczak + */ +class StreamStubMessagesSpec extends Specification { + + @Issue("694") + def "should resolve input channel if input and output have same destination and receive is called"() { + given: + ApplicationContext applicationContext = Mock(ApplicationContext) + BindingServiceProperties properties = new BindingServiceProperties( + bindings: [ + input: new BindingProperties(destination: "verifications"), + output: new BindingProperties(destination: "verifications"), + ] + ) + MessageCollector collector = Stub(MessageCollector) + and: + applicationContext.getBean(BindingServiceProperties) >> properties + applicationContext.getBean(MessageCollector) >> collector + and: + StreamStubMessages messages = new StreamStubMessages(applicationContext) + when: + messages.receive("verifications") + then: + 1 * applicationContext.getBean("input", MessageChannel) >> null + } + + @Issue("694") + def "should resolve output channel if input and output have same destination and send is called"() { + given: + ApplicationContext applicationContext = Mock(ApplicationContext) + BindingServiceProperties properties = new BindingServiceProperties( + bindings: [ + input: new BindingProperties(destination: "verifications"), + output: new BindingProperties(destination: "verifications"), + ] + ) + MessageCollector collector = Stub(MessageCollector) + MessageChannel channel = Stub(MessageChannel) + and: + applicationContext.getBean(BindingServiceProperties) >> properties + applicationContext.getBean(MessageCollector) >> collector + and: + StreamStubMessages messages = new StreamStubMessages(applicationContext) + when: + messages.send("foo", [:], "verifications") + then: + 1 * applicationContext.getBean("output", MessageChannel) >> channel + } + + def "should resolve channel via destination for send and receive"() { + given: + ApplicationContext applicationContext = Mock(ApplicationContext) + BindingServiceProperties properties = new BindingServiceProperties( + bindings: [ + foo: new BindingProperties(destination: "verifications") + ] + ) + MessageCollector collector = Stub(MessageCollector) + MessageChannel channel = Stub(MessageChannel) + and: + applicationContext.getBean(BindingServiceProperties) >> properties + applicationContext.getBean(MessageCollector) >> collector + and: + StreamStubMessages messages = new StreamStubMessages(applicationContext) + when: + messageInteraction(messages) + then: + 1 * applicationContext.getBean("foo", MessageChannel) >> channel + where: + messageInteraction << [ { StreamStubMessages stream -> stream.send("foo", [:], "verifications")}, + { StreamStubMessages stream -> stream.receive("verifications")}] + } + + def "should resolve channel via channel name for send and receive"() { + given: + ApplicationContext applicationContext = Mock(ApplicationContext) + BindingServiceProperties properties = new BindingServiceProperties( + bindings: [ + verifications: new BindingProperties(destination: "bar") + ] + ) + MessageCollector collector = Stub(MessageCollector) + MessageChannel channel = Stub(MessageChannel) + and: + applicationContext.getBean(BindingServiceProperties) >> properties + applicationContext.getBean(MessageCollector) >> collector + and: + StreamStubMessages messages = new StreamStubMessages(applicationContext) + when: + messageInteraction(messages) + then: + 1 * applicationContext.getBean("verifications", MessageChannel) >> channel + where: + messageInteraction << [ { StreamStubMessages stream -> stream.send("foo", [:], "verifications")}, + { StreamStubMessages stream -> stream.receive("verifications")}] + } +} diff --git a/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy b/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy index 71f804412d..abcf7f3a9b 100644 --- a/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy +++ b/tests/samples-messaging-stream/src/test/groovy/com/example/StreamMessagingApplicationSpec.groovy @@ -40,7 +40,7 @@ import javax.inject.Inject @DirtiesContext @SpringBootTest(properties = "debug=true") @AutoConfigureMessageVerifier -public class StreamMessagingApplicationSpec extends Specification { +class StreamMessagingApplicationSpec extends Specification { // ALL CASES @Inject MessageVerifier> contractVerifierMessaging