diff --git a/README.adoc b/README.adoc index f4f2837e96..b9b903aa3e 100644 --- a/README.adoc +++ b/README.adoc @@ -147,6 +147,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 @@ -198,8 +241,7 @@ Spring Boot container if there is one. https://projects.spring.io/spring-restdocs[Spring RestDocs] can be used to generate documentation (e.g. in asciidoctor format) for an -HTTP API with https://docs.spring.io/spring/docs/current/spring-framework-reference/html/integration-testing.html#spring-mvc-test-framework[Spring MockMvc] -or http://rest-assured.io/[RestAssured]. At the same time as you +HTTP API with Spring MockMvc or RestEasy. At the same time as you generate documentation for your API, you can also generate WireMock stubs, by using Spring Cloud Contract WireMock. Just write your normal RestDocs test cases and use `@AutoConfigureRestDocs` to have stubs 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 dce56be93a..f72ae700d4 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 @@ -63,9 +63,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 1719be19a9..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('-', '_') + 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) + } +}