diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/src/main/asciidoc/_attributes.adoc index 99a2353223..0f0e53e5a8 100644 --- a/docs/src/main/asciidoc/_attributes.adoc +++ b/docs/src/main/asciidoc/_attributes.adoc @@ -21,6 +21,7 @@ :verifier_root_path: {core_path}/spring-cloud-contract-verifier :contract_spec_path: {core_path}/specs/spring-cloud-contract-spec-java :contract_spec_tests_path: {core_path}/specs/spring-cloud-contract-spec +:contract_kotlin_spec_path: {core_path}/specs/spring-cloud-contract-spec-kotlin :samples_path: {core_path}/samples :verifier_core_path: {verifier_root_path} :stubrunner_core_path: {core_path}/spring-cloud-contract-stub-runner diff --git a/docs/src/main/asciidoc/_project-features-contract.adoc b/docs/src/main/asciidoc/_project-features-contract.adoc index 5e68a61597..f966635efe 100644 --- a/docs/src/main/asciidoc/_project-features-contract.adoc +++ b/docs/src/main/asciidoc/_project-features-contract.adoc @@ -7,6 +7,7 @@ Spring Cloud Contract supports the DSLs written in the following languages: * Groovy * YAML * Java +* Kotlin TIP: Spring Cloud Contract supports defining multiple contracts in a single file. @@ -30,6 +31,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0] ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest.java[tags=class,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_rest.kts[tags=class,indent=0] +---- ==== [TIP] @@ -89,6 +96,17 @@ contracts { ---- ==== +[[contract-kotlin]] +=== Contract DSL in Kotlin + +To get started with writing contracts in Kotlin you would need to start with a (newly created) Kotlin Script file (.kts). +Just like the with the Java DSL you can put your contracts in any directory of your choice. +The Maven and Gradle plugins will look at the `src/test/resources/contracts` directory by default. + +IMPORTANT: Remember that, inside the Kotlin Script file, you have to provide the fully qualified name to the `ContractDSL` class. +Generally you would use its contract function like this: `org.springframework.cloud.contract.spec.ContractDsl.contract { ... }`. +You can also provide an import to the `contract` function (`import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract`) and then call `contract { ... }`. + [[contract-limitations]] === Limitations @@ -136,6 +154,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0] ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=description,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{contract_kotlin_spec_path}/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt[tags=description,indent=0] +---- ==== [[contract-dsl-name]] @@ -171,6 +195,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=name,inde ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=name,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{contract_kotlin_spec_path}/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt[tags=name,indent=0] +---- ==== [[contract-dsl-ignoring-contracts]] @@ -198,6 +228,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=ignored,i ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=ignored,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{contract_kotlin_spec_path}/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt[tags=ignored,indent=0] +---- ==== [[contract-dsl-in-progress]] @@ -228,6 +264,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=in_progre ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=in_progress,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{contract_kotlin_spec_path}/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt[tags=in_progress,indent=0] +---- ==== You can set the value of the `failOnInProgress` Spring Cloud Contract plugin property to ensure that your build will break when at least one contract in progress remains in your sources. @@ -269,6 +311,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_from_file.yml[inde ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_from_file.java[tags=class,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/readFromFile.kts[tags=class,indent=0] +---- ==== Further assume that the JSON files is as follows: @@ -314,6 +362,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_pdf.yml[indent=0] ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_from_pdf.java[tags=class,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{contract_kotlin_spec_path}/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts[tags=class,indent=0] +---- ==== IMPORTANT: You should use this approach whenever you want to work with binary payloads, @@ -362,6 +416,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response, ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=http_dsl,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=http_dsl,indent=0] +---- ==== IMPORTANT: If you want to make your contract have a higher priority, @@ -394,6 +454,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request_o ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=request,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=request,indent=0] +---- ==== You can specify an absolute rather than a relative `url`, but using `urlPath` is @@ -419,6 +485,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_rest_with_path.yml ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=url,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=url,indent=0] +---- ==== `request` may contain query parameters, as the following example (which uses `urlPath`) shows: @@ -443,6 +515,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=query_par ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=urlpath,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=urlpath,indent=0] +---- ==== `request` can contain additional request headers, as the following example shows: @@ -467,6 +545,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=headers,i ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=headers,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=headers,indent=0] +---- ==== `request` may contain additional request cookies, as the following example shows: @@ -491,6 +575,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=cookies,i ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=cookies,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=cookies,indent=0] +---- ==== `request` may contain a request body, as the following example shows: @@ -515,6 +605,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=body,inde ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=body,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=body,indent=0] +---- ==== `request` can contain multipart elements. To include multipart elements, use the @@ -538,6 +634,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_multipart.yml[inde ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_multipart.java[tags=class,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/multipart.kts[tags=class,indent=0] +---- ==== In the preceding example, we define parameters in either of two ways: @@ -618,6 +720,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response_ ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=response,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=response,indent=0] +---- ==== Besides status, the response may contain headers, cookies, and a body, which are @@ -706,6 +814,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=regex,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=regex,indent=0] +---- ==== You can also provide only one side of the communication with a regular expression. If you @@ -744,13 +858,22 @@ All of those methods start with the `any` prefix, as follows: include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java[tags=regex_creating_props,indent=0] ---- -The following example shows how you can reference those methods (example for Groovy DSL): +The following example shows how you can reference those methods: -[source,groovy,indent=0] +==== +[source,groovy,indent=0,role="primary"] +.groovy ---- include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=regex_creating_props,indent=0] ---- +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=regex_creating_props,indent=0] +---- +==== + [[contract-dsl-regex-limitations]] ===== Limitations @@ -788,6 +911,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=optionals,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=optionals,indent=0] +---- ==== By wrapping a part of the body with the `optional()` method, you create a regular @@ -833,6 +962,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=method,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_docs_examples.kts[tags=method,indent=0] +---- ==== The following code shows the base class portion of the test case: @@ -978,6 +1113,37 @@ class shouldReturnStatsForAUser implements Supplier { } ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +package contracts.beer.rest + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = method("POST") + url = url("/stats") + body(mapOf( + "name" to anyAlphaUnicode + )) + headers { + contentType = APPLICATION_JSON + } + } + response { + status = OK + body(mapOf( + "text" to "Don't worry ${fromRequest().body("$.name")} thanks for your interested in drinking beer", + "quantity" to v(c(5), p(anyNumber)) + )) + headers { + contentType = fromRequest().header(CONTENT_TYPE) + } + } +} +---- ==== Running a JUnit test generation leads to a test that resembles the following example: @@ -1401,6 +1567,22 @@ class contract implements Supplier> { } ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + // ... + } + response { + async = true + // ... + } +} +---- ==== You can also use the `fixedDelayMilliseconds` method or property to add delay to your stubs. @@ -1450,6 +1632,22 @@ class contract implements Supplier> { } ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + // ... + } + response { + delay = fixedMilliseconds(1000) + // ... + } +} +---- ==== [[contract-dsl-xml]] @@ -1482,6 +1680,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.yml ---- include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_xml.java[tags=class,indent=0] ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +include::{verifier_core_path}/src/test/resources/kotlin/contract_xml.kts[tags=class,indent=0] +---- ==== The following example shows an automatically generated test for XML in the response body: @@ -1555,6 +1759,25 @@ class contract implements Supplier> { } ---- + +[source,kotlin,indent=0,subs="verbatim,attributes",role="secondary"] +.kotlin +---- +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +arrayOf( + contract { + name("should post a user") + // ... + }, + contract { + // ... + }, + contract { + // ... + } +} +---- ==== In the preceding example, one contract has the `name` field and the other does not. This diff --git a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts index 334c2d5e5a..081e7d89fc 100644 --- a/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts +++ b/samples/standalone/kotlin/http-server/src/test/resources/contracts/fraud/shouldMarkClientAsNotFraud.kts @@ -19,6 +19,7 @@ package fraud import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract import org.springframework.cloud.contract.spec.internal.HttpStatus +// tag::contract[] contract { request { method = PUT @@ -41,3 +42,4 @@ contract { } } } +// end::contract[] diff --git a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt index c0f7478cfd..e2866a4e4f 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/main/kotlin/org/springframework/cloud/contract/spec/internal/ResponseDsl.kt @@ -39,7 +39,7 @@ class ResponseDsl : CommonDsl() { fun code(code: Int): DslProperty = code.toDslProperty() - fun fixedMilliseconds(delay: Long) = delay.toDslProperty() + fun fixedMilliseconds(delay: Long): DslProperty = delay.toDslProperty() fun headers(headers: HeadersDsl.() -> Unit) { this.headers = ResponseHeadersDsl().apply(headers).get() diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt b/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt index dc7c9fbf60..c6488c73ac 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/kotlin/org/springframework/cloud/contract/spec/ContractTests.kt @@ -214,8 +214,11 @@ class ContractTests { @Test fun `should set a description`() { - val contract = contract { - description = """ + val contract = +// @formatter: off +// tag::description[] +contract { + description = """ given: An input when: @@ -223,8 +226,9 @@ when: then: Output """ - } - +} +// end::description[] +// @formatter: on assertDoesNotThrow { Contract.assertContract(contract) }.also { @@ -241,9 +245,12 @@ then: @Test fun `should set a name`() { - val contract = contract { + val contract = + // tag::name[] + contract { name = "some_special_name" } + // end::name[] assertDoesNotThrow { Contract.assertContract(contract) @@ -254,9 +261,12 @@ then: @Test fun `should mark a contract ignored`() { - val contract = contract { + val contract = + // tag::ignored[] + contract { ignored = true } + // end::ignored[] assertDoesNotThrow { Contract.assertContract(contract) @@ -267,9 +277,12 @@ then: @Test fun `should mark a contract in progress`() { - val contract = contract { + val contract = + // tag::in_progress[] + contract { inProgress = true } + // end::in_progress[] assertDoesNotThrow { Contract.assertContract(contract) diff --git a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts index a893effffe..6e66827302 100644 --- a/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts +++ b/specs/spring-cloud-contract-spec-kotlin/src/test/resources/contracts/shouldWorkWithBinaryPayload.kts @@ -16,8 +16,8 @@ package contracts +//tag::class[] import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract -import org.springframework.cloud.contract.spec.internal.HttpMethods contract { request { @@ -36,3 +36,4 @@ contract { } } } +//end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java index f3985893dd..c813fc1ca5 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_multipart.java @@ -15,6 +15,7 @@ * limitations under the License. */ +// tag::class[] import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -68,4 +69,4 @@ class contract_multipart implements Supplier> { } } -// end::class[] \ No newline at end of file +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java index a9745656b2..e650990866 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest.java @@ -15,6 +15,7 @@ * limitations under the License. */ +// tag::class[] import java.util.Collection; import java.util.Collections; import java.util.function.Supplier; @@ -69,4 +70,4 @@ class contract_rest implements Supplier> { } } -// end::class[] \ No newline at end of file +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java index addea24703..9dfc8a0e30 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_file.java @@ -15,6 +15,7 @@ * limitations under the License. */ +// tag::class[] import java.util.Collection; import java.util.Collections; import java.util.function.Supplier; @@ -39,4 +40,4 @@ class contract_rest_from_file implements Supplier> { } } -// end::class[] \ No newline at end of file +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_pdf.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_pdf.java index df507556e0..648fc30606 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_pdf.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_from_pdf.java @@ -14,7 +14,7 @@ * limitations under the License. */ -//tag::class[] +// tag::class[] import java.util.Collection; import java.util.Collections; import java.util.function.Supplier; @@ -45,4 +45,4 @@ class contract_rest_from_pdf implements Supplier> { } } -// end::class[] \ No newline at end of file +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java index 6d975f113a..d5b632bc3a 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_rest_with_tags.java @@ -15,6 +15,7 @@ * limitations under the License. */ +// tag::class[] import java.util.Collection; import java.util.Collections; import java.util.function.Supplier; @@ -102,4 +103,4 @@ class contract_rest_with_tags implements Supplier> { } } -// end::class[] \ No newline at end of file +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_xml.java b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_xml.java index 319a6d2e65..6d315a42de 100644 --- a/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_xml.java +++ b/spring-cloud-contract-verifier/src/test/resources/contractsToCompile/contract_xml.java @@ -15,6 +15,7 @@ * limitations under the License. */ +// tag::class[] import java.util.function.Supplier; import org.springframework.cloud.contract.spec.Contract; @@ -64,3 +65,4 @@ class contract_xml implements Supplier { }; } +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_docs_examples.kts b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_docs_examples.kts new file mode 100644 index 0000000000..501d5663f2 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_docs_examples.kts @@ -0,0 +1,329 @@ +/* + * Copyright 2013-2019 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 + * + * https://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. + */ + +package contracts + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract +import org.springframework.cloud.contract.spec.withQueryParameters + +var httpDsl: org.springframework.cloud.contract.spec.Contract = + // tag::http_dsl[] + contract { + // Definition of HTTP request part of the contract + // (this can be a valid request or invalid depending + // on type of contract being specified). + request { + method = GET + url = url("/foo") + // ... + } + + // Definition of HTTP response part of the contract + // (a service implementing this contract should respond + // with following response after receiving request + // specified in "request" part above). + response { + status = OK + // ... + } + + // Contract priority, which can be used for overriding + // contracts (1 is highest). Priority is optional. + priority = 1 + } + // end::http_dsl[] + + +var request: org.springframework.cloud.contract.spec.Contract = + // tag::request[] + contract { + request { + // HTTP request method (GET/POST/PUT/DELETE). + method = method("GET") + + // Path component of request URL is specified as follows. + urlPath = path("/users") + } + response { + // ... + status = code(200) + } + } + // end::request[] + + +var url: org.springframework.cloud.contract.spec.Contract = + // tag::url[] + contract { + request { + method = GET + + // Specifying `url` and `urlPath` in one contract is illegal. + url("http://localhost:8888/users") + } + response { + // ... + status = OK + } + } + // end::url[] + +var urlPaths: org.springframework.cloud.contract.spec.Contract = + // tag::urlpath[] + contract { + request { + // ... + method = GET + + // Each parameter is specified in form + // `'paramName' : paramValue` where parameter value + // may be a simple literal or one of matcher functions, + // all of which are used in this example. + urlPath = path("/users") withQueryParameters { + // If a simple literal is used as value + // default matcher function is used (equalTo) + parameter("limit", 100) + + // `equalTo` function simply compares passed value + // using identity operator (==). + parameter("filter", equalTo("email")) + + // `containing` function matches strings + // that contains passed substring. + parameter("gender", value(consumer(containing("[mf]")), producer("mf"))) + + // `matching` function tests parameter + // against passed regular expression. + parameter("offset", value(consumer(matching("[0-9]+")), producer(123))) + + // `notMatching` functions tests if parameter + // does not match passed regular expression. + parameter("loginStartsWith", value(consumer(notMatching(".{0,2}")), producer(3))) + } + + // ... + } + response { + // ... + status = code(200) + } + } + // end::urlpath[] + +var headers: org.springframework.cloud.contract.spec.Contract = + // tag::headers[] + contract { + request { + // ... + method = GET + url = url("/foo") + + // Each header is added in form `'Header-Name' : 'Header-Value'`. + // there are also some helper variables + headers { + header("key", "value") + contentType = APPLICATION_JSON + } + + // ... + } + response { + // ... + status = OK + } + } + // end::headers[] + +var cookies: org.springframework.cloud.contract.spec.Contract = + // tag::cookies[] + contract { + request { + // ... + method = GET + url = url("/foo") + + // Each Cookies is added in form `'Cookie-Key' : 'Cookie-Value'`. + // there are also some helper methods + cookies { + cookie("key", "value") + cookie("another_key", "another_value") + } + + // ... + } + + response { + // ... + status = code(200) + } + } + // end::cookies[] + +var body: org.springframework.cloud.contract.spec.Contract = + // tag::body[] + contract { + request { + // ... + method = GET + url = url("/foo") + + // Currently only JSON format of request body is supported. + // Format will be determined from a header or body's content. + body = body("{ \"login\" : \"john\", \"name\": \"John The Contract\" }") + } + response { + // ... + status = OK + } + } + // end::body[] + +var response: org.springframework.cloud.contract.spec.Contract = + // tag::response[] + contract { + request { + // ... + method = GET + url =url("/foo") + } + response { + // Status code sent by the server + // in response to request specified above. + status = OK + } + } + // end::response[] + +var regex: org.springframework.cloud.contract.spec.Contract = + // tag::regex[] + contract { + request { + method = method("GET") + url = url(v(consumer(regex("\\/[0-9]{2}")), producer("/12"))) + } + response { + status = OK + body(mapOf( + "id" to v(anyNumber), + "surname" to v(consumer("Kowalsky"), producer(regex("[a-zA-Z]+"))) + )) + headers { + header("Content-Type", "text/plain") + } + } + } + // end::regex[] + +var regexCreatingProps: org.springframework.cloud.contract.spec.Contract = + // tag::regex_creating_props[] + contract { + name = "foo" + label = "trigger_event" + input { + triggeredBy = "toString()" + } + outputMessage { + sentTo = sentTo("topic.rateablequote") + body(mapOf( + "alpha" to v(anyAlphaUnicode), + "number" to v(anyNumber), + "anInteger" to v(anyInteger), + "positiveInt" to v(anyPositiveInt), + "aDouble" to v(anyDouble), + "aBoolean" to v(aBoolean), + "ip" to v(anyIpAddress), + "hostname" to v(anyAlphaUnicode), + "email" to v(anyEmail), + "url" to v(anyUrl), + "httpsUrl" to v(anyHttpsUrl), + "uuid" to v(anyUuid), + "date" to v(anyDate), + "dateTime" to v(anyDateTime), + "time" to v(anyTime), + "iso8601WithOffset" to v(anyIso8601WithOffset), + "nonBlankString" to v(anyNonBlankString), + "nonEmptyString" to v(anyNonEmptyString), + "anyOf" to v(anyOf('foo', 'bar')) + )) + headers { + header("Content-Type", "text/plain") + } + } + } + //end::regex_creating_props[] + +var optionals: org.springframework.cloud.contract.spec.Contract = + // tag::optionals[] + contract { c -> + priority = 1 + name = "optionals" + request { + method = POST + url = url("/users/password") + headers { + contentType = APPLICATION_JSON + } + body = body(mapOf( + "email" to v(consumer(optional(regex(email))), producer("abc@abc.com")), + "callback_url" to v(consumer(regex(hostname)), producer("https://partners.com")) + )) + } + response { + status = NOT_FOUND + headers { + header("Content-Type", "application/json") + } + body(mapOf( + "code" to value(consumer("123123"), producer(optional("123123"))) + )) + } + } + // end::optionals[] + +var methodDsl: org.springframework.cloud.contract.spec.Contract = + contract { c -> + request { + // tag::method[] + method = GET + // end::method[] + url = url("/foo") + } + response { + status = NOT_FOUND + } + priority = 1 + } + +var method: org.springframework.cloud.contract.spec.Contract = + // tag::methodBuilder[] + contract { + request { + method = PUT + url = url(v(consumer(regex("^/api/[0-9]{2}$")), producer("/api/12"))) + headers { + header("Content-Type", "application/json") + } + body = body("[{\"text\": \"Gonna see you at Warsaw\" }]") + } + response { + status = OK + body = body(mapOf( + "path" to v(consumer("/api/12"), producer(regex("^/api/[0-9]{2}$"))), + "correlationId" to v(consumer("1223456"), producer(execute("isProperCorrelationId(\$it)"))) + )) + } + } + // end::methodBuilder[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_rest.kts b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_rest.kts new file mode 100644 index 0000000000..b5c177e249 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_rest.kts @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2019 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 + * + * https://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. + */ + +// tag::class[] +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract +import org.springframework.cloud.contract.spec.withQueryParameters + +contract { + name = "some name" + description = "Some description" + priority = 8 + ignored = true + request { + url = url("/foo") withQueryParameters { + parameter("a", "b") + parameter("b", "c") + } + method = PUT + headers { + header("foo", value(client(regex("bar")), server("bar"))) + header("fooReq", "baz") + } + body = body(mapOf("foo" to "bar")) + bodyMatchers { + jsonPath("$.foo", byRegex("bar")) + } + } + response { + delay = fixedMilliseconds(1000) + status = OK + headers { + header("foo2", value(server(regex("bar")), client("bar"))) + header("foo3", value(server(execute("andMeToo(\$it)")), client("foo33"))) + header("fooRes", "baz") + } + body = body(mapOf( + "foo" to "bar", + "foo3" to "baz", + "nullValue" to null + )) + bodyMatchers { + jsonPath("$.foo2", byRegex("bar")) + jsonPath("$.foo3", byCommand("executeMe(\$it)")) + jsonPath("$.nullValue", byNull) + } + } +} +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_xml.kts b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_xml.kts new file mode 100644 index 0000000000..40265aebf7 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/contract_xml.kts @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2019 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 + * + * https://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. + */ + +package contracts + +// tag::class[] +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = GET + urlPath = path("/get") + headers { + contentType = APPLICATION_XML + } + } + response { + status = OK + headers { + contentType =APPLICATION_XML + } + body = body("\n" + "123\n" + + "abc\n" + "\n" + "abc\n" + + "def\n" + "ghi\n" + "\n" + + "123\n" + "true\n" + + "2017-01-01\n" + + "2017-01-01T01:23:45\n" + + "\n" + + "foo\n" + + "foo\n" + "") + bodyMatchers { + xPath("/test/duck/text()", byRegex("[0-9]{3}")) + xPath("/test/duck/text()", byCommand("equals(\$it)")) + xPath("/test/duck/xxx", byNull) + xPath("/test/duck/text()", byEquality) + xPath("/test/alpha/text()", byRegex(onlyAlphaUnicode)) + xPath("/test/alpha/text()", byEquality) + xPath("/test/number/text()", byRegex(number)) + xPath("/test/date/text()", byDate) + xPath("/test/dateTime/text()", byTimestamp) + xPath("/test/time/text()", byTime) + xPath("/test/*/complex/text()", byEquality) + xPath("/test/duck/@type", byEquality) + } + } +} +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/multipart.kts b/spring-cloud-contract-verifier/src/test/resources/kotlin/multipart.kts new file mode 100644 index 0000000000..816f4160af --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/multipart.kts @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 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 + * + * https://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. + */ + +package contracts + +// tag::class[] +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = PUT + url = url("/multipart") + multipart { + field("formParameter", value(consumer(regex("\".+\"")), producer("\"formParameterValue\""))) + field("someBooleanParameter", value(consumer(anyBoolean), producer("true"))) + field("file", + named( + // name of the file + value(consumer(regex(nonEmpty)), producer("filename.csv")), + // content of the file + value(consumer(regex(nonEmpty)), producer("file content")), + // content type for the part + value(consumer(regex(nonEmpty)), producer("application/json")) + ) + ) + } + headers { + contentType = "multipart/form-data;boundary=AaB03x" + } + } + response { + status = OK + } +} +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/readFromFile.kts b/spring-cloud-contract-verifier/src/test/resources/kotlin/readFromFile.kts new file mode 100644 index 0000000000..d3d0043b70 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/readFromFile.kts @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2019 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 + * + * https://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. + */ + +// tag::class[] +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + url = url("/1") + method = PUT + headers { + contentType = APPLICATION_JSON + } + body = bodyFromFile("request.json") + } + response { + status = OK + body = bodyFromFile("response.json") + headers { + contentType = APPLICATION_JSON + } + } +} +// end::class[] diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/request.json b/spring-cloud-contract-verifier/src/test/resources/kotlin/request.json new file mode 100644 index 0000000000..26c2b2f565 --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/request.json @@ -0,0 +1,3 @@ +{ + "status": "REQUEST" +} diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/request.pdf b/spring-cloud-contract-verifier/src/test/resources/kotlin/request.pdf new file mode 100644 index 0000000000..69503635d8 Binary files /dev/null and b/spring-cloud-contract-verifier/src/test/resources/kotlin/request.pdf differ diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/response.json b/spring-cloud-contract-verifier/src/test/resources/kotlin/response.json new file mode 100644 index 0000000000..cb55e0cb2f --- /dev/null +++ b/spring-cloud-contract-verifier/src/test/resources/kotlin/response.json @@ -0,0 +1,3 @@ +{ + "status": "RESPONSE" +} diff --git a/spring-cloud-contract-verifier/src/test/resources/kotlin/response.pdf b/spring-cloud-contract-verifier/src/test/resources/kotlin/response.pdf new file mode 100644 index 0000000000..c3b233056b Binary files /dev/null and b/spring-cloud-contract-verifier/src/test/resources/kotlin/response.pdf differ