diff --git a/README.adoc b/README.adoc index a036b7567e..c33facddaa 100644 --- a/README.adoc +++ b/README.adoc @@ -131,15 +131,6 @@ org.springframework.cloud.contract.spec.Contract.make { } /* -Since we don't want to force on the user to hardcode values of fields that are dynamic -(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's - value in a `$(...)` or `value(...)` and provide a dynamic value of a field then - the concrete value will be generated for you. If you want to be really explicit about - which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation. - That way what's present in the `consumer` section will end up in the produced stub. What's - there in the `producer` will end up in the autogenerated test. If you provide only the - regular expression side without the concrete value then Spring Cloud Contract will generate one for you. - From the Consumer perspective, when shooting a request in the integration test: (1) - If the consumer sends a request @@ -410,15 +401,6 @@ org.springframework.cloud.contract.spec.Contract.make { } /* -Since we don't want to force on the user to hardcode values of fields that are dynamic -(timestamps, database ids etc.), one can parametrize those entries. If you wrap your field's - value in a `$(...)` or `value(...)` and provide a dynamic value of a field then - the concrete value will be generated for you. If you want to be really explicit about - which side gets which value you can do that by using the `value(consumer(...), producer(...))` notation. - That way what's present in the `consumer` section will end up in the produced stub. What's - there in the `producer` will end up in the autogenerated test. If you provide only the - regular expression side without the concrete value then Spring Cloud Contract will generate one for you. - From the Consumer perspective, when shooting a request in the integration test: (1) - If the consumer sends a request @@ -814,6 +796,12 @@ For `stub-runner` use `spring-cloud-starter-stub-runner` and when you're using a Below you can find some resources related to Spring Cloud Contract Verifier and Stub Runner. Note that some can be outdated since the Spring Cloud Contract Verifier project is under constant development. +===== Spring Cloud Contract video + +You can check out the video from the Warsaw JUG about Spring Cloud Contract: + +video::sAAklvxmPmk[youtube,start=538,width=640,height=480] + ===== Readings - http://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest] @@ -1501,6 +1489,49 @@ public class WiremockForDocsClassRuleTests { The use `@ClassRule` means that the server will shut down after all the methods in this class. +== Relaxed SSL Validation for Rest Template + +WireMock allows you to stub a "secure" server with an "https" URL protocol. If your application wants to +contact that stub server in an integration test, then it will find that the SSL certificates are not +valid (it's the usual problem with self-installed certificates). The best option is often to just +re-configure the client to use "http", but if that's not open to you then you can ask Spring to configure +an HTTP client that ignores SSL validation errors (just for tests). + +To make this work with minimum fuss you need to be using the Spring Boot `RestTemplateBuilder` in your app, +e.g. + +[source,java,indent=0] +---- + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + return builder.build(); + } +---- + +This is because the builder is passed through callbacks to initalize it, so the SSL validation can be set up +in the client at that point. This will happen automatically in your test if you are using the +`@AutoConfigureWireMock` annotation (or the stub runner). If you are using the JUnit `@Rule` approach you need +to add the `@AutoConfigureHttpClient` annotation as well: + +[source,java,indent=0] +---- +@RunWith(SpringRunner.class) +@SpringBootTest("app.baseUrl=https://localhost:6443") +@AutoConfigureHttpClient +public class WiremockHttpsServerApplicationTests { + + @ClassRule + public static WireMockClassRule wiremock = new WireMockClassRule( + WireMockSpring.options().httpsPort(6443)); +... +} +---- + +If you are using `spring-boot-starter-test` then you will have the Apache HTTP client on the classpath and it will +be selected by the `RestTemplateBuilder` and configured to ignore SSL errors. If you are using the default `java.net` +client you don't need the annotation (but it won't do any harm). There is no support currently for other clients, but +it may be added in future releases. + == WireMock and Spring MVC Mocks Spring Cloud Contract provides a convenience class that can load JSON WireMock stubs into a 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 d6c25aeec5..e3a34c2bbc 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 @@ -73,9 +73,6 @@ class TestGenerator { } int generate() { - if (!configProperties.basePackageForTests) { - - } generateTestClasses(configProperties.basePackageForTests) return counter.get() } 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 5c90f71d7e..12a6f66799 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 @@ -33,6 +33,8 @@ import org.springframework.cloud.contract.verifier.config.ContractVerifierConfig @PackageScope class ClassBuilder { + private static final String SEPARATOR = "_REPLACEME_" + private final String className private final String packageName private final String baseClass @@ -70,7 +72,7 @@ class ClassBuilder { } protected static String retrieveBaseClass(ContractVerifierConfigProperties properties, String includedDirectoryRelativePath) { - String contractPackage = 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 -> @@ -88,7 +90,7 @@ class ClassBuilder { } private static String generateDefaultBaseClassName(String classPackage, ContractVerifierConfigProperties properties) { - String[] splitPackage = NamesUtil.convertIllegalPackageChars(classPackage).split("\\.") + String[] splitPackage = NamesUtil.convertIllegalPackageChars(classPackage).split(SEPARATOR) if (splitPackage.size() > 1) { String last = NamesUtil.capitalize(splitPackage[-1]) String butLast = NamesUtil.capitalize(splitPackage[-2]) diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/NamesUtil.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/NamesUtil.groovy index d7b173becc..a3febc5e98 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/NamesUtil.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/util/NamesUtil.groovy @@ -106,6 +106,6 @@ class NamesUtil { * Converts illegal package characters to underscores */ static String convertIllegalPackageChars(String packageName) { - return packageName.replace('-', '_').replace(" ", "_") + return packageName.replaceAll('[_\\- .]', '_') } } 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 966b6a55b6..a575718526 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 @@ -60,4 +60,12 @@ class ClassBuilderSpec extends Specification { ClassBuilder.retrieveBaseClass(props, contractRelativeFolder) == 'com.example.base.SuperpackageBase' } + def "should return a class from the generated path by when external contracts are picked"() { + given: + ContractVerifierConfigProperties props = new ContractVerifierConfigProperties(packageWithBaseClasses: "foo.Bar") + String contractRelativeFolder = ["org","springframework","cloud","contract","verifier","tests","META_INF","com.example","hello_world","1.0.0"].join(File.separator) + expect: + ClassBuilder.retrieveBaseClass(props, contractRelativeFolder) == 'foo.Bar.Hello_world1_0_0Base' + } + } diff --git a/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/NamesUtilTestSpec.groovy b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/NamesUtilTestSpec.groovy new file mode 100644 index 0000000000..8455fdf0c1 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/NamesUtilTestSpec.groovy @@ -0,0 +1,100 @@ +package org.springframework.cloud.contract.verifier.util + +import spock.lang.Specification + +/** + * @author Marcin Grzejszczak + */ +class NamesUtilTestSpec extends Specification { + + def "should return the whole string before the last one"() { + given: + String string = "a.b.c.d.e" + expect: + "a.b.c.d" == NamesUtil.beforeLast(string, ".") + } + + def "should return empty string when no token was found for before last"() { + given: + String string = "a.b.c.d.e" + expect: + "" == NamesUtil.beforeLast(string, "/") + } + + def "should return first token after the last one"() { + given: + String string = "a.b.c.d.e" + expect: + "e" == NamesUtil.afterLast(string, ".") + } + + def "should return the input string when no token was found for after last"() { + given: + String string = "a.b.c.d.e" + expect: + string == NamesUtil.afterLast(string, "/") + } + + def "should return first token after the last dot"() { + given: + String string = "a.b.c.d.e" + expect: + "e" == NamesUtil.afterLastDot(string) + } + + def "should return the input string when no token was found for after last dot"() { + given: + String string = "abcde" + expect: + string == NamesUtil.afterLastDot(string) + } + + def "should return camel case version of a string"() { + given: + String string = "BlaBlaBla" + expect: + "blaBlaBla" == NamesUtil.camelCase(string) + } + + def "should return capitalized version of a string"() { + given: + String string = "blaBlaBla" + expect: + "BlaBlaBla" == NamesUtil.capitalize(string) + } + + def "should return all text to last dot"() { + given: + String string = "a.b.c.d.e" + expect: + "a.b.c.d" == NamesUtil.toLastDot(string) + } + + def "should return the input string when no token was found for to last dot"() { + given: + String string = "abcde" + expect: + string == NamesUtil.toLastDot(string) + } + + def "should convert a package notation to directory"() { + given: + String string = "a.b.c.d.e" + expect: + "a/b/c/d/e".replace("/", File.separator) == NamesUtil.packageToDirectory(string) + } + + def "should convert a directory notation to package"() { + given: + String string = "a/b/c/d/e".replace("/", File.separator) + expect: + "a.b.c.d.e" == NamesUtil.directoryToPackage(string) + } + + def "should convert all illegal package chars to legal ones"() { + given: + String string = "a-b c.1.0.x" + expect: + "a_b_c_1_0_x" == NamesUtil.convertIllegalPackageChars(string) + } +}