diff --git a/docs/src/main/asciidoc/verifier/contract.adoc b/docs/src/main/asciidoc/verifier/contract.adoc index 88128fd1e2..c81babd407 100644 --- a/docs/src/main/asciidoc/verifier/contract.adoc +++ b/docs/src/main/asciidoc/verifier/contract.adoc @@ -9,6 +9,8 @@ Contract DSL is written in Groovy, but don't be alarmed if you didn't use Groovy a tiny subset of it (namely literals, method calls and closures). What's more the DSL is designed to be programmer-readable without any knowledge of the DSL itself - it's statically typed. +TIP: Spring Cloud Contract supports defining multiple contracts in a single file! + The Contract is present in the `spring-cloud-contract-spec` module of the Spring Cloud Contract Verifier repository. Let's look at full example of a contract definition. @@ -26,8 +28,6 @@ Not all features of the DSL are used in example above. If you didn't find what y WARNING: Spring Cloud Contract Verifier doesn't support XML properly. Please use JSON or help us implement this feature. -WARNING: Spring Cloud Contract Verifier supports equality check on text response. Regular expressions are not yet available. - WARNING: The support for the verification of size of JSON arrays is experimental. If you want to turn it on please provide the value of a system property `spring.cloud.contract.verifier.assert.size` equal to `true`. By default this feature is set to `false`. You can also provide the `assertJsonSize` property in the plugin configuration. @@ -47,6 +47,16 @@ You can add a `description` to your contract that is nothing else but an arbitra include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=description,indent=0] ---- +===== Name + +You can provide a name of your contract. Let's assume that you've provided a name `should register a user`. +If you do this then the name of the autogenerated test will be equal to `validate_should_register_a_user`. +Also the name of the stub will be `should_register_a_user.json` in case of a WireMock stub. + +IMPORTANT: Please ensure that the name doesn't contain any characters that will make the generated test + not possible to compile. Also remember that if you provide the same name for multiple contracts then your + autogenerated tests will fail to compile and your generated stubs will override each other. + ===== Ignoring contracts If you want to ignore a contract you can either set a value of ignored contracts in the plugin configuration @@ -111,8 +121,6 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract //include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=bodyAsXml,indent=0] //---- - - ==== Response Minimal response must contain **HTTP status code**. @@ -216,12 +224,13 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract and the following stub: -[source,javascript,indent=0] +[source,groovy,indent=0] ---- include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=wiremock,indent=0] ---- ==== Executing custom methods on server side + It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" in the configuration. Please see the examples below: @@ -240,7 +249,8 @@ include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/f ---- ==== JAX-RS support -Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define `protected WebTarget webTarget` and server initialization, right now the only option how to test JAX-RS API is to start a web server. + +We support JAX-RS 2 Client API. Base class needs to define `protected WebTarget webTarget` and server initialization, right now the only option how to test JAX-RS API is to start a web server. Request with a body needs to have a content type set otherwise `application/octet-stream` is going to be used. @@ -268,7 +278,7 @@ section a `async()` method. Example: ---- org.springframework.cloud.contract.spec.Contract.make { request { - method 'GET' + method GET() url '/get' } response { @@ -319,7 +329,86 @@ as presented below (note you can use either `$` or `value` methods to provide `c include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=consumer_producer] ---- -=== Cutomization +==== Multiple contracts in one file + +It's possible to define multiple contracts in one file. An example of such a contract can look like this + +[source,groovy,indent=0] +---- +include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy[lines=18..-1,indent=0] +---- + +In this example one contract has the `name` field and the other doesn't. This will lead to generation of +two tests that will look more or less like this: + +[source,java,indent=0] +---- +package org.springframework.cloud.contract.verifier.tests.com.hello; + +import com.example.TestBase; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.restassured.module.mockmvc.specification.MockMvcRequestSpecification; +import com.jayway.restassured.response.ResponseOptions; +import org.junit.Test; + +import static com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*; +import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +public class V1Test extends TestBase { + + @Test + public void validate_should_post_a_user() throws Exception { + // given: + MockMvcRequestSpecification request = given(); + + // when: + ResponseOptions response = given().spec(request) + .post("/users/1"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + } + + @Test + public void validate_withList_1() throws Exception { + // given: + MockMvcRequestSpecification request = given(); + + // when: + ResponseOptions response = given().spec(request) + .post("/users/2"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + } + +} +---- + +Notice that for the contract that has the `name` field the generated test method is named +`validate_should_post_a_user`. For the one that doesn't have the name it's called +`validate_withList_1`. It corresponds to the name of the file `WithList.groovy` and the +index of the contract in the list. + +The generated stubs will look like this + +[source] +---- +should post a user.json +1_WithList.json +---- + +As you can see the first file got the `name` parameter from the contract. The second +got the name of the contract file `WithList.groovy` prefixed with the index (in this case +contract had index `1` in the list of contracts in the file). + +TIP: As you can see it's much better if you name your contracts since then your tests + are far more meaningful. + + +=== Customization ==== Extending the DSL diff --git a/docs/src/main/asciidoc/verifier/rest.adoc b/docs/src/main/asciidoc/verifier/rest.adoc index 1217e77cd3..dad46bd012 100644 --- a/docs/src/main/asciidoc/verifier/rest.adoc +++ b/docs/src/main/asciidoc/verifier/rest.adoc @@ -138,7 +138,7 @@ contracts { contractsWorkOffline = false } -tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateWireMockClientStubs') { +tasks.create(type: Jar, name: 'verifierStubsJar', dependsOn: 'convert') { baseName = project.name classifier = contracts.stubsSuffix from contractVerifier.stubsOutputDir @@ -265,7 +265,7 @@ In consumer service you need to configure Spring Cloud Contract Verifier plugin [source,bash,indent=0] ---- -./gradlew generateWireMockClientStubs +./gradlew generateClientStubs ---- Note that `stubsOutputDir` option has to be set for stub generation to work. diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/SingleFileConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/SingleFileConverter.groovy index 99f5e8b042..fa2e21892b 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/SingleFileConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/converter/SingleFileConverter.groovy @@ -43,7 +43,7 @@ interface SingleFileConverter { * Returns the collection of converted contracts into stubs. One contract can * result in multiple stubs. */ - Collection convertContents(String rootName, ContractMetadata content) + Map convertContents(String rootName, ContractMetadata content) /** * Returns the name of the converted stub file. If you have multiple contracts diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverter.groovy index 678a1a56fd..4e80c4aa3c 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverter.groovy @@ -23,7 +23,6 @@ import org.springframework.cloud.contract.verifier.file.ContractMetadata import org.springframework.cloud.contract.verifier.util.NamesUtil import java.nio.charset.StandardCharsets - /** * Converts DSLs to WireMock stubs * @@ -44,15 +43,19 @@ class DslToWireMockClientConverter extends DslToWireMockConverter { } @Override - Collection convertContents(String rootName, ContractMetadata contract) { - if (contract.convertedContract.size() == 1) { - return [convertASingleContract(rootName, contract, contract.convertedContract.first())] + Map convertContents(String rootName, ContractMetadata contract) { + if (!(contract.convertedContract.any { it.request })) { + return [:] } - List convertedContracts = [] - contract.convertedContract.eachWithIndex { Contract dsl, int index -> + if (contract.convertedContract.size() == 1) { + return [(contract.convertedContract.first()): convertASingleContract(rootName, contract, contract.convertedContract.first())] + } + Map convertedContracts = [:] + contract.convertedContract.findAll { it.request }.eachWithIndex { Contract dsl, int index -> String name = dsl.name ? NamesUtil.convertIllegalPackageChars(dsl.name) : "${rootName}_${index}" - convertedContracts << convertASingleContract(name, contract, dsl) + convertedContracts << [(dsl) : convertASingleContract(name, contract, dsl)] } return convertedContracts } + } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy index feb391b509..d066f82eaa 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/main/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverter.groovy @@ -88,15 +88,17 @@ class RecursiveFilesConverter { return } int contractsSize = contract.convertedContract.size() - contract.convertedContract.eachWithIndex { Contract dsl, int index -> - String convertedContent = singleFileConverter.convertContent(entry.key.last().toString(), contract) - if (!convertedContent) { - return - } + Map convertedContent = singleFileConverter.convertContents(entry.key.last().toString(), contract) + if (!convertedContent) { + return + } + convertedContent.entrySet().eachWithIndex { Map.Entry content, int index -> + Contract dsl = content.key + String converted = content.value Path absoluteTargetPath = createAndReturnTargetDirectory(sourceFile) File newJsonFile = createTargetFileWithProperName(singleFileConverter, absoluteTargetPath, sourceFile, contractsSize, index, dsl) - newJsonFile.setText(convertedContent, StandardCharsets.UTF_8.toString()) + newJsonFile.setText(converted, StandardCharsets.UTF_8.toString()) } } catch (Exception e) { throw new ConversionContractVerifierException("Unable to make conversion of ${sourceFile.name}", e) diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy index debe34c0bb..9406083d08 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy @@ -20,6 +20,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping import org.junit.Rule import org.junit.rules.TemporaryFolder import org.skyscreamer.jsonassert.JSONAssert +import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.verifier.file.ContractMetadata import spock.lang.Issue import spock.lang.Specification @@ -46,7 +47,7 @@ class DslToWireMockClientConverterSpec extends Specification { } """) when: - String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() then: JSONAssert.assertEquals(''' {"request":{"method":"PUT","urlPattern":"/[0-9]{2}"},"response":{"status":200}} @@ -75,17 +76,37 @@ class DslToWireMockClientConverterSpec extends Specification { } ''') when: - List json = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)) + Map convertedContents = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)) then: - json.size() == 2 - JSONAssert.assertEquals(jsonResponse(1), json.first(), false) - JSONAssert.assertEquals(jsonResponse(2), json.last(), false) + convertedContents.size() == 2 + JSONAssert.assertEquals(jsonResponse(1), convertedContents.values().first(), false) + JSONAssert.assertEquals(jsonResponse(2), convertedContents.values().last(), false) } private String jsonResponse(int index) { return """{"request":{"method":"PUT","url":"/${index}"},"response":{"status":200}}""" } + def "should not convert if contract is messaging related"() { + given: + def converter = new DslToWireMockClientConverter() + and: + File file = tmpFolder.newFile("dsl1_list.groovy") + file.write(''' + (1..2).collect { int index -> + org.springframework.cloud.contract.spec.Contract.make { + input { + + } + } + } + ''') + when: + Map convertedContents = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)) + then: + convertedContents.isEmpty() + } + @Issue("196") def "should creation of delayed stub responses be possible"() { given: @@ -105,7 +126,7 @@ class DslToWireMockClientConverterSpec extends Specification { } """) when: - String json = converter.convertContent("test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() then: JSONAssert.assertEquals(''' {"request":{ @@ -168,7 +189,7 @@ class DslToWireMockClientConverterSpec extends Specification { } """) when: - String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() then: JSONAssert.assertEquals(''' { @@ -250,7 +271,7 @@ class DslToWireMockClientConverterSpec extends Specification { } """) when: - String json = converter.convertContent("test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() then: JSONAssert.assertEquals(''' {"request":{"urlPath":"/foos","method":"GET"},"response":{"body":"[{\\"id\\":\\"123\\"},{\\"id\\":\\"567\\"}]"}} @@ -280,7 +301,7 @@ class DslToWireMockClientConverterSpec extends Specification { } """) when: - String json = converter.convertContent("test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() StubMapping.buildFrom(json) then: noExceptionThrown() @@ -320,7 +341,7 @@ class DslToWireMockClientConverterSpec extends Specification { } ''') when: - String json = converter.convertContent("Test", new ContractMetadata(file.toPath(), false, 0, null)) + String json = converter.convertContents("Test", new ContractMetadata(file.toPath(), false, 0, null)).values().first() then: JSONAssert.assertEquals( // tag::wiremock[] ''' diff --git a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverterSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverterSpec.groovy index 7407181f4c..4d1f92cd09 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverterSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/RecursiveFilesConverterSpec.groovy @@ -17,19 +17,17 @@ package org.springframework.cloud.contract.verifier.wiremock import groovy.io.FileType -import org.springframework.cloud.contract.verifier.converter.SingleFileConvertersHolder - -import java.nio.file.Path -import java.nio.file.Paths - import org.junit.Rule import org.junit.rules.TemporaryFolder import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties import org.springframework.cloud.contract.verifier.converter.SingleFileConverter +import org.springframework.cloud.contract.verifier.converter.SingleFileConvertersHolder import org.springframework.util.FileSystemUtils - import spock.lang.Specification +import java.nio.file.Path +import java.nio.file.Paths + class RecursiveFilesConverterSpec extends Specification { private static @@ -49,12 +47,7 @@ class RecursiveFilesConverterSpec extends Specification { properties.stubsOutputDir = tmpFolder.newFolder("target") FileSystemUtils.copyRecursively(originalSourceRootDirectory, properties.contractsDslDir) and: - def singleFileConverterStub = Stub(SingleFileConverter) - singleFileConverterStub.canHandleFileName(_) >> { String fileName -> fileName.endsWith(".groovy") } - singleFileConverterStub.convertContent(_, _) >> { "converted" } - singleFileConverterStub.generateOutputFileNameForInput(_) >> { String inputFileName -> inputFileName.replaceAll('.groovy', '.json') } - - RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(singleFileConverterStub, properties) + RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(properties) when: recursiveFilesConverter.processFiles() then: @@ -63,7 +56,7 @@ class RecursiveFilesConverterSpec extends Specification { Set relativizedCreatedFiles = getRelativePathsForFilesInDirectory(createdFiles, properties.stubsOutputDir) EXPECTED_TARGET_FILES == relativizedCreatedFiles and: - createdFiles.each { it.text == "converted" } + createdFiles.each { assert it.text.contains("uuid") } } def "should recursively convert matching files with exlusions"() { @@ -98,7 +91,7 @@ class RecursiveFilesConverterSpec extends Specification { and: def singleFileConverterStub = Stub(SingleFileConverter) singleFileConverterStub.canHandleFileName(_) >> { true } - singleFileConverterStub.convertContent(_, _) >> { throw new NullPointerException("Test conversion error") } + singleFileConverterStub.convertContents(_, _) >> { throw new NullPointerException("Test conversion error") } singleFileConverterStub.generateOutputFileNameForInput(_) >> { String inputFileName -> "${inputFileName}2" } ContractVerifierConfigProperties properties = new ContractVerifierConfigProperties() properties.contractsDslDir = tmpFolder.root diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy index c0b5477b51..0d2ceb634f 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierExtension.groovy @@ -70,7 +70,7 @@ class ContractVerifierExtension { File generatedTestSourcesDir /** - * Dir where the generated WireMock stubs from Groovy DSL should be placed. + * Dir where the generated stubs from Groovy DSL should be placed. * You can then mention them in your packaging task to create jar with stubs */ File stubsOutputDir diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy index f02d131792..38604981b6 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/GenerateWireMockClientStubsFromDslTask.groovy @@ -26,7 +26,8 @@ import org.springframework.cloud.contract.verifier.wiremock.RecursiveFilesConver import static org.springframework.cloud.contract.verifier.plugin.SpringCloudContractVerifierGradlePlugin.COPY_CONTRACTS_TASK_NAME //TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ? /** - * Generates WireMock stubs from the contracts + * Generates stubs from the contracts. The name is WireMock related but the implementation + * can differ * * @since 1.0.0 */ @@ -45,7 +46,7 @@ class GenerateWireMockClientStubsFromDslTask extends ConventionTask { Task copyContractsTask = project.getTasksByName(COPY_CONTRACTS_TASK_NAME, false).first() ContractVerifierConfigProperties props = props(copyContractsTask) File contractsDslDir = contractsDslDir(copyContractsTask, props) - logger.info("Spring Cloud Contract Verifier Plugin: Invoking DSL to WireMock client stubs conversion") + logger.info("Spring Cloud Contract Verifier Plugin: Invoking DSL to client stubs conversion") props.contractsDslDir = contractsDslDir props.includedContracts = ".*" File outMappingsDir = getStubsOutputDir() != null ? new File(getStubsOutputDir(), DEFAULT_MAPPINGS_FOLDER) @@ -71,7 +72,7 @@ class GenerateWireMockClientStubsFromDslTask extends ConventionTask { try { return task.ext.contractsDslDir } catch (Exception e) { - project.logger.error("Couldn't retrieve the contractdsl property set by the copy contracts task", e) + project.logger.error("Couldn't retrieve the contract dsl property set by the copy contracts task", e) return props.contractsDslDir } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy index dc89646269..b9900f9d0b 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/main/groovy/org/springframework/cloud/contract/verifier/plugin/SpringCloudContractVerifierGradlePlugin.groovy @@ -47,7 +47,8 @@ import org.gradle.jvm.tasks.Jar class SpringCloudContractVerifierGradlePlugin implements Plugin { private static final String GENERATE_SERVER_TESTS_TASK_NAME = 'generateContractTests' - private static final String DSL_TO_WIREMOCK_CLIENT_TASK_NAME = 'generateWireMockClientStubs' + private static final String DEPRECATED_DSL_TO_WIREMOCK_CLIENT_TASK_NAME = 'generateWireMockClientStubs' + private static final String DSL_TO_CLIENT_TASK_NAME = 'generateClientStubs' @PackageScope static final String COPY_CONTRACTS_TASK_NAME = 'copyContracts' private static final String VERIFIER_STUBS_JAR_TASK_NAME = 'verifierStubsJar' @@ -69,7 +70,8 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { Task copyContracts = createAndConfigureCopyContractsTask(stubsJar, downloader, extension) createAndConfigureMavenPublishPlugin(stubsJar) createGenerateTestsTask(extension, copyContracts) - createAndConfigureGenerateWireMockClientStubsFromDslTask(extension, copyContracts) + Task clientTask = createAndConfigureGenerateClientStubsFromDslTask(extension, copyContracts) + createAndConfigureGenerateWireMockClientStubsFromDslTask(extension, clientTask) addProjectDependencies(project) addIdeaTestSources(project, extension) } @@ -89,6 +91,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { } private void addProjectDependencies(Project project) { + //TODO: Consider removing this at some point project.dependencies.add("testCompile", "com.github.tomakehurst:wiremock:2.1.7") project.dependencies.add("testCompile", "com.toomuchcoding.jsonassert:jsonassert:0.4.7") project.dependencies.add("testCompile", "org.assertj:assertj-core:2.3.0") @@ -119,10 +122,19 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { task.dependsOn copyContracts } + // TODO: Remove this task at some point private void createAndConfigureGenerateWireMockClientStubsFromDslTask(ContractVerifierExtension extension, + Task mainTask) { + Task task = project.tasks.create(DEPRECATED_DSL_TO_WIREMOCK_CLIENT_TASK_NAME) + task.description = "DEPRECATED: Generate WireMock client stubs from the contracts. Use ${DSL_TO_CLIENT_TASK_NAME} task." + task.group = GROUP_NAME + task.dependsOn mainTask + } + + private Task createAndConfigureGenerateClientStubsFromDslTask(ContractVerifierExtension extension, Task copyContracts) { - Task task = project.tasks.create(DSL_TO_WIREMOCK_CLIENT_TASK_NAME, GenerateWireMockClientStubsFromDslTask) - task.description = "Generate WireMock client stubs from the contracts" + Task task = project.tasks.create(DSL_TO_CLIENT_TASK_NAME, GenerateWireMockClientStubsFromDslTask) + task.description = "Generate client stubs from the contracts" task.group = GROUP_NAME task.conventionMapping.with { downloader = { gradleContractsDownloader } @@ -130,6 +142,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { configProperties = { extension } } task.dependsOn copyContracts + return task } private Task createAndConfigureStubsJarTasks(ContractVerifierExtension extension) { @@ -139,7 +152,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin { return task } else { task = project.tasks.create(type: Jar, name: VERIFIER_STUBS_JAR_TASK_NAME, - dependsOn: DSL_TO_WIREMOCK_CLIENT_TASK_NAME) { + dependsOn: DSL_TO_CLIENT_TASK_NAME) { baseName = project.name classifier = extension.stubsSuffix from { extension.stubsOutputDir ?: project.file("${project.buildDir}/stubs") } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/BasicFunctionalSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/BasicFunctionalSpec.groovy index 8265ac9236..c5bc8c9547 100755 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/BasicFunctionalSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/BasicFunctionalSpec.groovy @@ -43,6 +43,7 @@ class BasicFunctionalSpec extends ContractVerifierIntegrationSpec { BuildResult result = run(checkAndPublishToMavenLocal()) then: result.task(":generateWireMockClientStubs").outcome == SUCCESS + result.task(":generateClientStubs").outcome == SUCCESS result.task(":generateContractTests").outcome == SUCCESS and: "tests generated" diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy index 8da257cb0d..caf68dbb48 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/groovy/org/springframework/cloud/contract/verifier/plugin/ContractVerifierSpec.groovy @@ -55,7 +55,7 @@ class ContractVerifierSpec extends Specification { project.plugins.apply(SpringCloudContractVerifierGradlePlugin) expect: - project.tasks.findByName("generateWireMockClientStubs") != null + project.tasks.findByName("generateClientStubs") != null } def "should create verifierStubsJar task"() { @@ -71,7 +71,7 @@ class ContractVerifierSpec extends Specification { project.plugins.apply(SpringCloudContractVerifierGradlePlugin) expect: - project.tasks.verifierStubsJar.getDependsOn().contains("generateWireMockClientStubs") + project.tasks.verifierStubsJar.getDependsOn().contains("generateClientStubs") } def "should create copyContracts task"() { diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/resources/application.yml index a30a91f034..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/fraudDetectionService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8085 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/loanApplicationService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/loanApplicationService/src/main/resources/application.yml index 874c96fb5b..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/loanApplicationService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleJerseyProject/loanApplicationService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8094 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/fraudDetectionService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/fraudDetectionService/src/main/resources/application.yml index a30a91f034..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/fraudDetectionService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/fraudDetectionService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8085 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/loanApplicationService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/loanApplicationService/src/main/resources/application.yml index b285fcd8bd..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/loanApplicationService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/sampleProject/loanApplicationService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8091 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/fraudDetectionService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/fraudDetectionService/src/main/resources/application.yml index a30a91f034..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/fraudDetectionService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/fraudDetectionService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8085 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml index d3f59e8de6..1c421cf2b7 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml +++ b/spring-cloud-contract-tools/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/scenarioProject/loanApplicationService/src/main/resources/application.yml @@ -1 +1 @@ -server.port=8092 \ No newline at end of file +server.port=0 \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java index c984576063..491a0da50a 100644 --- a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/java/org/springframework/cloud/contract/maven/verifier/PluginUnitTest.java @@ -16,6 +16,11 @@ */ package org.springframework.cloud.contract.maven.verifier; +import static io.takari.maven.testing.TestMavenRuntime.newParameter; +import static io.takari.maven.testing.TestResources.assertFilesNotPresent; +import static io.takari.maven.testing.TestResources.assertFilesPresent; +import static org.assertj.core.api.BDDAssertions.then; + import java.io.File; import org.apache.commons.io.FileUtils; @@ -25,11 +30,6 @@ import org.junit.Test; import io.takari.maven.testing.TestMavenRuntime; import io.takari.maven.testing.TestResources; -import static io.takari.maven.testing.TestMavenRuntime.newParameter; -import static io.takari.maven.testing.TestResources.assertFilesNotPresent; -import static io.takari.maven.testing.TestResources.assertFilesPresent; -import static org.assertj.core.api.BDDAssertions.then; - public class PluginUnitTest { @Rule @@ -213,4 +213,34 @@ public class PluginUnitTest { File test = new File(basedir, path); then(FileUtils.readFileToString(test)).contains("extends TestBase").contains("import com.example.TestBase"); } + + @Test + public void shouldGenerateContractTestsWithAFileContainingAListOfContracts() throws Exception { + File basedir = this.resources.getBasedir("multiple-contracts"); + + this.maven.executeMojo(basedir, "generateTests", newParameter("testFramework", "JUNIT")); + + String path = "target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/com/hello/V1Test.java"; + assertFilesPresent(basedir, path); + File test = new File(basedir, path); + then(FileUtils.readFileToString(test)) + .contains("public void validate_should_post_a_user() throws Exception {") + .contains("public void validate_withList_1() throws Exception {"); + } + + @Test + public void shouldGenerateStubsWithAFileContainingAListOfContracts() throws Exception { + File basedir = this.resources.getBasedir("multiple-contracts"); + + this.maven.executeMojo(basedir, "convert", newParameter("stubsDirectory", "target/foo")); + + String firstFile = "target/foo/mappings/com/hello/v1/should post a user.json"; + File test = new File(basedir, firstFile); + assertFilesPresent(basedir, "target/foo/mappings/com/hello/v1/1_WithList.json"); + then(FileUtils.readFileToString(test)).contains("/users/1"); + String secondFile = "target/foo/mappings/com/hello/v1/1_WithList.json"; + File test2 = new File(basedir, secondFile); + assertFilesPresent(basedir, "target/foo/mappings/com/hello/v1/should post a user.json"); + then(FileUtils.readFileToString(test2)).contains("/users/2"); + } } diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/pom.xml b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/pom.xml new file mode 100644 index 0000000000..b7a1c33b92 --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + org.springframework.cloud.verifier.sample + sample-project + 0.1 + + + + + org.springframework.cloud + spring-cloud-contract-maven-plugin + + com.example.FooBase + + + .*com.* + com.example.TestBase + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy new file mode 100644 index 0000000000..08ee2dc9ac --- /dev/null +++ b/spring-cloud-contract-tools/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy @@ -0,0 +1,40 @@ +/** + * + * Copyright 2013-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.springframework.cloud.contract.spec.Contract + +[ + Contract.make { + name("should post a user") + request { + method 'POST' + url('/users/1') + } + response { + status 200 + } + }, + Contract.make { + request { + method 'POST' + url('/users/2') + } + response { + status 200 + } + } +] diff --git a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBuilder.groovy b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBuilder.groovy index b86797c08f..e89463f5a6 100644 --- a/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBuilder.groovy +++ b/spring-cloud-contract-verifier/src/main/groovy/org/springframework/cloud/contract/verifier/builder/MethodBuilder.groovy @@ -63,7 +63,7 @@ class MethodBuilder { private static String methodName(ContractMetadata contract, File stubsFile, Contract stubContent) { if (stubContent.name) { - return NamesUtil.camelCase(stubContent.name) + return NamesUtil.camelCase(NamesUtil.convertIllegalPackageChars(stubContent.name)) } else if (contract.convertedContract.size() > 1) { int index = contract.convertedContract.findIndexOf { it == stubContent} return "${camelCasedMethodFromFileName(stubsFile)}_${index}"