Separated the Java Consumer es a standalone example example-consumer-java-siren
authorKai Moritz <kai@juplo.de>
Fri, 3 Jun 2022 14:59:32 +0000 (16:59 +0200)
committerKai Moritz <kai@juplo.de>
Fri, 3 Jun 2022 14:59:34 +0000 (16:59 +0200)
* The `README.md` is not yet reworked.

14 files changed:
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
pom.xml [new file with mode: 0644]
spring-consumer/LICENSE [deleted file]
spring-consumer/README.md [deleted file]
spring-consumer/pom.xml [deleted file]
spring-consumer/src/main/java/io/pactflow/example/sirenconsumer/Application.java [deleted file]
spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java [deleted file]
spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java [deleted file]
spring-consumer/target/pacts/SpringConsumer-SirenOrderProvider.json [deleted file]
src/main/java/io/pactflow/example/sirenconsumer/Application.java [new file with mode: 0644]
src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java [new file with mode: 0644]
src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java [new file with mode: 0644]
target/pacts/SpringConsumer-SirenOrderProvider.json [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..8f862e2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+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.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..b6eb530
--- /dev/null
+++ b/README.md
@@ -0,0 +1,263 @@
+# 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
+```
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..2927e83
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,65 @@
+<?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>
diff --git a/spring-consumer/LICENSE b/spring-consumer/LICENSE
deleted file mode 100644 (file)
index 8f862e2..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-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.
diff --git a/spring-consumer/README.md b/spring-consumer/README.md
deleted file mode 100644 (file)
index b6eb530..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-# 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
-```
diff --git a/spring-consumer/pom.xml b/spring-consumer/pom.xml
deleted file mode 100644 (file)
index 2927e83..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<?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>
diff --git a/spring-consumer/src/main/java/io/pactflow/example/sirenconsumer/Application.java b/spring-consumer/src/main/java/io/pactflow/example/sirenconsumer/Application.java
deleted file mode 100644 (file)
index fee9494..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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);
-       }
-
-}
diff --git a/spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java b/spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java
deleted file mode 100644 (file)
index d1ca158..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package io.pactflow.example.sirenconsumer;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class ApplicationTests
-{
-
-       @Test
-       void contextLoads() {
-       }
-
-}
diff --git a/spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java b/spring-consumer/src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java
deleted file mode 100644 (file)
index 16235cc..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-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);
-    }
-  }
-}
diff --git a/spring-consumer/target/pacts/SpringConsumer-SirenOrderProvider.json b/spring-consumer/target/pacts/SpringConsumer-SirenOrderProvider.json
deleted file mode 100644 (file)
index a8f8b03..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-{
-  "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"
-  }
-}
diff --git a/src/main/java/io/pactflow/example/sirenconsumer/Application.java b/src/main/java/io/pactflow/example/sirenconsumer/Application.java
new file mode 100644 (file)
index 0000000..fee9494
--- /dev/null
@@ -0,0 +1,14 @@
+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);
+       }
+
+}
diff --git a/src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java b/src/test/java/io/pactflow/example/sirenconsumer/ApplicationTests.java
new file mode 100644 (file)
index 0000000..d1ca158
--- /dev/null
@@ -0,0 +1,14 @@
+package io.pactflow.example.sirenconsumer;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ApplicationTests
+{
+
+       @Test
+       void contextLoads() {
+       }
+
+}
diff --git a/src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java b/src/test/java/io/pactflow/example/sirenconsumer/ContractTest.java
new file mode 100644 (file)
index 0000000..16235cc
--- /dev/null
@@ -0,0 +1,143 @@
+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);
+    }
+  }
+}
diff --git a/target/pacts/SpringConsumer-SirenOrderProvider.json b/target/pacts/SpringConsumer-SirenOrderProvider.json
new file mode 100644 (file)
index 0000000..a8f8b03
--- /dev/null
@@ -0,0 +1,260 @@
+{
+  "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"
+  }
+}