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") 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 feda50f68d..a3905a2aba 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 @@ -47,7 +47,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 +63,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 @@ -592,9 +591,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 d705d285aa..7c48bfb88e 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");') } } @Issue("#465") @@ -451,4 +453,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) } + } + }