Added support for writing contracts in Java
fixes gh-1161
This commit is contained in:
@@ -6,20 +6,7 @@ Spring Cloud Contract supports the DSLs written in the following languages:
|
||||
|
||||
* Groovy
|
||||
* YAML
|
||||
|
||||
TIP: If you are not familiar with Groovy, do not worry - you can use Java syntax in the
|
||||
Groovy DSL files as well.
|
||||
|
||||
If you decide to write the contract in Groovy, do not be alarmed if you have not used Groovy
|
||||
before. Knowledge of the language is not really needed, as the Contract DSL uses only a
|
||||
tiny subset of it (only literals, method calls, and closures). Also, the DSL is statically
|
||||
typed, to make it programmer-readable without any knowledge of the DSL itself.
|
||||
|
||||
IMPORTANT: Remember that, inside the Groovy contract file, you have to provide the fully
|
||||
qualified name to the `Contract` class and `make` static imports, such as
|
||||
`org.springframework.cloud.spec.Contract.make { ... }`. You can also provide an import to
|
||||
the `Contract` class (`import org.springframework.cloud.spec.Contract`) and then call
|
||||
`Contract.make { ... }`.
|
||||
* Java
|
||||
|
||||
TIP: Spring Cloud Contract supports defining multiple contracts in a single file.
|
||||
|
||||
@@ -37,8 +24,13 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest.java[tags=class,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -49,6 +41,54 @@ mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-groovy]]
|
||||
=== Contract DSL in Groovy
|
||||
|
||||
If you are not familiar with Groovy, do not worry - you can use Java syntax in the
|
||||
Groovy DSL files as well.
|
||||
|
||||
If you decide to write the contract in Groovy, do not be alarmed if you have not used Groovy
|
||||
before. Knowledge of the language is not really needed, as the Contract DSL uses only a
|
||||
tiny subset of it (only literals, method calls, and closures). Also, the DSL is statically
|
||||
typed, to make it programmer-readable without any knowledge of the DSL itself.
|
||||
|
||||
IMPORTANT: Remember that, inside the Groovy contract file, you have to provide the fully
|
||||
qualified name to the `Contract` class and `make` static imports, such as
|
||||
`org.springframework.cloud.spec.Contract.make { ... }`. You can also provide an import to
|
||||
the `Contract` class (`import org.springframework.cloud.spec.Contract`) and then call
|
||||
`Contract.make { ... }`.
|
||||
|
||||
[[contract-java]]
|
||||
=== Contract DSL in Java
|
||||
|
||||
To write a contract definition in Java, you need to create a class, that implements either the `Supplier<Contract>` interface for a single contract or `Supplier<Collection<Contract>>` for multiple contracts.
|
||||
|
||||
You can also write the contract definitions under `src/test/java` (e.g. `src/test/java/contracts`) so that you don't have to modify the classpath of your project. In this case you'll have to provide a new location of contract definitions to your Spring Cloud Contract plugin.
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.Maven
|
||||
----
|
||||
<plugin>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<contractsDirectory>src/test/java/contracts</contractsDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Gradle
|
||||
----
|
||||
contracts {
|
||||
contractsDslDir = new File(project.rootDir, "src/test/java/contracts")
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-limitations]]
|
||||
=== Limitations
|
||||
|
||||
@@ -90,6 +130,12 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=description,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-dsl-name]]
|
||||
@@ -119,6 +165,12 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=name,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=name,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-dsl-ignoring-contracts]]
|
||||
@@ -140,6 +192,12 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=ignored,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[tags=ignored,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-dsl-in-progress]]
|
||||
@@ -164,6 +222,12 @@ include::{contract_spec_tests_path}/src/test/groovy/org/springframework/cloud/co
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=in_progress,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_with_tags.java[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.
|
||||
@@ -199,6 +263,12 @@ include::{verifier_core_path}/src/test/resources/classpath/readFromFile.groovy[i
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_from_file.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_from_file.java[tags=class,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
Further assume that the JSON files is as follows:
|
||||
@@ -222,7 +292,7 @@ of a request or a response. The name of the file needs to be a file with locatio
|
||||
relative to the folder in which the contract lays.
|
||||
|
||||
If you need to pass the contents of a file in binary form,
|
||||
you can use the `fileAsBytes` method in Groovy DSL or a `bodyFromFileAsBytes` field in YAML.
|
||||
you can use the `fileAsBytes` method in the coded DSL or a `bodyFromFileAsBytes` field in YAML.
|
||||
|
||||
The following example shows how to pass the contents of binary files:
|
||||
|
||||
@@ -238,6 +308,12 @@ include::{verifier_core_path}/src/test/resources/body_builder/worksWithPdf.groov
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_pdf.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_rest_from_pdf.java[tags=class,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
IMPORTANT: You should use this approach whenever you want to work with binary payloads,
|
||||
@@ -280,6 +356,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,indent=0]
|
||||
...
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=http_dsl,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
IMPORTANT: If you want to make your contract have a higher priority,
|
||||
@@ -306,6 +388,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request_obligatory,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=request,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
You can specify an absolute rather than a relative `url`, but using `urlPath` is
|
||||
@@ -325,6 +413,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_rest_with_path.yml[tags=url_path,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=url,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
`request` may contain query parameters, as the following example (which uses `urlPath`) shows:
|
||||
@@ -343,6 +437,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i
|
||||
...
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=query_params,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=urlpath,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
`request` can contain additional request headers, as the following example shows:
|
||||
@@ -361,6 +461,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i
|
||||
...
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=headers,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=headers,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
`request` may contain additional request cookies, as the following example shows:
|
||||
@@ -379,6 +485,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i
|
||||
...
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=cookies,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=cookies,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
`request` may contain a request body, as the following example shows:
|
||||
@@ -397,6 +509,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,i
|
||||
...
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=body,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=body,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
`request` can contain multipart elements. To include multipart elements, use the
|
||||
@@ -414,11 +532,17 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_multipart.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_multipart.java[tags=class,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
In the preceding example, we define parameters in either of two ways:
|
||||
|
||||
.Groovy DSL
|
||||
.Coded DSL
|
||||
* Directly, by using the map notation, where the value can be a dynamic property (such as
|
||||
`formParameter: $(consumer(...), producer(...))`).
|
||||
* By using the `named(...)` method that lets you set a named parameter. A named parameter
|
||||
@@ -488,6 +612,12 @@ include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,
|
||||
...
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response_obligatory,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=response,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
Besides status, the response may contain headers, cookies, and a body, which are
|
||||
@@ -516,7 +646,7 @@ For YAML, you can use only the `matchers` section.
|
||||
[[contract-dsl-dynamic-properties-in-body]]
|
||||
==== Dynamic Properties inside the Body
|
||||
|
||||
IMPORTANT: This section is valid only for the Groovy DSL. Check out the
|
||||
IMPORTANT: This section is valid only for the Coded DSL (Groovy, Java etc.). Check out the
|
||||
<<contract-dsl-matchers>> section for YAML examples of a similar feature.
|
||||
|
||||
You can set the properties inside the body either with the `value` method or, if you use
|
||||
@@ -564,14 +694,23 @@ There are several additional <<contract-dsl-regex-limitations,known limitations>
|
||||
|
||||
The following example shows how to use regular expressions to write a request:
|
||||
|
||||
[source,groovy,indent=0]
|
||||
====
|
||||
[source,groovy,indent=0,role="primary"]
|
||||
.groovy
|
||||
----
|
||||
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=regex,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=regex,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
You can also provide only one side of the communication with a regular expression. If you
|
||||
do so, then the contract engine automatically provides the generated string that matches
|
||||
the provided regular expression. The following code shows an example:
|
||||
the provided regular expression. The following code shows an example for Groovy:
|
||||
|
||||
[source,groovy,indent=0]
|
||||
----
|
||||
@@ -589,7 +728,7 @@ use in your contracts, as the following example shows:
|
||||
include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java[tags=regexps,indent=0]
|
||||
----
|
||||
|
||||
In your contract, you can use it as follows:
|
||||
In your contract, you can use it as follows (example for the Groovy DSL):
|
||||
|
||||
[source,groovy,indent=0]
|
||||
----
|
||||
@@ -605,7 +744,7 @@ 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:
|
||||
The following example shows how you can reference those methods (example for Groovy DSL):
|
||||
|
||||
[source,groovy,indent=0]
|
||||
----
|
||||
@@ -637,20 +776,32 @@ optional parameters only for the following:
|
||||
|
||||
The following example shows how to provide optional parameters:
|
||||
|
||||
[source,groovy,indent=0]
|
||||
====
|
||||
[source,groovy,indent=0,role="primary"]
|
||||
.groovy
|
||||
----
|
||||
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=optionals,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
By wrapping a part of the body with the `optional()` method, you create a regular
|
||||
expression that must be present 0 or more times.
|
||||
|
||||
If you use Spock, the following test would be generated from the previous example:
|
||||
|
||||
====
|
||||
[source,groovy,indent=0]
|
||||
.groovy
|
||||
----
|
||||
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals_test,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
The following stub would also be generated:
|
||||
|
||||
@@ -669,11 +820,21 @@ You can define a method call that runs on the server side during the test. Such
|
||||
method can be added to the class defined as `baseClassForTests` in the configuration. The
|
||||
following code shows an example of the contract portion of the test case:
|
||||
|
||||
[source,groovy,indent=0]
|
||||
|
||||
====
|
||||
[source,groovy,indent=0,role="primary"]
|
||||
.groovy
|
||||
----
|
||||
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=method,indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[tags=method,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
The following code shows the base class portion of the test case:
|
||||
|
||||
[source,groovy,indent=0]
|
||||
@@ -747,7 +908,7 @@ given name.
|
||||
* `fromRequest().body(String jsonPath)`: Returns the element from the request that
|
||||
matches the JSON Path.
|
||||
|
||||
If you use the YAML contract definition, you have to use the
|
||||
If you use the YAML contract definition or the Java one, you have to use the
|
||||
https://handlebarsjs.com/[Handlebars] `{{{ }}}` notation with custom Spring Cloud Contract
|
||||
functions to achieve this. In that case, you can use the following options:
|
||||
|
||||
@@ -777,6 +938,46 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_reference_request.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
package contracts.beer.rest;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.map;
|
||||
|
||||
class shouldReturnStatsForAUser implements Supplier<Contract> {
|
||||
|
||||
@Override
|
||||
public Contract get() {
|
||||
return Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("POST");
|
||||
r.url("/stats");
|
||||
r.body(map().entry("name", r.anyAlphaUnicode()));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(map()
|
||||
.entry("text",
|
||||
"Dear {{{jsonPath request.body '$.name'}}} thanks for your interested in drinking beer")
|
||||
.entry("quantity", r.$(r.c(5), r.p(r.anyNumber()))));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Running a JUnit test generation leads to a test that resembles the following example:
|
||||
@@ -896,7 +1097,7 @@ contract.
|
||||
Currently, Spring Cloud Contract Verifier supports only JSON path-based matchers with the
|
||||
following matching possibilities:
|
||||
|
||||
===== Groovy DSL
|
||||
===== Coded DSL
|
||||
|
||||
* For the stubs (in tests on the consumer's side):
|
||||
** `byEquality()`: The value taken from the consumer's request in the provided JSON path must be
|
||||
@@ -1149,8 +1350,6 @@ As you can see, the assertion is malformed. Only the first element of the array
|
||||
asserted. In order to fix this, you should apply the assertion to the whole `$.events`
|
||||
collection and assert it with the `byCommand(...)` method.
|
||||
|
||||
|
||||
|
||||
[[contract-dsl-async]]
|
||||
=== Asynchronous Support
|
||||
|
||||
@@ -1181,6 +1380,27 @@ org.springframework.cloud.contract.spec.Contract.make {
|
||||
response:
|
||||
async: true
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
class contract implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
});
|
||||
c.response(r -> {
|
||||
r.async();
|
||||
// ...
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can also use the `fixedDelayMilliseconds` method or property to add delay to your stubs.
|
||||
@@ -1209,6 +1429,27 @@ org.springframework.cloud.contract.spec.Contract.make {
|
||||
response:
|
||||
fixedDelayMilliseconds: 1000
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
class contract implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
});
|
||||
c.response(r -> {
|
||||
r.fixedDelayMilliseconds(1000);
|
||||
// ...
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[contract-dsl-xml]]
|
||||
@@ -1235,6 +1476,12 @@ include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.yml
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/contractsToCompile/contract_xml.java[tags=class,indent=0]
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows an automatically generated test for XML in the response body:
|
||||
@@ -1286,6 +1533,28 @@ include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/mul
|
||||
----
|
||||
include::{verifier_core_path}/src/test/resources/yml/multiple_contracts.yml[indent=0]
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.java
|
||||
----
|
||||
class contract implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Arrays.asList(
|
||||
Contract.make(c -> {
|
||||
c.name("should post a user");
|
||||
// ...
|
||||
}), Contract.make(c -> {
|
||||
// ...
|
||||
}), Contract.make(c -> {
|
||||
// ...
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
In the preceding example, one contract has the `name` field and the other does not. This
|
||||
@@ -1395,9 +1664,5 @@ name of `scenario1` and the three following steps:
|
||||
. showCart, marked as `Step1` pointing to...
|
||||
. logout, marked as `Step2` (which closes the scenario).
|
||||
|
||||
You can find nore details about WireMock scenarios at
|
||||
https://wiremock.org/docs/stateful-behaviour/[https://wiremock.org/docs/stateful-behaviour/].
|
||||
|
||||
Spring Cloud Contract also generates tests with a guaranteed order of execution.
|
||||
// TODO: How can someone specify the order of execution in SC Contract?
|
||||
// That sentence is a great lead-in to more content, which then does not appear.
|
||||
You can find more details about WireMock scenarios at
|
||||
https://wiremock.org/docs/stateful-behaviour/[https://wiremock.org/docs/stateful-behaviour/].
|
||||
7
pom.xml
7
pom.xml
@@ -35,6 +35,7 @@
|
||||
<spring-cloud-netflix.version>2.2.0.BUILD-SNAPSHOT</spring-cloud-netflix.version>
|
||||
<spring-cloud-consul.version>2.2.0.BUILD-SNAPSHOT</spring-cloud-consul.version>
|
||||
<spring-cloud-commons.version>2.2.0.BUILD-SNAPSHOT</spring-cloud-commons.version>
|
||||
<spring-cloud-function.version>3.0.0.BUILD-SNAPSHOT</spring-cloud-function.version>
|
||||
<jopt-simple.version>5.0.4</jopt-simple.version>
|
||||
<cglib.version>3.2.9</cglib.version>
|
||||
<spock-spring.version>1.3-groovy-2.5</spock-spring.version>
|
||||
@@ -61,6 +62,7 @@
|
||||
<xerces.version>2.11.0</xerces.version>
|
||||
<jacoco-maven-plugin.version>0.8.4</jacoco-maven-plugin.version>
|
||||
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
|
||||
<InMemoryJavaCompiler.version>1.3.0</InMemoryJavaCompiler.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -88,6 +90,11 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-compiler</artifactId>
|
||||
<version>${spring-cloud-function.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-spring-boot-starter</artifactId>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 com.example.loan;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerPort;
|
||||
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// tag::autoconfigure_stubrunner[]
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
|
||||
@AutoConfigureStubRunner(ids = {
|
||||
"com.example:http-server-dsl:+:stubs:6565"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL)
|
||||
public class TestControllerTests {
|
||||
// end::autoconfigure_stubrunner[]
|
||||
|
||||
@StubRunnerPort("http-server-dsl")
|
||||
int port;
|
||||
|
||||
@Test
|
||||
public void should_work_with_java_dsl() {
|
||||
// when:
|
||||
ResponseEntity<String> response = new RestTemplate()
|
||||
.getForEntity("http://localhost:" + this.port + "/example", String.class);
|
||||
// then:
|
||||
assertThat(response.getStatusCodeValue()).isEqualTo(200);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -60,6 +61,10 @@ public class TestController {
|
||||
return new Test("ok");
|
||||
}
|
||||
|
||||
@GetMapping("/example")
|
||||
public void example() {
|
||||
}
|
||||
|
||||
@PutMapping(value = "/1", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public byte[] response(@RequestBody byte[] requestBody) {
|
||||
if (!Arrays.equals(this.request, requestBody)) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 com.example.fraud;
|
||||
|
||||
import io.restassured.module.mockmvc.RestAssuredMockMvc;
|
||||
import org.junit.Before;
|
||||
|
||||
public class TestBase {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
RestAssuredMockMvc.standaloneSetup(new TestController());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 test;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class MyHttpContract implements Supplier<Contract> {
|
||||
@Override
|
||||
public Contract get() {
|
||||
return Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method(r.GET());
|
||||
r.url("/example");
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<logger name="org.springframework.cloud" level="DEBUG"/>
|
||||
|
||||
<logger name="org.springframework.cloud.function" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
@@ -59,12 +59,13 @@ class RecursiveFilesConverter {
|
||||
}
|
||||
|
||||
void processFiles() {
|
||||
ContractFileScanner scanner = new ContractFileScannerBuilder().
|
||||
baseDir(props.contractsDslDir)
|
||||
.excluded(props.excludedFiles as Set)
|
||||
.ignored([] as Set).included([] as Set)
|
||||
.includeMatcher(props.includedContracts)
|
||||
.build()
|
||||
ContractFileScanner scanner = new ContractFileScannerBuilder()
|
||||
.baseDir(props.contractsDslDir)
|
||||
.excluded(props.excludedFiles as Set)
|
||||
.ignored([] as Set)
|
||||
.included([] as Set)
|
||||
.includeMatcher(props.includedContracts)
|
||||
.build()
|
||||
ListMultimap<Path, ContractMetadata> contracts = scanner.findContracts()
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found the following contracts $contracts")
|
||||
|
||||
@@ -177,10 +177,14 @@
|
||||
<artifactId>spring-boot-starter-jersey</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-compiler</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mdkt.compiler</groupId>
|
||||
<artifactId>InMemoryJavaCompiler</artifactId>
|
||||
<version>1.2</version>
|
||||
<version>${InMemoryJavaCompiler.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -242,6 +246,24 @@
|
||||
<testNGArtifactName>none:none</testNGArtifactName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>generate-test-sources</phase>
|
||||
<goals>
|
||||
<goal>add-test-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/test/resources/contractsToCompile</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -123,6 +123,9 @@ class TestGenerator {
|
||||
void generateTestClasses(final String basePackageName) {
|
||||
ListMultimap<Path, ContractMetadata> contracts = contractFileScanner.
|
||||
findContracts()
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Found the following contracts " + contracts.keySet())
|
||||
}
|
||||
Set<Map.Entry<Path,Collection<ContractMetadata>>> inProgress = contracts.asMap().entrySet()
|
||||
.findAll { Map.Entry<Path, Collection<ContractMetadata>> entry -> entry.value.any { it.anyInProgress() }}
|
||||
if (!inProgress.isEmpty() && configProperties.failOnInProgress) {
|
||||
|
||||
@@ -41,6 +41,7 @@ class ToYamlConverter {
|
||||
}
|
||||
|
||||
private static YamlContractConverter yamlContractConverter = new YamlContractConverter()
|
||||
|
||||
private static final List<ContractConverter> CONTRACT_CONVERTERS = converters()
|
||||
|
||||
protected static void doReplaceContractWithYaml(ContractConverter converter, File file) {
|
||||
@@ -105,27 +106,7 @@ class ToYamlConverter {
|
||||
List<ContractConverter> converters =
|
||||
SpringFactoriesLoader.loadFactories(ContractConverter, null)
|
||||
converters.add(YamlContractConverter.INSTANCE)
|
||||
converters.add(GroovyContractConverter.INSTANCE)
|
||||
converters.add(ContractVerifierDslConverter.INSTANCE)
|
||||
return converters
|
||||
}
|
||||
}
|
||||
|
||||
class GroovyContractConverter implements ContractConverter<Collection<Contract>> {
|
||||
|
||||
static final GroovyContractConverter INSTANCE = new GroovyContractConverter()
|
||||
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
return file.name.endsWith(".groovy")
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
return ContractVerifierDslConverter.convertAsCollection(file)
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertTo(Collection<Contract> contract) {
|
||||
return contract
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ class ContractFileScanner {
|
||||
|
||||
protected List<ContractConverter> convertersWithYml() {
|
||||
List<ContractConverter> converters = converters()
|
||||
converters.add(ContractVerifierDslConverter.INSTANCE)
|
||||
converters.add(YamlContractConverter.INSTANCE)
|
||||
return converters
|
||||
}
|
||||
@@ -213,22 +214,7 @@ class ContractFileScanner {
|
||||
}
|
||||
|
||||
private boolean isContractFile(File file) {
|
||||
return file.isFile() && getFilenameExtension(file.toString())?.equalsIgnoreCase("groovy")
|
||||
}
|
||||
|
||||
private static String getFilenameExtension(String path) {
|
||||
if (path == null) {
|
||||
return null
|
||||
}
|
||||
int extIndex = path.lastIndexOf('.')
|
||||
if (extIndex == -1) {
|
||||
return null
|
||||
}
|
||||
int folderIndex = path.lastIndexOf('/')
|
||||
if (folderIndex > extIndex) {
|
||||
return null
|
||||
}
|
||||
return path.substring(extIndex + 1)
|
||||
return file.isFile() && ContractVerifierDslConverter.INSTANCE.isAccepted(file)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,7 +85,7 @@ final class ContractScanner {
|
||||
contractDescriptors
|
||||
.addAll(ContractVerifierDslConverter
|
||||
.convertAsCollection(
|
||||
file.getParentFile(), file))
|
||||
file.getParentFile(), file).findAll { it })
|
||||
}
|
||||
else if (converter != null
|
||||
&& converter.isAccepted(file)) {
|
||||
@@ -120,6 +120,6 @@ final class ContractScanner {
|
||||
}
|
||||
|
||||
private static boolean isContractDescriptor(File file) {
|
||||
return file.isFile() && file.getName().endsWith(".groovy")
|
||||
return ContractVerifierDslConverter.INSTANCE.isAccepted(file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,23 @@
|
||||
|
||||
package org.springframework.cloud.contract.verifier.util
|
||||
|
||||
import java.lang.reflect.Constructor
|
||||
import java.util.function.Supplier
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.util.logging.Commons
|
||||
import org.codehaus.groovy.control.CompilerConfiguration
|
||||
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.ContractConverter
|
||||
import org.springframework.cloud.function.compiler.java.CompilationResult
|
||||
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler
|
||||
import org.springframework.util.StringUtils
|
||||
|
||||
/**
|
||||
* Converts a file or String into a {@link Contract}
|
||||
* Converts a String or a Groovy or Java file into a {@link Contract}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*
|
||||
@@ -33,7 +40,15 @@ import org.springframework.util.StringUtils
|
||||
*/
|
||||
@CompileStatic
|
||||
@Commons
|
||||
class ContractVerifierDslConverter {
|
||||
class ContractVerifierDslConverter implements ContractConverter<Collection<Contract>> {
|
||||
|
||||
public static final ContractVerifierDslConverter INSTANCE = new ContractVerifierDslConverter()
|
||||
|
||||
private static final Pattern PACKAGE_PATTERN = Pattern.compile(".+?package (.+?);.+?", Pattern.DOTALL)
|
||||
|
||||
private static final Pattern CLASS_PATTERN = Pattern.compile(".+?class (.+?)( |\\{).+?", Pattern.DOTALL)
|
||||
|
||||
private static final RuntimeJavaCompiler COMPILER = new RuntimeJavaCompiler()
|
||||
|
||||
/**
|
||||
* @deprecated - use {@link ContractVerifierDslConverter#convertAsCollection(java.io.File, java.lang.String)}
|
||||
@@ -80,7 +95,7 @@ class ContractVerifierDslConverter {
|
||||
ClassLoader classLoader = ContractVerifierDslConverter.getClassLoader()
|
||||
try {
|
||||
ClassLoader urlCl = updatedClassLoader(rootFolder, classLoader)
|
||||
Object object = groovyShell(urlCl, rootFolder).evaluate(dsl)
|
||||
Object object = toObject(urlCl, rootFolder, dsl)
|
||||
return listOfContracts(dsl, object)
|
||||
}
|
||||
catch (DslParseException e) {
|
||||
@@ -106,6 +121,65 @@ class ContractVerifierDslConverter {
|
||||
return new GroovyShell(ContractVerifierDslConverter.classLoader, new CompilerConfiguration(sourceEncoding: 'UTF-8'))
|
||||
}
|
||||
|
||||
private static Object toObject(ClassLoader cl, File rootFolder, File dsl) {
|
||||
if (isJava(dsl)) {
|
||||
try {
|
||||
return parseJavaFile(dsl)
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Exception occurred while trying to parse the file [" + dsl + "] as a contract. Will not parse it.", ex)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
return groovyShell(cl, rootFolder).evaluate(dsl)
|
||||
}
|
||||
|
||||
private static Object parseJavaFile(File dsl) {
|
||||
Constructor<?> constructor = classConstructor(dsl)
|
||||
Object newInstance = constructor.newInstance()
|
||||
if (!newInstance instanceof Supplier) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The class [" + dsl + "] is not instance of Supplier. Will not parse it as a contract")
|
||||
}
|
||||
return null
|
||||
}
|
||||
Supplier supplier = (Supplier) newInstance
|
||||
return supplier.get()
|
||||
}
|
||||
|
||||
private static Constructor<?> classConstructor(File dsl) {
|
||||
String classText = dsl.text
|
||||
String fqn = fqn(classText)
|
||||
CompilationResult compilationResult = COMPILER
|
||||
.compile(fqn, classText)
|
||||
if (!compilationResult.wasSuccessful()) {
|
||||
throw new IllegalStateException("Exceptions occurred while trying to compile the file " + compilationResult.compilationMessages)
|
||||
}
|
||||
Class<?> clazz = compilationResult.compiledClasses.find { it.name == fqn}
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor()
|
||||
constructor.setAccessible(true)
|
||||
return constructor
|
||||
}
|
||||
|
||||
private static boolean isJava(File dsl) {
|
||||
return dsl.name.endsWith(".java")
|
||||
}
|
||||
|
||||
private static String fqn(String classText) {
|
||||
Matcher packageMatcher = PACKAGE_PATTERN.matcher(classText)
|
||||
String fqn = "";
|
||||
if (packageMatcher.matches()) {
|
||||
fqn = packageMatcher.group(1) + "."
|
||||
}
|
||||
Matcher classMatcher = CLASS_PATTERN.matcher(classText)
|
||||
if (!classMatcher.matches()) {
|
||||
throw new IllegalAccessException("Can't parse the class name")
|
||||
}
|
||||
return fqn + classMatcher.group(1)
|
||||
}
|
||||
|
||||
private static GroovyShell groovyShell(ClassLoader cl, File rootFolder) {
|
||||
return new GroovyShell(cl,
|
||||
new CompilerConfiguration(sourceEncoding: 'UTF-8',
|
||||
@@ -123,7 +197,10 @@ class ContractVerifierDslConverter {
|
||||
}
|
||||
|
||||
private static Collection<Contract> listOfContracts(File file, Object object) {
|
||||
if (object instanceof Collection) {
|
||||
if (object == null) {
|
||||
return Collections.emptyList()
|
||||
}
|
||||
else if (isACollectionOfContracts(object)) {
|
||||
return withName(file, object as Collection<Contract>)
|
||||
}
|
||||
else if (!object instanceof Contract) {
|
||||
@@ -132,6 +209,10 @@ class ContractVerifierDslConverter {
|
||||
return withName(file, [object] as Collection<Contract>)
|
||||
}
|
||||
|
||||
private static boolean isACollectionOfContracts(object) {
|
||||
return object instanceof Collection && ((Collection) object).every { it instanceof Contract }
|
||||
}
|
||||
|
||||
private static Collection<Contract> withName(File file, Collection<Contract> contracts) {
|
||||
int counter = 0
|
||||
return contracts.collect {
|
||||
@@ -146,4 +227,19 @@ class ContractVerifierDslConverter {
|
||||
private static boolean contractNameEmpty(Contract it) {
|
||||
return it != null && StringUtils.isEmpty(it.name)
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAccepted(File file) {
|
||||
return file.name.endsWith(".groovy") || file.name.endsWith(".gvy") || file.name.endsWith(".java")
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertFrom(File file) {
|
||||
return convertAsCollection(file)
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Contract> convertTo(Collection<Contract> contract) {
|
||||
return contract
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
@@ -105,4 +109,95 @@ public final class ContractVerifierUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a builder for map
|
||||
*/
|
||||
public static ContractVerifierMap map() {
|
||||
return new ContractVerifierMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* A map with a fluent interface.
|
||||
*/
|
||||
public static class ContractVerifierMap implements Map<Object, Object> {
|
||||
|
||||
private final Map<Object, Object> delegate = new HashMap<>();
|
||||
|
||||
public ContractVerifierMap entry(String key, Object value) {
|
||||
put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return this.delegate.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.delegate.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return this.delegate.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
return this.delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return this.delegate.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Object, ?> m) {
|
||||
this.delegate.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Object> keySet() {
|
||||
return this.delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return this.delegate.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Object, Object>> entrySet() {
|
||||
return this.delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this.delegate.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.contract.verifier.builder
|
||||
|
||||
import org.junit.Rule
|
||||
import org.mdkt.compiler.CompilationException
|
||||
import spock.lang.Issue
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
@@ -237,10 +238,10 @@ class MockMvcMethodBodyBuilderWithMatchersSpec extends Specification implements
|
||||
try {
|
||||
SyntaxChecker.tryToCompileWithoutCompileStatic(methodBuilderName, test)
|
||||
}
|
||||
catch (ClassFormatError classFormatError) {
|
||||
String output = outputCapture.toString()
|
||||
assert output.contains('error: cannot find symbol')
|
||||
assert output.contains('assertThatValueIsANumber(parsedJson.read("$.duck"));')
|
||||
catch (CompilationException classFormatError) {
|
||||
String output = classFormatError.message
|
||||
assert output.contains('cannot find symbol')
|
||||
assert output.contains('assertThatValueIsANumber')
|
||||
}
|
||||
where:
|
||||
methodBuilderName | methodBuilder | rootElement
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.contract.verifier.converter
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract
|
||||
import org.springframework.cloud.contract.spec.internal.ExecutionProperty
|
||||
import org.springframework.cloud.contract.spec.internal.FromFileProperty
|
||||
import org.springframework.cloud.contract.spec.internal.RegexProperty
|
||||
import org.springframework.cloud.contract.spec.internal.Url
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter
|
||||
import org.springframework.cloud.contract.verifier.util.MapConverter
|
||||
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.COMMAND
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.NULL
|
||||
import static org.springframework.cloud.contract.spec.internal.MatchingType.REGEX
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class JavaContractConverterSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
URL javaRest = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest.java")
|
||||
@Shared
|
||||
File javaRestFile = new File(javaRest.toURI())
|
||||
|
||||
@Shared
|
||||
URL javaRestWithTags = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_with_tags.java")
|
||||
@Shared
|
||||
File javaRestWithTagsFile = new File(javaRestWithTags.toURI())
|
||||
|
||||
@Shared
|
||||
URL contractBody = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_from_file.java")
|
||||
@Shared
|
||||
File contractBodyFile = new File(contractBody.toURI())
|
||||
|
||||
@Shared
|
||||
URL contractBodyBytes = JavaContractConverterSpec.getResource("/contractsToCompile/contract_rest_from_pdf.java")
|
||||
@Shared
|
||||
File contractBodyBytesFile = new File(contractBodyBytes.toURI())
|
||||
|
||||
@Shared
|
||||
URL docs = JavaContractConverterSpec.getResource("/contractsToCompile/contract_docs_examples.java")
|
||||
@Shared
|
||||
File docsFile = new File(docs.toURI())
|
||||
|
||||
def "should convert Java DSL with REST to DSL for [#contractFile]"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractFile)
|
||||
then:
|
||||
Contract contract = contracts.first()
|
||||
contract.description == "Some description"
|
||||
contract.name == "some name"
|
||||
contract.priority == 8
|
||||
contract.ignored == true
|
||||
Url url = contract.request.url
|
||||
url.clientValue == "/foo"
|
||||
url.queryParameters.parameters[0].name == "a"
|
||||
url.queryParameters.parameters[0].serverValue == "b"
|
||||
url.queryParameters.parameters[1].name == "b"
|
||||
url.queryParameters.parameters[1].serverValue == "c"
|
||||
contract.request.method.clientValue == "PUT"
|
||||
contract.request.headers.entries.find {
|
||||
it.name == "foo" &&
|
||||
((RegexProperty) it.clientValue).pattern() == "bar" && it.serverValue == "bar"
|
||||
}
|
||||
contract.request.headers.entries.find {
|
||||
it.name == "fooReq" &&
|
||||
it.serverValue == "baz"
|
||||
}
|
||||
MapConverter.getStubSideValues(contract.request.body) == [foo: "bar"]
|
||||
contract.request.bodyMatchers.matchers[0].path() == '$.foo'
|
||||
contract.request.bodyMatchers.matchers[0].matchingType() == REGEX
|
||||
contract.request.bodyMatchers.matchers[0].value().pattern() == 'bar'
|
||||
and:
|
||||
contract.response.status.clientValue == 200
|
||||
contract.response.delay.clientValue == 1000
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "foo2" &&
|
||||
((RegexProperty) it.serverValue).pattern() == "bar" && it.clientValue == "bar"
|
||||
}
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "foo3" &&
|
||||
((ExecutionProperty) it.serverValue).insertValue('foo') == "andMeToo(foo)"
|
||||
}
|
||||
contract.response.headers.entries.find {
|
||||
it.name == "fooRes" &&
|
||||
it.clientValue == "baz"
|
||||
}
|
||||
MapConverter.getStubSideValues(contract.response.body) == [foo2: "bar", foo3: "baz", nullValue: null]
|
||||
contract.response.bodyMatchers.matchers[0].path() == '$.foo2'
|
||||
contract.response.bodyMatchers.matchers[0].matchingType() == REGEX
|
||||
contract.response.bodyMatchers.matchers[0].value().pattern() == 'bar'
|
||||
contract.response.bodyMatchers.matchers[1].path() == '$.foo3'
|
||||
contract.response.bodyMatchers.matchers[1].matchingType() == COMMAND
|
||||
contract.response.bodyMatchers.matchers[1].value() == new ExecutionProperty('executeMe($it)')
|
||||
contract.response.bodyMatchers.matchers[2].path() == '$.nullValue'
|
||||
contract.response.bodyMatchers.matchers[2].matchingType() == NULL
|
||||
contract.response.bodyMatchers.matchers[2].value() == null
|
||||
where:
|
||||
contractFile << [javaRestFile, javaRestWithTagsFile]
|
||||
}
|
||||
|
||||
def "should convert java with REST with body from file"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractBodyFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
Contract contract = contracts.first()
|
||||
new JsonSlurper().parseText(contract.request.body.clientValue.toString()) ==
|
||||
new JsonSlurper().parseText('''{ "hello" : "request" }''')
|
||||
and:
|
||||
new JsonSlurper().parseText(contract.response.body.clientValue.toString()) ==
|
||||
new JsonSlurper().parseText('''{ "hello" : "response" }''')
|
||||
}
|
||||
|
||||
def "should convert java with REST with body as bytes"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), contractBodyBytesFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
Contract contract = contracts.first()
|
||||
contract.request.body.clientValue instanceof FromFileProperty
|
||||
and:
|
||||
contract.response.body.clientValue instanceof FromFileProperty
|
||||
}
|
||||
|
||||
def "should convert java with REST for docs"() {
|
||||
when:
|
||||
Collection<Contract> contracts = ContractVerifierDslConverter.convertAsCollection(new File("/"), docsFile)
|
||||
then:
|
||||
contracts.size() == 1
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,10 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
|
||||
URL single = ContractVerifierDslConverterSpec.getResource("/contract.groovy")
|
||||
File singleContract = new File(single.toURI())
|
||||
URL singleJava = ContractVerifierDslConverterSpec.getResource("/contractsToCompile/contract.java")
|
||||
File singleContractJava = new File(singleJava.toURI())
|
||||
URL singleRestJava = ContractVerifierDslConverterSpec.getResource("/contractsToCompile/contract_rest.java")
|
||||
File singleContractRestJava = new File(singleRestJava.toURI())
|
||||
URL multiple = ContractVerifierDslConverterSpec.getResource("/multiple_contracts.groovy")
|
||||
File multipleContracts = new File(multiple.toURI())
|
||||
URL invalid = ContractVerifierDslConverterSpec.getResource("/contract.yml")
|
||||
@@ -51,6 +55,25 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
}
|
||||
}
|
||||
|
||||
Contract expectedSingleContractForJava = Contract.make {
|
||||
name("contract")
|
||||
request {
|
||||
method('PUT')
|
||||
headers {
|
||||
contentType(applicationJson())
|
||||
}
|
||||
body(""" { "status" : "OK" } """)
|
||||
url("/1")
|
||||
}
|
||||
response {
|
||||
status OK()
|
||||
body(""" { "status" : "OK" } """)
|
||||
headers {
|
||||
contentType(textPlain())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Contract expectedSingleContractForText = Contract.make {
|
||||
request {
|
||||
method('PUT')
|
||||
@@ -154,6 +177,20 @@ class ContractVerifierDslConverterSpec extends Specification {
|
||||
contract == [expectedSingleContract]
|
||||
}
|
||||
|
||||
def "should convert file to a list of Contracts when there's only one declared java contract"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContractJava)
|
||||
then:
|
||||
contract == [expectedSingleContract]
|
||||
}
|
||||
|
||||
def "should convert file to a list of Contracts for a REST contract for docs"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContractRestJava)
|
||||
then:
|
||||
contract == [expectedSingleContractForJava]
|
||||
}
|
||||
|
||||
def "should convert text to a list of Contracts when there's only one declared contract"() {
|
||||
when:
|
||||
Collection<Contract> contract = ContractVerifierDslConverter.convertAsCollection(new File("/"), singleContract.text)
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.springframework.cloud.contract.verifier.messaging.internal.ContractVe
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierMessaging
|
||||
import org.springframework.cloud.contract.verifier.messaging.internal.ContractVerifierObjectMapper
|
||||
import org.springframework.cloud.contract.verifier.messaging.util.ContractVerifierMessagingUtil
|
||||
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler
|
||||
import org.springframework.util.ReflectionUtils
|
||||
/**
|
||||
* checking the syntax of produced scripts
|
||||
@@ -53,7 +54,7 @@ import org.springframework.util.ReflectionUtils
|
||||
@Commons
|
||||
class SyntaxChecker {
|
||||
|
||||
Entity entity
|
||||
public static final RuntimeJavaCompiler COMPILER = new RuntimeJavaCompiler()
|
||||
|
||||
private static final String[] DEFAULT_IMPORTS = [
|
||||
Contract.name,
|
||||
@@ -179,20 +180,26 @@ private void test(String test) {
|
||||
String fqnClassName = "com.example.${className}"
|
||||
test = test.replaceAll("class FooTest", "class " + className)
|
||||
.replaceAll("import javax.ws.rs.core.Response", "import javax.ws.rs.core.Response; import javax.ws.rs.client.WebTarget;")
|
||||
return InMemoryJavaCompiler.compile(fqnClassName, test)
|
||||
return compileJava(fqnClassName, test)
|
||||
|
||||
}
|
||||
|
||||
private static Class<?> compileJava(String fqnClassName, String test) {
|
||||
return InMemoryJavaCompiler.newInstance()
|
||||
.ignoreWarnings()
|
||||
.compile(fqnClassName, test)
|
||||
}
|
||||
|
||||
private static String className(String test) {
|
||||
Random random = new Random()
|
||||
int first = Math.abs(random.nextInt())
|
||||
int hashCode = Math.abs(test.hashCode())
|
||||
StringBuffer sourceCode = new StringBuffer()
|
||||
String className = "TestClass_${first}_${hashCode}"
|
||||
return className
|
||||
}
|
||||
|
||||
static boolean tryToCompileJavaWithoutImports(String fqn, String test) {
|
||||
InMemoryJavaCompiler.compile(fqn, test)
|
||||
compileJava(fqn, test)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 example;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
r.body(" { \"status\" : \"OK\" } ");
|
||||
r.url("/1");
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(" { \"status\" : \"OK\" } ");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.textPlain());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_docs_examples implements Supplier<Collection<Contract>> {
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract httpDsl =
|
||||
// tag::http_dsl[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
// Definition of HTTP request part of the contract
|
||||
// (this can be a valid request or invalid depending
|
||||
// on type of contract being specified).
|
||||
c.request(r -> {
|
||||
r.method(r.GET());
|
||||
r.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).
|
||||
c.response(r -> {
|
||||
r.status(200);
|
||||
// ...
|
||||
});
|
||||
|
||||
// Contract priority, which can be used for overriding
|
||||
// contracts (1 is highest). Priority is optional.
|
||||
c.priority(1);
|
||||
});
|
||||
|
||||
// end::http_dsl[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract methodDsl = org.springframework.cloud.contract.spec.Contract
|
||||
.make(c -> {
|
||||
c.request(r -> {
|
||||
// tag::method[]
|
||||
r.method(r.GET());
|
||||
// end::method[]
|
||||
r.url("/foo");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
r.status(200);
|
||||
});
|
||||
|
||||
c.priority(1);
|
||||
});
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract request =
|
||||
// tag::request[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// HTTP request method (GET/POST/PUT/DELETE).
|
||||
r.method("GET");
|
||||
|
||||
// Path component of request URL is specified as follows.
|
||||
r.urlPath("/users");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::request[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract url =
|
||||
// tag::url[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("GET");
|
||||
|
||||
// Specifying `url` and `urlPath` in one contract is illegal.
|
||||
r.url("http://localhost:8888/users");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::url[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract urlPaths =
|
||||
// tag::urlpath[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
|
||||
r.urlPath("/users", u -> {
|
||||
|
||||
// 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.
|
||||
u.queryParameters(q -> {
|
||||
|
||||
// If a simple literal is used as value
|
||||
// default matcher function is used (equalTo)
|
||||
q.parameter("limit", 100);
|
||||
|
||||
// `equalTo` function simply compares passed value
|
||||
// using identity operator (==).
|
||||
q.parameter("filter", r.equalTo("email"));
|
||||
|
||||
// `containing` function matches strings
|
||||
// that contains passed substring.
|
||||
q.parameter("gender", r.value(
|
||||
r.consumer(r.containing("[mf]")), r.producer("mf")));
|
||||
|
||||
// `matching` function tests parameter
|
||||
// against passed regular expression.
|
||||
q.parameter("offset", r.value(
|
||||
r.consumer(r.matching("[0-9]+")), r.producer(123)));
|
||||
|
||||
// `notMatching` functions tests if parameter
|
||||
// does not match passed regular expression.
|
||||
q.parameter("loginStartsWith", r.value(
|
||||
r.consumer(r.notMatching(".{0,2}")), r.producer(3)));
|
||||
});
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::urlpath[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract headers =
|
||||
// tag::headers[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Each header is added in form `'Header-Name' : 'Header-Value'`.
|
||||
// there are also some helper methods
|
||||
r.headers(h -> {
|
||||
h.header("key", "value");
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::headers[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract cookies =
|
||||
// tag::cookies[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Each Cookies is added in form `'Cookie-Key' : 'Cookie-Value'`.
|
||||
// there are also some helper methods
|
||||
r.cookies(ck -> {
|
||||
ck.cookie("key", "value");
|
||||
ck.cookie("another_key", "another_value");
|
||||
});
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::cookies[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract body =
|
||||
// tag::body[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// Currently only JSON format of request body is supported.
|
||||
// Format will be determined from a header or body's content.
|
||||
r.body("{ \"login\" : \"john\", \"name\": \"John The Contract\" }");
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::body[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract bodyAsXml =
|
||||
// tag::bodyAsXml[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
|
||||
// In this case body will be formatted as XML.
|
||||
r.body(r.equalToXml(
|
||||
"<user><login>john</login><name>John The Contract</name></user>"));
|
||||
});
|
||||
|
||||
c.response(r -> {
|
||||
// ...
|
||||
r.status(200);
|
||||
});
|
||||
});
|
||||
|
||||
// end::bodyAsXml[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract response =
|
||||
// tag::response[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
// ...
|
||||
r.method(r.GET());
|
||||
r.url("/foo");
|
||||
});
|
||||
c.response(r -> {
|
||||
// Status code sent by the server
|
||||
// in response to request specified above.
|
||||
r.status(r.OK());
|
||||
});
|
||||
});
|
||||
|
||||
// end::response[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract regex =
|
||||
// tag::regex[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("GET");
|
||||
r.url(r.$(r.consumer(r.regex("\\/[0-9]{2}")), r.producer("/12")));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(ContractVerifierUtil.map().entry("id", r.$(r.anyNumber()))
|
||||
.entry("surname", r.$(r.consumer("Kowalsky"),
|
||||
r.producer(r.regex("[a-zA-Z]+")))));
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "text/plain");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// end::regex[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract optionals =
|
||||
// tag::optionals[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.priority(1);
|
||||
c.name("optionals");
|
||||
c.request(r -> {
|
||||
r.method("POST");
|
||||
r.url("/users/password");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationJson());
|
||||
});
|
||||
r.body(ContractVerifierUtil.map()
|
||||
.entry("email",
|
||||
r.$(r.consumer(r.optional(r.regex(r.email()))),
|
||||
r.producer("abc@abc.com")))
|
||||
.entry("callback_url", r.$(r.consumer(r.regex(r.hostname())),
|
||||
r.producer("https://partners.com"))));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(404);
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "application/json");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("code", r.value(
|
||||
r.consumer("123123"), r.producer(r.optional("123123")))));
|
||||
});
|
||||
});
|
||||
|
||||
// end::optionals[]
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract method =
|
||||
// tag::methodBuilder[]
|
||||
org.springframework.cloud.contract.spec.Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("PUT");
|
||||
r.url(r.$(r.consumer(r.regex("^/api/[0-9]{2}$")),
|
||||
r.producer("/api/12")));
|
||||
r.headers(h -> {
|
||||
h.header("Content-Type", "application/json");
|
||||
});
|
||||
r.body("[{\"text\": \"Gonna see you at Warsaw\" }]");
|
||||
});
|
||||
c.response(r -> {
|
||||
r.body(ContractVerifierUtil.map()
|
||||
.entry("path",
|
||||
r.$(r.consumer("/api/12"),
|
||||
r.producer(r.regex("^/api/[0-9]{2}$"))))
|
||||
.entry("correlationId", r.$(r.consumer("1223456"), r
|
||||
.producer(r.execute("isProperCorrelationId($it)")))));
|
||||
r.status(r.OK());
|
||||
});
|
||||
});
|
||||
|
||||
// end::methodBuilder[]
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(method);
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.spec.internal.DslProperty;
|
||||
import org.springframework.cloud.contract.spec.internal.Request;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_multipart implements Supplier<Collection<Contract>> {
|
||||
|
||||
private static Map<String, DslProperty> namedProps(Request r) {
|
||||
Map<String, DslProperty> map = new HashMap<>();
|
||||
// name of the file
|
||||
map.put("name", r.$(r.c(r.regex(r.nonEmpty())), r.p("filename.csv")));
|
||||
// content of the file
|
||||
map.put("content", r.$(r.c(r.regex(r.nonEmpty())), r.p("file content")));
|
||||
// content type for the part
|
||||
map.put("contentType", r.$(r.c(r.regex(r.nonEmpty())), r.p("application/json")));
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method("PUT");
|
||||
r.url("/multipart");
|
||||
r.headers(h -> {
|
||||
h.contentType("multipart/form-data;boundary=AaB03x");
|
||||
});
|
||||
r.multipart(ContractVerifierUtil.map()
|
||||
// key (parameter name), value (parameter value) pair
|
||||
.entry("formParameter",
|
||||
r.$(r.c(r.regex("\".+\"")),
|
||||
r.p("\"formParameterValue\"")))
|
||||
.entry("someBooleanParameter",
|
||||
r.$(r.c(r.regex(r.anyBoolean())), r.p("true")))
|
||||
// a named parameter (e.g. with `file` name) that represents file
|
||||
// with
|
||||
// `name` and `content`. You can also call `named("fileName",
|
||||
// "fileContent")`
|
||||
.entry("file", r.named(namedProps(r))));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_rest implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
c.name("some name");
|
||||
c.priority(8);
|
||||
c.ignored();
|
||||
c.request(r -> {
|
||||
r.url("/foo", u -> {
|
||||
u.queryParameters(q -> {
|
||||
q.parameter("a", "b");
|
||||
q.parameter("b", "c");
|
||||
});
|
||||
});
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.header("foo", r.value(r.client(r.regex("bar")), r.server("bar")));
|
||||
h.header("fooReq", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo", "bar"));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo", m.byRegex("bar"));
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.fixedDelayMilliseconds(1000);
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.header("foo2", r.value(r.server(r.regex("bar")), r.client("bar")));
|
||||
h.header("foo3", r.value(r.server(r.execute("andMeToo($it)")),
|
||||
r.client("foo33")));
|
||||
h.header("fooRes", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo2", "bar")
|
||||
.entry("foo3", "baz").entry("nullValue", null));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo2", m.byRegex("bar"));
|
||||
m.jsonPath("$.foo3", m.byCommand("executeMe($it)"));
|
||||
m.jsonPath("$.nullValue", m.byNull());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_rest_from_file implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.url("/foo");
|
||||
r.method(r.GET());
|
||||
r.body(r.file("request.json"));
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(r.file("response.json"));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -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.
|
||||
*/
|
||||
//tag::class[]
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_rest_from_pdf implements Supplier<Collection<Contract>> {
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.url("/1");
|
||||
r.method(r.PUT());
|
||||
r.body(r.fileAsBytes("request.pdf"));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationOctetStream());
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.body(r.fileAsBytes("response.pdf"));
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationOctetStream());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil;
|
||||
|
||||
class contract_rest_with_tags implements Supplier<Collection<Contract>> {
|
||||
|
||||
static Object description = Collections.singletonList(
|
||||
// tag::description[]
|
||||
Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
}));
|
||||
|
||||
// end::description[]
|
||||
|
||||
static Object name = Collections.singletonList(
|
||||
// tag::name[]
|
||||
Contract.make(c -> {
|
||||
c.name("some name");
|
||||
}));
|
||||
|
||||
// end::name[]
|
||||
|
||||
static Object ignored = Collections.singletonList(
|
||||
// tag::ignored[]
|
||||
Contract.make(c -> {
|
||||
c.ignored();
|
||||
}));
|
||||
|
||||
// end::ignored[]
|
||||
|
||||
static Object in_progress = Collections.singletonList(
|
||||
// tag::in_progress[]
|
||||
Contract.make(c -> {
|
||||
c.inProgress();
|
||||
}));
|
||||
|
||||
// end::in_progress[]
|
||||
|
||||
@Override
|
||||
public Collection<Contract> get() {
|
||||
return Collections.singletonList(Contract.make(c -> {
|
||||
c.description("Some description");
|
||||
c.name("some name");
|
||||
c.priority(8);
|
||||
c.ignored();
|
||||
c.inProgress();
|
||||
c.request(r -> {
|
||||
r.url("/foo", u -> {
|
||||
u.queryParameters(q -> {
|
||||
q.parameter("a", "b");
|
||||
q.parameter("b", "c");
|
||||
});
|
||||
});
|
||||
r.method(r.PUT());
|
||||
r.headers(h -> {
|
||||
h.header("foo", r.value(r.client(r.regex("bar")), r.server("bar")));
|
||||
h.header("fooReq", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo", "bar"));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo", m.byRegex("bar"));
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.fixedDelayMilliseconds(1000);
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.header("foo2", r.value(r.server(r.regex("bar")), r.client("bar")));
|
||||
h.header("foo3", r.value(r.server(r.execute("andMeToo($it)")),
|
||||
r.client("foo33")));
|
||||
h.header("fooRes", "baz");
|
||||
});
|
||||
r.body(ContractVerifierUtil.map().entry("foo2", "bar")
|
||||
.entry("foo3", "baz").entry("nullValue", null));
|
||||
r.bodyMatchers(m -> {
|
||||
m.jsonPath("$.foo2", m.byRegex("bar"));
|
||||
m.jsonPath("$.foo3", m.byCommand("executeMe($it)"));
|
||||
m.jsonPath("$.nullValue", m.byNull());
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
// end::class[]
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.contract.spec.Contract;
|
||||
|
||||
class contract_xml implements Supplier<Contract> {
|
||||
|
||||
@Override
|
||||
public Contract get() {
|
||||
return Contract.make(c -> {
|
||||
c.request(r -> {
|
||||
r.method(r.GET());
|
||||
r.urlPath("/get");
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationXml());
|
||||
});
|
||||
});
|
||||
c.response(r -> {
|
||||
r.status(r.OK());
|
||||
r.headers(h -> {
|
||||
h.contentType(h.applicationXml());
|
||||
});
|
||||
r.body("<test>\n" + "<duck type='xtype'>123</duck>\n"
|
||||
+ "<alpha>abc</alpha>\n" + "<list>\n" + "<elem>abc</elem>\n"
|
||||
+ "<elem>def</elem>\n" + "<elem>ghi</elem>\n" + "</list>\n"
|
||||
+ "<number>123</number>\n" + "<aBoolean>true</aBoolean>\n"
|
||||
+ "<date>2017-01-01</date>\n"
|
||||
+ "<dateTime>2017-01-01T01:23:45</dateTime>\n"
|
||||
+ "<time>01:02:34</time>\n"
|
||||
+ "<valueWithoutAMatcher>foo</valueWithoutAMatcher>\n"
|
||||
+ "<key><complex>foo</complex></key>\n" + "</test>");
|
||||
r.bodyMatchers(m -> {
|
||||
m.xPath("/test/duck/text()", m.byRegex("[0-9]{3}"));
|
||||
m.xPath("/test/duck/text()", m.byCommand("equals($it)"));
|
||||
m.xPath("/test/duck/xxx", m.byNull());
|
||||
m.xPath("/test/duck/text()", m.byEquality());
|
||||
m.xPath("/test/alpha/text()", m.byRegex(r.onlyAlphaUnicode()));
|
||||
m.xPath("/test/alpha/text()", m.byEquality());
|
||||
m.xPath("/test/number/text()", m.byRegex(r.number()));
|
||||
m.xPath("/test/date/text()", m.byDate());
|
||||
m.xPath("/test/dateTime/text()", m.byTimestamp());
|
||||
m.xPath("/test/time/text()", m.byTime());
|
||||
m.xPath("/test/*/complex/text()", m.byEquality());
|
||||
m.xPath("/test/duck/@type", m.byEquality());
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "request"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "response"
|
||||
}
|
||||
Binary file not shown.
@@ -9,6 +9,8 @@
|
||||
|
||||
<logger name="com.github.jknack.handlebars.internal" level="INFO"/>
|
||||
|
||||
<logger name="org.springframework.cloud.function" level="INFO"/>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
Reference in New Issue
Block a user