* The `README.md` is not yet reworked.
--- /dev/null
+MIT License
+
+Copyright (c) 2020 Pactflow
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+# Example Pact + Siren project
+Example project using [Siren](https://github.com/kevinswiber/siren) for hypermedia entities and testing with Pact.
+
+This project has two sub-projects, a provider springboot project which is using `spring-hateoas-siren` to provide Siren
+responses and a Javascript consumer project using `ketting` to parse and navigate the Siren responses.
+
+## Provider Project
+
+The provider project is a springboot application with Siren support provided by `spring-hateoas-siren`. It has two
+resources, a root resource which provides links to the other resources and an order resource for dealing with orders
+in the system.
+
+### Root Resource
+
+This just provides the links to the other resources.
+
+`GET /`:
+
+ ```json
+{
+ "class": [
+ "representation"
+ ],
+ "links": [
+ {
+ "rel": [
+ "orders"
+ ],
+ "href": "http://localhost:8080/orders"
+ }
+ ]
+}
+```
+
+### Order Resource
+
+This provides all the CRUD operations on Orders: fetch all orders, fetch an order by ID, update a resource or delete one.
+
+`GET /orders`
+
+```json
+{
+ "class": [
+ "entity"
+ ],
+ "entities": [
+ {
+ "class": [
+ "entity"
+ ],
+ "rel": [
+ "item"
+ ],
+ "properties": {
+ "id": 1234
+ },
+ "links": [
+ {
+ "rel": [
+ "self"
+ ],
+ "href": "http://localhost:8080/orders/1234"
+ }
+ ],
+ "actions": [
+ {
+ "name": "update",
+ "method": "PUT",
+ "href": "http://localhost:8080/orders/1234"
+ },
+ {
+ "name": "delete",
+ "method": "DELETE",
+ "href": "http://localhost:8080/orders/1234"
+ }
+ ]
+ }
+ ],
+ "links": [
+ {
+ "rel": [
+ "self"
+ ],
+ "href": "http://localhost:8080/orders"
+ }
+ ]
+}
+```
+
+## Consumer Project
+
+This is a simple Javascript application that uses [Ketting](https://github.com/badgateway/ketting) which is a
+hypermedia client for javascript. It has a single function in `consumer/src/consumer.js` that navigates the links from the provider to find the
+orders resource, get all the orders, find the first one and execute the delete action.
+
+The consumer does the following:
+
+1. Get the root resource
+2. Find the orders relation
+3. Execute a GET to the URL of the orders relation
+4. Extract the first order entity from the embedded entities
+5. Find the delete action for that order
+6. Execute the action (which executes a DELETE to the URL of the action)
+
+## Pact Tests
+
+The problem with using normal Pact tests to test this scenario is that Siren responses contain URLs to the resources and
+actions. The URLs when running the consumer test will be different than those when verifying the provider. This will
+result in a verification failure.
+
+To get round this problem, we use the `url` matcher function from the consumer Pact DSL. This function takes a list of
+path fragments. The path fragments can be either plain strings or regular expressions. It then constructs
+the actual URL to use in the consumer test using the mock servers base URL, and a regular expression matcher that can
+match the URLs in the provider verification test.
+
+### Dealing with hypermedia formats like Siren actions
+
+Siren takes hypermedia links one step further by introducing resource actions. These encode the URL, HTTP method and
+optionally any required parameters needed to make the requests for the actions supported by the resource.
+
+The problem could then arise that the consumer make only use a few actions provided by the provider. We would want to
+ensure that these actions are present in the list for the resource, and ignore the ones we are not using. The other issue
+is that our tests should not be dependent on the order of the actions.
+
+This is where the "array contains" matcher can help. It will allow us to match the resource actions for the ones we are
+using, and ignore the others. It will also not depend on the order the actions are returned.
+
+This is the actions for the order resource in the provider:
+
+```json
+{
+ "actions": [
+ {
+ "name": "update",
+ "method": "PUT",
+ "href": "http://localhost:8080/orders/6774860028109588394"
+ },
+ {
+ "name": "delete",
+ "method": "DELETE",
+ "href": "http://localhost:8080/orders/6774860028109588394"
+ },
+ {
+ "name": "changeStatus",
+ "method": "PUT",
+ "href": "http://localhost:8080/orders/6774860028109588394/status"
+ }
+ ]
+}
+```
+
+For example, in the consumer test we can specify:
+
+```js
+"actions": arrayContaining(
+ {
+ "name": "update",
+ "method": "PUT",
+ "href": url(["orders", regex("\\d+", "1234")])
+ },
+ {
+ "name": "delete",
+ "method": "DELETE",
+ "href": url(["orders", regex("\\d+", "1234")])
+ }
+)
+```
+
+This will match the actions if they contain the update and delete actions. it will ignore the other actions.
+
+You can see this in work if you remove one of the controller methods in the provider. For instance, if we commented out
+the delete endpoint, and then run the pact verification in the provider, we get this error:
+
+```console
+$ ./gradlew pactverify
+
+> Task :startServer
+
+ . ____ _ __ _ _
+ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/ ___)| |_)| | | | | || (_| | ) ) ) )
+ ' |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot :: (v2.3.4.RELEASE)
+
+2020-11-09 14:53:32.046 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : Starting SirenProviderApplication on ronald-P95xER with PID 39485 (/home/ronald/Development/Projects/Pact/example-siren/provider/build/libs/siren-provider-0.0.1.jar started by ronald in /home/ronald/Development/Projects/Pact/example-siren/provider)
+2020-11-09 14:53:32.048 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : No active profile set, falling back to default profiles: default
+2020-11-09 14:53:32.797 INFO 39485 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
+2020-11-09 14:53:32.808 INFO 39485 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
+2020-11-09 14:53:32.808 INFO 39485 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
+2020-11-09 14:53:32.870 INFO 39485 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
+2020-11-09 14:53:32.870 INFO 39485 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 759 ms
+2020-11-09 14:53:33.071 INFO 39485 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
+2020-11-09 14:53:33.221 INFO 39485 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
+2020-11-09 14:53:33.229 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : Started SirenProviderApplication in 1.53 seconds (JVM running for 1.903)
+java -jar /home/ronald/Development/Projects/Pact/example-siren/provider/build/libs/siren-provider-0.0.1.jar is ready.
+
+> Task :pactVerify_Siren_Order_Provider FAILED
+
+Verifying a pact between Siren Consumer and Siren Order Provider
+ [Using File /home/ronald/Development/Projects/Pact/example-siren/consumer/pacts/Siren Order Provider-Siren Order Service.json]
+ get root
+ returns a response which
+ has status code 200 (OK)
+ has a matching body (OK)
+ get all orders
+ returns a response which
+ has status code 200 (OK)
+ has a matching body (FAILED)
+ delete order
+ returns a response which
+ has status code 200 (FAILED)
+ has a matching body (OK)
+
+NOTE: Skipping publishing of verification results as it has been disabled (pact.verifier.publishResults is not 'true')
+
+
+Failures:
+
+1) Verifying a pact between Siren Consumer and Siren Order Provider - get all orders
+
+ 1.1) body: $.entities.0.actions Variant at index 1 ({"href":http://localhost:9000/orders/1234,"method":DELETE,"name":delete}) was not found in the actual list
+
+ [
+ {
+ - "href": "http://localhost:9000/orders/1234",
+ + "href": "http://localhost:8080/orders/7779028774458252624",
+ "method": "PUT",
+ "name": "update"
+ },
+ {
+ - "href": "http://localhost:9000/orders/1234",
+ - "method": "DELETE",
+ - "name": "delete"
+ + "href": "http://localhost:8080/orders/7779028774458252624/status",
+ + "method": "PUT",
+ + "name": "changeStatus"
+ }
+ ]
+
+
+ 1.2) status: expected status of 200 but was 405
+
+
+
+FAILURE: Build failed with an exception.
+
+* What went wrong:
+There were 2 non-pending pact failures for provider Siren Order Provider
+
+* Try:
+Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
+
+* Get more help at https://help.gradle.org
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
+Use '--warning-mode all' to show the individual deprecation warnings.
+See https://docs.gradle.org/6.6.1/userguide/command_line_interface.html#sec:command_line_warnings
+
+BUILD FAILED in 4s
+8 actionable tasks: 6 executed, 2 up-to-date
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.7.0</version>
+ <relativePath/> <!-- lookup parent from repository -->
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>io.pactflow.example.sirenconsumer.pact</groupId>
+ <artifactId>siren-consumer</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+
+ <properties>
+ <java.version>11</java.version>
+ <pact.version>4.2.2</pact.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>au.com.dius.pact.consumer</groupId>
+ <artifactId>junit5</artifactId>
+ <version>${pact.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
+++ /dev/null
-MIT License
-
-Copyright (c) 2020 Pactflow
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+++ /dev/null
-# Example Pact + Siren project
-Example project using [Siren](https://github.com/kevinswiber/siren) for hypermedia entities and testing with Pact.
-
-This project has two sub-projects, a provider springboot project which is using `spring-hateoas-siren` to provide Siren
-responses and a Javascript consumer project using `ketting` to parse and navigate the Siren responses.
-
-## Provider Project
-
-The provider project is a springboot application with Siren support provided by `spring-hateoas-siren`. It has two
-resources, a root resource which provides links to the other resources and an order resource for dealing with orders
-in the system.
-
-### Root Resource
-
-This just provides the links to the other resources.
-
-`GET /`:
-
- ```json
-{
- "class": [
- "representation"
- ],
- "links": [
- {
- "rel": [
- "orders"
- ],
- "href": "http://localhost:8080/orders"
- }
- ]
-}
-```
-
-### Order Resource
-
-This provides all the CRUD operations on Orders: fetch all orders, fetch an order by ID, update a resource or delete one.
-
-`GET /orders`
-
-```json
-{
- "class": [
- "entity"
- ],
- "entities": [
- {
- "class": [
- "entity"
- ],
- "rel": [
- "item"
- ],
- "properties": {
- "id": 1234
- },
- "links": [
- {
- "rel": [
- "self"
- ],
- "href": "http://localhost:8080/orders/1234"
- }
- ],
- "actions": [
- {
- "name": "update",
- "method": "PUT",
- "href": "http://localhost:8080/orders/1234"
- },
- {
- "name": "delete",
- "method": "DELETE",
- "href": "http://localhost:8080/orders/1234"
- }
- ]
- }
- ],
- "links": [
- {
- "rel": [
- "self"
- ],
- "href": "http://localhost:8080/orders"
- }
- ]
-}
-```
-
-## Consumer Project
-
-This is a simple Javascript application that uses [Ketting](https://github.com/badgateway/ketting) which is a
-hypermedia client for javascript. It has a single function in `consumer/src/consumer.js` that navigates the links from the provider to find the
-orders resource, get all the orders, find the first one and execute the delete action.
-
-The consumer does the following:
-
-1. Get the root resource
-2. Find the orders relation
-3. Execute a GET to the URL of the orders relation
-4. Extract the first order entity from the embedded entities
-5. Find the delete action for that order
-6. Execute the action (which executes a DELETE to the URL of the action)
-
-## Pact Tests
-
-The problem with using normal Pact tests to test this scenario is that Siren responses contain URLs to the resources and
-actions. The URLs when running the consumer test will be different than those when verifying the provider. This will
-result in a verification failure.
-
-To get round this problem, we use the `url` matcher function from the consumer Pact DSL. This function takes a list of
-path fragments. The path fragments can be either plain strings or regular expressions. It then constructs
-the actual URL to use in the consumer test using the mock servers base URL, and a regular expression matcher that can
-match the URLs in the provider verification test.
-
-### Dealing with hypermedia formats like Siren actions
-
-Siren takes hypermedia links one step further by introducing resource actions. These encode the URL, HTTP method and
-optionally any required parameters needed to make the requests for the actions supported by the resource.
-
-The problem could then arise that the consumer make only use a few actions provided by the provider. We would want to
-ensure that these actions are present in the list for the resource, and ignore the ones we are not using. The other issue
-is that our tests should not be dependent on the order of the actions.
-
-This is where the "array contains" matcher can help. It will allow us to match the resource actions for the ones we are
-using, and ignore the others. It will also not depend on the order the actions are returned.
-
-This is the actions for the order resource in the provider:
-
-```json
-{
- "actions": [
- {
- "name": "update",
- "method": "PUT",
- "href": "http://localhost:8080/orders/6774860028109588394"
- },
- {
- "name": "delete",
- "method": "DELETE",
- "href": "http://localhost:8080/orders/6774860028109588394"
- },
- {
- "name": "changeStatus",
- "method": "PUT",
- "href": "http://localhost:8080/orders/6774860028109588394/status"
- }
- ]
-}
-```
-
-For example, in the consumer test we can specify:
-
-```js
-"actions": arrayContaining(
- {
- "name": "update",
- "method": "PUT",
- "href": url(["orders", regex("\\d+", "1234")])
- },
- {
- "name": "delete",
- "method": "DELETE",
- "href": url(["orders", regex("\\d+", "1234")])
- }
-)
-```
-
-This will match the actions if they contain the update and delete actions. it will ignore the other actions.
-
-You can see this in work if you remove one of the controller methods in the provider. For instance, if we commented out
-the delete endpoint, and then run the pact verification in the provider, we get this error:
-
-```console
-$ ./gradlew pactverify
-
-> Task :startServer
-
- . ____ _ __ _ _
- /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
-( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
- \\/ ___)| |_)| | | | | || (_| | ) ) ) )
- ' |____| .__|_| |_|_| |_\__, | / / / /
- =========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v2.3.4.RELEASE)
-
-2020-11-09 14:53:32.046 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : Starting SirenProviderApplication on ronald-P95xER with PID 39485 (/home/ronald/Development/Projects/Pact/example-siren/provider/build/libs/siren-provider-0.0.1.jar started by ronald in /home/ronald/Development/Projects/Pact/example-siren/provider)
-2020-11-09 14:53:32.048 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : No active profile set, falling back to default profiles: default
-2020-11-09 14:53:32.797 INFO 39485 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
-2020-11-09 14:53:32.808 INFO 39485 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
-2020-11-09 14:53:32.808 INFO 39485 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.38]
-2020-11-09 14:53:32.870 INFO 39485 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
-2020-11-09 14:53:32.870 INFO 39485 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 759 ms
-2020-11-09 14:53:33.071 INFO 39485 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
-2020-11-09 14:53:33.221 INFO 39485 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
-2020-11-09 14:53:33.229 INFO 39485 --- [ main] i.p.e.s.SirenProviderApplication : Started SirenProviderApplication in 1.53 seconds (JVM running for 1.903)
-java -jar /home/ronald/Development/Projects/Pact/example-siren/provider/build/libs/siren-provider-0.0.1.jar is ready.
-
-> Task :pactVerify_Siren_Order_Provider FAILED
-
-Verifying a pact between Siren Consumer and Siren Order Provider
- [Using File /home/ronald/Development/Projects/Pact/example-siren/consumer/pacts/Siren Order Provider-Siren Order Service.json]
- get root
- returns a response which
- has status code 200 (OK)
- has a matching body (OK)
- get all orders
- returns a response which
- has status code 200 (OK)
- has a matching body (FAILED)
- delete order
- returns a response which
- has status code 200 (FAILED)
- has a matching body (OK)
-
-NOTE: Skipping publishing of verification results as it has been disabled (pact.verifier.publishResults is not 'true')
-
-
-Failures:
-
-1) Verifying a pact between Siren Consumer and Siren Order Provider - get all orders
-
- 1.1) body: $.entities.0.actions Variant at index 1 ({"href":http://localhost:9000/orders/1234,"method":DELETE,"name":delete}) was not found in the actual list
-
- [
- {
- - "href": "http://localhost:9000/orders/1234",
- + "href": "http://localhost:8080/orders/7779028774458252624",
- "method": "PUT",
- "name": "update"
- },
- {
- - "href": "http://localhost:9000/orders/1234",
- - "method": "DELETE",
- - "name": "delete"
- + "href": "http://localhost:8080/orders/7779028774458252624/status",
- + "method": "PUT",
- + "name": "changeStatus"
- }
- ]
-
-
- 1.2) status: expected status of 200 but was 405
-
-
-
-FAILURE: Build failed with an exception.
-
-* What went wrong:
-There were 2 non-pending pact failures for provider Siren Order Provider
-
-* Try:
-Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
-
-* Get more help at https://help.gradle.org
-
-Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
-Use '--warning-mode all' to show the individual deprecation warnings.
-See https://docs.gradle.org/6.6.1/userguide/command_line_interface.html#sec:command_line_warnings
-
-BUILD FAILED in 4s
-8 actionable tasks: 6 executed, 2 up-to-date
-```
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.7.0</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
-
- <modelVersion>4.0.0</modelVersion>
- <groupId>io.pactflow.example.sirenconsumer.pact</groupId>
- <artifactId>siren-consumer</artifactId>
- <version>1.0.0-SNAPSHOT</version>
-
- <properties>
- <java.version>11</java.version>
- <pact.version>4.2.2</pact.version>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>au.com.dius.pact.consumer</groupId>
- <artifactId>junit5</artifactId>
- <version>${pact.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </exclude>
- </excludes>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-failsafe-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
-</project>
+++ /dev/null
-package io.pactflow.example.sirenconsumer;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class Application
-{
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-
-}
+++ /dev/null
-package io.pactflow.example.sirenconsumer;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class ApplicationTests
-{
-
- @Test
- void contextLoads() {
- }
-
-}
+++ /dev/null
-package io.pactflow.example.sirenconsumer;
-
-import au.com.dius.pact.consumer.MockServer;
-import au.com.dius.pact.consumer.dsl.*;
-import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
-import au.com.dius.pact.consumer.junit5.PactTestFor;
-import au.com.dius.pact.core.model.RequestResponsePact;
-import au.com.dius.pact.core.model.annotations.Pact;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.fail;
-
-
-@ExtendWith(PactConsumerTestExt.class)
-@PactTestFor(providerName = "SirenOrderProvider")
-public class ContractTest
-{
- @Pact(consumer="SpringConsumer")
- public RequestResponsePact deletesTheFirstOrderUsingtheDeleteAction(PactDslWithProvider builder)
- {
- return builder
- .uponReceiving("get root")
- .path("/")
- .method("GET")
- .willRespondWith()
- .status(200)
- .headers(Map.of("Content-Type", "application/vnd.siren+json"))
- .body(LambdaDsl.newJsonBody(body ->
- {
- body.array("class", classArray ->
- {
- classArray.stringValue("representation");
- });
- body.array("links", linksArray ->
- {
- linksArray.object(object->
- {
- object.matchUrl2("href", "orders");
- object.array("rel", relArray ->
- {
- relArray.stringValue("orders");
- });
- });
- });
- }).build())
- .uponReceiving("get all orders")
- .path("/orders")
- .method("GET")
- .willRespondWith()
- .status(200)
- .headers(Map.of("Content-Type", "application/vnd.siren+json"))
- .body(LambdaDsl.newJsonBody(body ->
- {
- body.array("class", classArray ->
- {
- classArray.stringValue("entity");
- });
- body.eachLike("entities", entities ->
- {
- entities.arrayContaining("actions", actionsArray->
- {
- actionsArray.object(object ->
- {
- object.stringValue("name","update");
- object.stringValue("method", "PUT");
- object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
- });
- actionsArray.object(object ->
- {
- object.stringValue("name","delete");
- object.stringValue("method", "DELETE");
- object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
- });
- });
- entities.array("class", classArray ->
- {
- classArray.stringValue("entity");
- });
- entities.array("links", linksArray ->
- {
- linksArray.object(object->
- {
- object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
- object.array("rel", relArray ->
- {
- relArray.stringValue("self");
- });
- });
- });
- entities.object("properties", object->
- {
- object.integerType("id", 1234);
- });
- entities.array("rel", relArray ->
- {
- relArray.stringValue("item");
- });
- });
- body.array("links", linksArray ->
- {
- linksArray.object(object->
- {
- object.matchUrl2("href", "orders");
- object.array("rel", relArray ->
- {
- relArray.stringValue("self");
- });
- });
- });
- }).build())
- .uponReceiving("delete order")
- .matchPath("/orders/\\d+", "/orders/1234")
- .method("DELETE")
- .willRespondWith()
- .status(200)
- .toPact();
- }
-
- @Test
- @PactTestFor(pactMethod = "deletesTheFirstOrderUsingtheDeleteAction")
- public void testDeletesTheFirstOrderUsingtheDeleteAction(MockServer mockServer)
- {
- RestTemplate restTemplate =
- new RestTemplateBuilder()
- .rootUri(mockServer.getUrl())
- .build();
- try
- {
- restTemplate.getForEntity("/", String.class);
- restTemplate.getForEntity("/orders", String.class);
- restTemplate.delete("/orders/1234");
- }
- catch (Exception e)
- {
- fail("Unexpected exception", e);
- }
- }
-}
+++ /dev/null
-{
- "consumer": {
- "name": "SpringConsumer"
- },
- "interactions": [
- {
- "description": "delete order",
- "request": {
- "matchingRules": {
- "path": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": "/orders/\\d+"
- }
- ]
- }
- },
- "method": "DELETE",
- "path": "/orders/1234"
- },
- "response": {
- "status": 200
- }
- },
- {
- "description": "get all orders",
- "request": {
- "method": "GET",
- "path": "/orders"
- },
- "response": {
- "body": {
- "class": [
- "entity"
- ],
- "entities": [
- {
- "actions": [
- {
- "href": "http://localhost:8080/orders/1234",
- "method": "PUT",
- "name": "update"
- },
- {
- "href": "http://localhost:8080/orders/1234",
- "method": "DELETE",
- "name": "delete"
- }
- ],
- "class": [
- "entity"
- ],
- "links": [
- {
- "href": "http://localhost:8080/orders/1234",
- "rel": [
- "self"
- ]
- }
- ],
- "properties": {
- "id": 1234
- },
- "rel": [
- "item"
- ]
- }
- ],
- "links": [
- {
- "href": "http://localhost:8080/orders",
- "rel": [
- "self"
- ]
- }
- ]
- },
- "generators": {
- "body": {
- "$.entities[*].links[0].href": {
- "example": "http://localhost:8080/orders/1234",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
- "type": "MockServerURL"
- },
- "$.entities[*][0].href": {
- "example": "http://localhost:8080/orders/1234",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
- "type": "MockServerURL"
- },
- "$.entities[*][1].href": {
- "example": "http://localhost:8080/orders/1234",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
- "type": "MockServerURL"
- },
- "$.links[0].href": {
- "example": "http://localhost:8080/orders",
- "regex": ".*\\/(\\Qorders\\E)$",
- "type": "MockServerURL"
- }
- }
- },
- "headers": {
- "Content-Type": "application/vnd.siren+json"
- },
- "matchingRules": {
- "body": {
- "$.entities": {
- "combine": "AND",
- "matchers": [
- {
- "match": "type"
- }
- ]
- },
- "$.entities[*].actions": {
- "combine": "AND",
- "matchers": [
- {
- "match": "arrayContains",
- "variants": [
- {
- "generators": {
- "$.href": {
- "example": "http://localhost:8080/orders/1234",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
- "type": "MockServerURL"
- }
- },
- "index": 0,
- "rules": {
- "$.href": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
- }
- ]
- }
- }
- },
- {
- "generators": {
- "$.href": {
- "example": "http://localhost:8080/orders/1234",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
- "type": "MockServerURL"
- }
- },
- "index": 1,
- "rules": {
- "$.href": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
- }
- ]
- }
- }
- }
- ]
- }
- ]
- },
- "$.entities[*].links[0].href": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
- }
- ]
- },
- "$.entities[*].properties.id": {
- "combine": "AND",
- "matchers": [
- {
- "match": "integer"
- }
- ]
- },
- "$.links[0].href": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": ".*\\/(\\Qorders\\E)$"
- }
- ]
- }
- }
- },
- "status": 200
- }
- },
- {
- "description": "get root",
- "request": {
- "method": "GET",
- "path": "/"
- },
- "response": {
- "body": {
- "class": [
- "representation"
- ],
- "links": [
- {
- "href": "http://localhost:8080/orders",
- "rel": [
- "orders"
- ]
- }
- ]
- },
- "generators": {
- "body": {
- "$.links[0].href": {
- "example": "http://localhost:8080/orders",
- "regex": ".*\\/(\\Qorders\\E)$",
- "type": "MockServerURL"
- }
- }
- },
- "headers": {
- "Content-Type": "application/vnd.siren+json"
- },
- "matchingRules": {
- "body": {
- "$.links[0].href": {
- "combine": "AND",
- "matchers": [
- {
- "match": "regex",
- "regex": ".*\\/(\\Qorders\\E)$"
- }
- ]
- }
- }
- },
- "status": 200
- }
- }
- ],
- "metadata": {
- "pact-jvm": {
- "version": "4.2.2"
- },
- "pactSpecification": {
- "version": "3.0.0"
- }
- },
- "provider": {
- "name": "SirenOrderProvider"
- }
-}
--- /dev/null
+package io.pactflow.example.sirenconsumer;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application
+{
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
--- /dev/null
+package io.pactflow.example.sirenconsumer;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ApplicationTests
+{
+
+ @Test
+ void contextLoads() {
+ }
+
+}
--- /dev/null
+package io.pactflow.example.sirenconsumer;
+
+import au.com.dius.pact.consumer.MockServer;
+import au.com.dius.pact.consumer.dsl.*;
+import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
+import au.com.dius.pact.consumer.junit5.PactTestFor;
+import au.com.dius.pact.core.model.RequestResponsePact;
+import au.com.dius.pact.core.model.annotations.Pact;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.fail;
+
+
+@ExtendWith(PactConsumerTestExt.class)
+@PactTestFor(providerName = "SirenOrderProvider")
+public class ContractTest
+{
+ @Pact(consumer="SpringConsumer")
+ public RequestResponsePact deletesTheFirstOrderUsingtheDeleteAction(PactDslWithProvider builder)
+ {
+ return builder
+ .uponReceiving("get root")
+ .path("/")
+ .method("GET")
+ .willRespondWith()
+ .status(200)
+ .headers(Map.of("Content-Type", "application/vnd.siren+json"))
+ .body(LambdaDsl.newJsonBody(body ->
+ {
+ body.array("class", classArray ->
+ {
+ classArray.stringValue("representation");
+ });
+ body.array("links", linksArray ->
+ {
+ linksArray.object(object->
+ {
+ object.matchUrl2("href", "orders");
+ object.array("rel", relArray ->
+ {
+ relArray.stringValue("orders");
+ });
+ });
+ });
+ }).build())
+ .uponReceiving("get all orders")
+ .path("/orders")
+ .method("GET")
+ .willRespondWith()
+ .status(200)
+ .headers(Map.of("Content-Type", "application/vnd.siren+json"))
+ .body(LambdaDsl.newJsonBody(body ->
+ {
+ body.array("class", classArray ->
+ {
+ classArray.stringValue("entity");
+ });
+ body.eachLike("entities", entities ->
+ {
+ entities.arrayContaining("actions", actionsArray->
+ {
+ actionsArray.object(object ->
+ {
+ object.stringValue("name","update");
+ object.stringValue("method", "PUT");
+ object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
+ });
+ actionsArray.object(object ->
+ {
+ object.stringValue("name","delete");
+ object.stringValue("method", "DELETE");
+ object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
+ });
+ });
+ entities.array("class", classArray ->
+ {
+ classArray.stringValue("entity");
+ });
+ entities.array("links", linksArray ->
+ {
+ linksArray.object(object->
+ {
+ object.matchUrl2("href", "orders", Matchers.regexp("\\d+", "1234").getMatcher());
+ object.array("rel", relArray ->
+ {
+ relArray.stringValue("self");
+ });
+ });
+ });
+ entities.object("properties", object->
+ {
+ object.integerType("id", 1234);
+ });
+ entities.array("rel", relArray ->
+ {
+ relArray.stringValue("item");
+ });
+ });
+ body.array("links", linksArray ->
+ {
+ linksArray.object(object->
+ {
+ object.matchUrl2("href", "orders");
+ object.array("rel", relArray ->
+ {
+ relArray.stringValue("self");
+ });
+ });
+ });
+ }).build())
+ .uponReceiving("delete order")
+ .matchPath("/orders/\\d+", "/orders/1234")
+ .method("DELETE")
+ .willRespondWith()
+ .status(200)
+ .toPact();
+ }
+
+ @Test
+ @PactTestFor(pactMethod = "deletesTheFirstOrderUsingtheDeleteAction")
+ public void testDeletesTheFirstOrderUsingtheDeleteAction(MockServer mockServer)
+ {
+ RestTemplate restTemplate =
+ new RestTemplateBuilder()
+ .rootUri(mockServer.getUrl())
+ .build();
+ try
+ {
+ restTemplate.getForEntity("/", String.class);
+ restTemplate.getForEntity("/orders", String.class);
+ restTemplate.delete("/orders/1234");
+ }
+ catch (Exception e)
+ {
+ fail("Unexpected exception", e);
+ }
+ }
+}
--- /dev/null
+{
+ "consumer": {
+ "name": "SpringConsumer"
+ },
+ "interactions": [
+ {
+ "description": "delete order",
+ "request": {
+ "matchingRules": {
+ "path": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": "/orders/\\d+"
+ }
+ ]
+ }
+ },
+ "method": "DELETE",
+ "path": "/orders/1234"
+ },
+ "response": {
+ "status": 200
+ }
+ },
+ {
+ "description": "get all orders",
+ "request": {
+ "method": "GET",
+ "path": "/orders"
+ },
+ "response": {
+ "body": {
+ "class": [
+ "entity"
+ ],
+ "entities": [
+ {
+ "actions": [
+ {
+ "href": "http://localhost:8080/orders/1234",
+ "method": "PUT",
+ "name": "update"
+ },
+ {
+ "href": "http://localhost:8080/orders/1234",
+ "method": "DELETE",
+ "name": "delete"
+ }
+ ],
+ "class": [
+ "entity"
+ ],
+ "links": [
+ {
+ "href": "http://localhost:8080/orders/1234",
+ "rel": [
+ "self"
+ ]
+ }
+ ],
+ "properties": {
+ "id": 1234
+ },
+ "rel": [
+ "item"
+ ]
+ }
+ ],
+ "links": [
+ {
+ "href": "http://localhost:8080/orders",
+ "rel": [
+ "self"
+ ]
+ }
+ ]
+ },
+ "generators": {
+ "body": {
+ "$.entities[*].links[0].href": {
+ "example": "http://localhost:8080/orders/1234",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
+ "type": "MockServerURL"
+ },
+ "$.entities[*][0].href": {
+ "example": "http://localhost:8080/orders/1234",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
+ "type": "MockServerURL"
+ },
+ "$.entities[*][1].href": {
+ "example": "http://localhost:8080/orders/1234",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
+ "type": "MockServerURL"
+ },
+ "$.links[0].href": {
+ "example": "http://localhost:8080/orders",
+ "regex": ".*\\/(\\Qorders\\E)$",
+ "type": "MockServerURL"
+ }
+ }
+ },
+ "headers": {
+ "Content-Type": "application/vnd.siren+json"
+ },
+ "matchingRules": {
+ "body": {
+ "$.entities": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "type"
+ }
+ ]
+ },
+ "$.entities[*].actions": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "arrayContains",
+ "variants": [
+ {
+ "generators": {
+ "$.href": {
+ "example": "http://localhost:8080/orders/1234",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
+ "type": "MockServerURL"
+ }
+ },
+ "index": 0,
+ "rules": {
+ "$.href": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "generators": {
+ "$.href": {
+ "example": "http://localhost:8080/orders/1234",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$",
+ "type": "MockServerURL"
+ }
+ },
+ "index": 1,
+ "rules": {
+ "$.href": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "$.entities[*].links[0].href": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": ".*\\/(\\Qorders\\E\\/\\d+)$"
+ }
+ ]
+ },
+ "$.entities[*].properties.id": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "integer"
+ }
+ ]
+ },
+ "$.links[0].href": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": ".*\\/(\\Qorders\\E)$"
+ }
+ ]
+ }
+ }
+ },
+ "status": 200
+ }
+ },
+ {
+ "description": "get root",
+ "request": {
+ "method": "GET",
+ "path": "/"
+ },
+ "response": {
+ "body": {
+ "class": [
+ "representation"
+ ],
+ "links": [
+ {
+ "href": "http://localhost:8080/orders",
+ "rel": [
+ "orders"
+ ]
+ }
+ ]
+ },
+ "generators": {
+ "body": {
+ "$.links[0].href": {
+ "example": "http://localhost:8080/orders",
+ "regex": ".*\\/(\\Qorders\\E)$",
+ "type": "MockServerURL"
+ }
+ }
+ },
+ "headers": {
+ "Content-Type": "application/vnd.siren+json"
+ },
+ "matchingRules": {
+ "body": {
+ "$.links[0].href": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "regex",
+ "regex": ".*\\/(\\Qorders\\E)$"
+ }
+ ]
+ }
+ }
+ },
+ "status": 200
+ }
+ }
+ ],
+ "metadata": {
+ "pact-jvm": {
+ "version": "4.2.2"
+ },
+ "pactSpecification": {
+ "version": "3.0.0"
+ }
+ },
+ "provider": {
+ "name": "SirenOrderProvider"
+ }
+}