From 99ad52c2ddde248a8a433b7a7843f9ca6968a2c5 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Sat, 9 Dec 2017 06:59:57 -0800 Subject: [PATCH 1/2] Fixed stub runner cloud registration --- .../cloud/eureka/EurekaStubsRegistrar.java | 15 ++++- ...nerSpringCloudEurekaAutoConfiguration.java | 55 ++++++++++++++++--- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java index e12bc4c3af..5210c97056 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/EurekaStubsRegistrar.java @@ -48,9 +48,10 @@ public class EurekaStubsRegistrar implements StubsRegistrar { .validNamesAndPorts(); for (Map.Entry entry : activeStubs.entrySet()) { Application application = new Application(name(entry.getKey()), entry.getKey().getArtifactId(), - StringUtils.hasText(this.eurekaInstanceConfigBean.getHostname()) ? - this.eurekaInstanceConfigBean.getHostname() : - this.inetUtils.findFirstNonLoopbackAddress().getHostName(), entry.getValue()); + StringUtils.hasText(hostName(entry)) ? + hostName(entry) : + this.inetUtils.findFirstNonLoopbackAddress().getHostName(), + port(entry)); try { Registration register = this.eurekaClient.register(application); this.discoveryList.add(new Renewer( @@ -67,6 +68,14 @@ public class EurekaStubsRegistrar implements StubsRegistrar { } } + protected String hostName(Map.Entry entry) { + return this.eurekaInstanceConfigBean.getHostname(); + } + + protected int port(Map.Entry entry) { + return entry.getValue(); + } + private String name(StubConfiguration stubConfiguration) { String resolvedName = this.stubMapperProperties.fromIvyNotationToId( stubConfiguration.toColonSeparatedDependencyNotation()); diff --git a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java index 4bb6b448bf..b900c5aadd 100644 --- a/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java +++ b/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/spring/cloud/eureka/StubRunnerSpringCloudEurekaAutoConfiguration.java @@ -16,10 +16,19 @@ package org.springframework.cloud.contract.stubrunner.spring.cloud.eureka; +import java.util.Map; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.discovery.EurekaClientConfig; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.cloud.contract.stubrunner.StubConfiguration; import org.springframework.cloud.contract.stubrunner.StubRunning; import org.springframework.cloud.contract.stubrunner.spring.StubRunnerConfiguration; import org.springframework.cloud.contract.stubrunner.spring.cloud.ConditionalOnStubbedDiscoveryDisabled; @@ -32,9 +41,8 @@ import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - -import com.netflix.appinfo.ApplicationInfoManager; -import com.netflix.discovery.EurekaClientConfig; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; /** * Autoconfiguration for registering stubs in a Eureka Service discovery @@ -51,10 +59,43 @@ import com.netflix.discovery.EurekaClientConfig; @ConditionalOnProperty(value = "stubrunner.cloud.eureka.enabled", matchIfMissing = true) public class StubRunnerSpringCloudEurekaAutoConfiguration { - @Bean(initMethod = "registerStubs") - public StubsRegistrar stubsRegistrar(StubRunning stubRunning, Eureka eureka, - StubMapperProperties stubMapperProperties, InetUtils inetUtils, EurekaInstanceConfigBean eurekaInstanceConfigBean) { - return new EurekaStubsRegistrar(stubRunning, eureka, stubMapperProperties, inetUtils, eurekaInstanceConfigBean); + @Profile("!cloud") + @Configuration + protected static class NonCloudConfig { + @Bean(initMethod = "registerStubs") + public StubsRegistrar stubsRegistrar(StubRunning stubRunning, Eureka eureka, + StubMapperProperties stubMapperProperties, InetUtils inetUtils, EurekaInstanceConfigBean eurekaInstanceConfigBean) { + return new EurekaStubsRegistrar(stubRunning, eureka, stubMapperProperties, inetUtils, eurekaInstanceConfigBean); + } + } + + @Profile("cloud") + @Configuration + protected static class CloudConfig { + private static final int DEFAULT_PORT = 80; + private static final Log log = LogFactory.getLog(CloudConfig.class); + + @Autowired Environment environment; + + @Bean(initMethod = "registerStubs") + public StubsRegistrar cloudStubsRegistrar(StubRunning stubRunning, Eureka eureka, + StubMapperProperties stubMapperProperties, InetUtils inetUtils, EurekaInstanceConfigBean eurekaInstanceConfigBean) { + final RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + CloudConfig.this.environment); + return new EurekaStubsRegistrar(stubRunning, eureka, stubMapperProperties, inetUtils, eurekaInstanceConfigBean) { + @Override protected String hostName(Map.Entry entry) { + String hostname = + resolver.getProperty("application.hostname") + + "-" + entry.getValue() + "." + resolver.getProperty("application.domain"); + log.info("Registering stub [" + entry.getKey().getArtifactId() + "] with hostname [" + hostname + "]"); + return hostname; + } + + @Override protected int port(Map.Entry entry) { + return DEFAULT_PORT; + } + }; + } } @Bean(name = "eurekaRegistrar") From b3a8124168456307781f38139b96cb4182fbcc3b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 20 Dec 2017 15:22:42 +0100 Subject: [PATCH 2/2] Fixed the way form URL encoded request body is treated fixes #493 --- .../verifier/builder/MethodBodyBuilder.groovy | 11 ++--- .../contract/verifier/util/ContentType.groovy | 17 ++++++- .../builder/MethodBodyBuilderSpec.groovy | 48 ++++++++++++++++--- 3 files changed, 63 insertions(+), 13 deletions(-) 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 af7fbd0279..745920bef0 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 @@ -23,8 +23,6 @@ import groovy.json.JsonOutput import groovy.transform.PackageScope import groovy.transform.TypeChecked import org.apache.commons.lang3.StringEscapeUtils -import org.apache.commons.logging.Log -import org.apache.commons.logging.LogFactory import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.spec.ContractTemplate import org.springframework.cloud.contract.spec.internal.BodyMatcher @@ -47,7 +45,6 @@ import org.springframework.cloud.contract.verifier.util.MapConverter import org.springframework.util.SerializationUtils import org.springframework.util.StringUtils -import java.lang.invoke.MethodHandles import java.util.regex.Pattern import static org.springframework.cloud.contract.verifier.util.ContentUtils.extractValue @@ -64,7 +61,7 @@ import static org.springframework.cloud.contract.verifier.util.ContentUtils.extr @PackageScope abstract class MethodBodyBuilder { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()) + private static final Closure GET_SERVER_VALUE = { it instanceof DslProperty ? it.serverValue : it } protected final ContractVerifierConfigProperties configProperties protected final TemplateProcessor templateProcessor @@ -602,9 +599,11 @@ abstract class MethodBodyBuilder { */ protected Object extractServerValueFromBody(bodyValue) { if (bodyValue instanceof GString) { - bodyValue = extractValue(bodyValue, { DslProperty dslProperty -> dslProperty.serverValue }) + bodyValue = extractValue(bodyValue, ContentType.from(MapConverter.getTestSideValues(this.contract.request.headers?.entries?.find { + it.name.toLowerCase() == "Content-Type".toLowerCase() + }).toString()), GET_SERVER_VALUE) } else { - bodyValue = MapConverter.transformValues(bodyValue, { it instanceof DslProperty ? it.serverValue : it }) + bodyValue = MapConverter.transformValues(bodyValue, GET_SERVER_VALUE) } return bodyValue } diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentType.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentType.groovy index af2212fe59..13ec29fe00 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentType.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/ContentType.groovy @@ -17,7 +17,7 @@ package org.springframework.cloud.contract.verifier.util /** - * Represents content type + * Represents content type. Used to pick the way bodies are parsed. * * @since 1.0.0 */ @@ -34,4 +34,19 @@ enum ContentType { this.mimeType = mimeType } + static ContentType from(String header) { + try { + if (header.contains("json")) { + return JSON + } else if (header.contains("xml")) { + return XML + } else if (header.contains("text") || + header.contains("application/x-www-form-urlencoded")) { + // we want both to be treated as text + return TEXT + } + } catch(e) {} + return UNKNOWN + } + } \ No newline at end of file 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 fe4941a834..84f9434d63 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 @@ -349,7 +349,7 @@ DocumentContext parsedJson = JsonPath.parse(json); } @Issue("#458") - def "should reference request from body whtn body is a string [#methodBuilderName]"() { + def "should reference request from body when body is a string [#methodBuilderName]"() { given: Contract contractDsl = Contract.make { request { @@ -367,15 +367,17 @@ DocumentContext parsedJson = JsonPath.parse(json); when: builder.appendTo(blockBuilder) then: - SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, blockBuilder.toString()) + String test = blockBuilder.toString() + SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) + responseAsserter(test) and: stubMappingIsValidWireMockStub(contractDsl) where: methodBuilderName | methodBuilder | responseAsserter - "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | { String string -> assert string.contains('responseBody == "My name"') } - "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | { String string -> assert string.contains('assertThat(responseBody).isEqualTo("My name");') } - "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | { String string -> assert string.contains('responseBody == "My name"') } - "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | { String string -> assert string.contains('assertThat(responseBody).isEqualTo("My name");') } + "MockMvcSpockMethodBuilder" | { Contract dsl -> new MockMvcSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | { String string -> string.contains('responseBody == "My name"') } + "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } | { String string -> string.contains('assertThat(responseBody).isEqualTo("My name");') } + "JaxRsClientSpockMethodRequestProcessingBodyBuilder" | { Contract dsl -> new JaxRsClientSpockMethodRequestProcessingBodyBuilder(dsl, properties) } | { String string -> string.contains('responseBody == "My name"') } + "JaxRsClientJUnitMethodBodyBuilder" | { Contract dsl -> new JaxRsClientJUnitMethodBodyBuilder(dsl, properties) } | { String string -> string.contains('assertThat(responseBody).isEqualTo("My name");') } } def "should use fixed delay milliseconds in the generated test [#methodBuilderName]"() { @@ -408,4 +410,38 @@ DocumentContext parsedJson = JsonPath.parse(json); "MockMvcJUnitMethodBuilder" | { Contract dsl -> new MockMvcJUnitMethodBodyBuilder(dsl, properties) } } + @Issue("#493") + def "should not escape a form URL encoded request body [#methodBuilderName]"() { + given: + Contract contractDsl = Contract.make { + request { + method 'POST' + url '/api/form-endpoint' + headers { + header("Content-Type": 'application/x-www-form-urlencoded') + } + body('a=abc&b=123') + } + response { + status 200 + } + } + MethodBodyBuilder builder = methodBuilder(contractDsl) + BlockBuilder blockBuilder = new BlockBuilder(" ") + when: + builder.appendTo(blockBuilder) + then: + String test = blockBuilder.toString() + SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test) + !test.contains("a=abc&b=123") + 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) } + } + }