diff --git a/README.md b/README.md
index f74740a..3601b7c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
# WireMock State extension
+
+
+
+
+
+
Adds support to transport state across different stubs.
## Feature summary
@@ -701,6 +707,8 @@ The handler has the following parameters:
- `property='updateCount` retrieves the number of updates to a certain state.
The number matches the one described in [Context update count match](#context-update-count-match)
- `property='listSize` retrieves the number of entries of `list`
+ - `property='list` get the whole list as array, e.g. to use it with [handlebars #each](https://handlebarsjs.com/guide/builtin-helpers.html#each)
+ - this property always has a default value (empty list), which can be overwritten with a JSON list
- `list`: Getting an entry of the context's `list`, identified via a JSON path. Examples:
- getting the first state in the list: `list='[0].myProperty`
- getting the last state in the list: `list='[-1].myProperty`
@@ -709,11 +717,42 @@ The handler has the following parameters:
You have to choose either `property` or `list` (otherwise, you will get a configuration error).
-To retrieve a full body, use: `{{{state context=request.pathSegments.[1] property='fullBody'}}}` .
+To retrieve a full body, use tripple braces: `{{{state context=request.pathSegments.[1] property='fullBody'}}}` .
When registering this extension, this helper is available via WireMock's [response templating](https://wiremock.org/3.x/docs/response-templating/) as well as
in all configuration options of this extension.
+### List operations
+
+You can use [handlebars #each](https://handlebarsjs.com/guide/builtin-helpers.html#each) to build a full JSON response with the current list's content.
+
+Things to consider:
+
+- this syntax only works with `body`. It DOES NOT work with `jsonBody`
+ - as this might get ugly, consider using `bodyFileName` / `withBodyFile()` have proper indentation
+- the default response for non-existant context as well as non-existant list in a context is an empty list. These states cannot be differentiated here
+ - if you still want a different response, consider using a [StateRequestMatcher](#negative-context-exists-match)
+- the default value for this property has to be a valid JSON list - otherwise you will get an error log and the empty list response
+- JSON does not allow trailing commas, so in order to create a valid JSON list, use `{{#unless @last}},{{/unless}` before `{{/each}}`
+
+
+Example:
+```json
+{
+ "request" : {
+ "urlPathPattern" : "/listing",
+ "method" : "GET"
+ },
+ "response" : {
+ "status" : 200,
+ "body" : "[\n{{# each (state context=list property='list' default='[]') }} {\n \"id\": \"{{id}}\",\n \"firstName\": \"{{firstName}}\",\n \"firstName\": \"{{firstName}}\" }{{#unless @last}},{{/unless}}\n{{/each}}]",
+ "headers" : {
+ "content-type" : "application/json"
+ }
+ }
+}
+```
+
### Error handling
Missing Helper properties as well as unknown context properties are reported as error. Wiremock renders them in the field, itself, so there won't be an
diff --git a/src/main/java/org/wiremock/extensions/state/extensions/StateHandlerbarHelper.java b/src/main/java/org/wiremock/extensions/state/extensions/StateHandlerbarHelper.java
index ef23716..31ed5c2 100644
--- a/src/main/java/org/wiremock/extensions/state/extensions/StateHandlerbarHelper.java
+++ b/src/main/java/org/wiremock/extensions/state/extensions/StateHandlerbarHelper.java
@@ -16,13 +16,19 @@
package org.wiremock.extensions.state.extensions;
import com.github.jknack.handlebars.Options;
+import com.github.tomakehurst.wiremock.common.Json;
+import com.github.tomakehurst.wiremock.common.JsonException;
import com.github.tomakehurst.wiremock.extension.responsetemplating.helpers.HandlebarsHelper;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import org.apache.commons.lang3.StringUtils;
+import org.wiremock.extensions.state.internal.Context;
import org.wiremock.extensions.state.internal.ContextManager;
+import java.util.ArrayList;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier;
@@ -57,35 +63,36 @@ public Object apply(Object o, Options options) {
return handleError("Either 'property' or 'list' has to be set");
}
if (StringUtils.isNotBlank(property)) {
- return getProperty(contextName, property)
- .orElseGet(() ->
- Optional
- .ofNullable(defaultValue)
- .orElseGet(() -> handleError(String.format("No state for context %s, property %s found", contextName, property)))
- );
+ return getProperty(contextName, property, defaultValue)
+ .orElseGet(() -> handleError(String.format("No state for context %s, property %s found", contextName, property)));
} else {
return getList(contextName, list)
.orElseGet(() ->
- Optional
- .ofNullable(defaultValue)
- .orElseGet(() -> handleError(String.format("No state for context %s, list %s found", contextName, list)))
- );
+ Optional.ofNullable(defaultValue)
+ .orElseGet(() -> handleError(String.format("No state for context %s, list %s found", contextName, list)))
+ );
}
}
- private Optional getProperty(String contextName, String property) {
+ private Optional getProperty(String contextName, String property, String defaultValue) {
return contextManager.getContext(contextName)
- .map(context -> {
- if ("updateCount" .equals(property)) {
- return context.getUpdateCount();
- } else if ("listSize" .equals(property)) {
- return context.getList().size();
- } else {
- return context.getProperties().get(property);
- }
- }
- );
+ .map(context ->
+ Stream.of(SpecialProperties.values())
+ .filter(it -> it.name().equals(property))
+ .findFirst()
+ .map(it -> it.getFromContext(context))
+ .orElseGet(() -> context.getProperties().get(property))
+ )
+ .or(() -> convertToPropertySpecificDefault(property, defaultValue));
+ }
+
+ private Optional convertToPropertySpecificDefault(String property, String defaultValue) {
+ return Stream.of(SpecialProperties.values())
+ .filter(it -> it.name().equals(property))
+ .findFirst()
+ .map(it -> it.convertDefaultValue(defaultValue))
+ .or(() -> Optional.ofNullable(defaultValue));
}
private Optional getList(String contextName, String list) {
@@ -99,4 +106,41 @@ private Optional getList(String contextName, String list) {
}
});
}
+
+ private enum SpecialProperties {
+ updateCount(Context::getUpdateCount, it -> it),
+ listSize((context) -> context.getList().size(), it -> it),
+ @SuppressWarnings("rawtypes") list(
+ Context::getList,
+ (defaultValue) -> Optional.ofNullable(defaultValue)
+ .map(it -> {
+ try {
+ return Json.read(it, ArrayList.class);
+ } catch (JsonException ex) {
+ notifier().error("default for list property is not a JSON list - fallback to empty list: " + defaultValue);
+ return null;
+ }
+ })
+ .or(() -> Optional.of(new ArrayList()))
+ .map(it -> (Object) it)
+ .get()
+ );
+
+ private final Function contextExtractor;
+ private final Function defaultConverter;
+
+ SpecialProperties(Function contextExtractor, Function defaultConverter) {
+ this.contextExtractor = contextExtractor;
+ this.defaultConverter = defaultConverter;
+ }
+
+ public Object getFromContext(Context context) {
+ return contextExtractor.apply(context);
+ }
+
+ public Object convertDefaultValue(String defaultValue) {
+ return defaultConverter.apply(defaultValue);
+ }
+ }
+
}
diff --git a/src/test/java/org/wiremock/extensions/state/examples/StateExtensionListExampleTest.java b/src/test/java/org/wiremock/extensions/state/examples/StateExtensionListExampleTest.java
index 6e3e385..e9f2aa5 100644
--- a/src/test/java/org/wiremock/extensions/state/examples/StateExtensionListExampleTest.java
+++ b/src/test/java/org/wiremock/extensions/state/examples/StateExtensionListExampleTest.java
@@ -47,13 +47,13 @@
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
/**
- * Sample test for creating a mock for a queue with java.
+ * Sample test for creating a mock for a listing with java.
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(SAME_THREAD)
class StateExtensionListExampleTest {
- private static final String TEST_URL = "/test";
+ private static final String TEST_URL = "/listing";
private static final Store store = new CaffeineStore();
private static final ObjectMapper mapper = new ObjectMapper();
@@ -77,7 +77,7 @@ public void setup() throws JsonProcessingException {
}
@Test
- public void testQueue() {
+ public void testList() {
var firstNameOne = RandomStringUtils.randomAlphabetic(5);
var lastNameOne = RandomStringUtils.randomAlphabetic(5);
var firstNameTwo = RandomStringUtils.randomAlphabetic(5);
@@ -113,17 +113,13 @@ public void testQueue() {
.get(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
.then()
.statusCode(HttpStatus.SC_OK)
- .body("id", Matchers.equalTo(idOne))
- .body("firstName", Matchers.equalTo(firstNameOne))
- .body("lastName", Matchers.equalTo(lastNameOne));
- given()
- .accept(ContentType.JSON)
- .get(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
- .then()
- .statusCode(HttpStatus.SC_OK)
- .body("id", Matchers.equalTo(idTwo))
- .body("firstName", Matchers.equalTo(firstNameTwo))
- .body("lastName", Matchers.equalTo(lastNameTwo));
+ .body("$", Matchers.hasSize(2))
+ .body("[0].id", Matchers.equalTo(idOne))
+ .body("[0].firstName", Matchers.equalTo(firstNameOne))
+ .body("[0].lastName", Matchers.equalTo(lastNameOne))
+ .body("[1].id", Matchers.equalTo(idTwo))
+ .body("[1].firstName", Matchers.equalTo(firstNameTwo))
+ .body("[1].lastName", Matchers.equalTo(lastNameTwo));
}
@@ -149,7 +145,7 @@ private void createPostStub() throws JsonProcessingException {
"recordState",
Parameters.from(
Map.of(
- "context", "queue",
+ "context", "list",
"list", Map.of(
"addLast", Map.of(
"id", "{{jsonPath response.body '$.id'}}",
@@ -163,33 +159,24 @@ private void createPostStub() throws JsonProcessingException {
);
}
- private void createGetStub() throws JsonProcessingException {
+ private void createGetStub() {
wm.stubFor(
get(urlPathMatching(TEST_URL))
.willReturn(
WireMock.ok()
.withHeader("content-type", "application/json")
- .withJsonBody(
- mapper.readTree(
- mapper.writeValueAsString(Map.of(
- "id", "{{state context='queue' list='[0].id'}}",
- "firstName", "{{state context='queue' list='[0].firstName'}}",
- "lastName", "{{state context='queue' list='[0].lastName'}}"
- )
- )
- )
+ .withBody(
+ "[\n" +
+ "{{#each (state context='list' property='list' default='[]') }}" +
+ " {\n" +
+ " \"id\": \"{{id}}\",\n" +
+ " \"firstName\": \"{{firstName}}\",\n" +
+ " \"lastName\": \"{{lastName}}\"" +
+ " }{{#unless @last}},{{/unless}}\n" +
+ "{{/each}}" +
+ "]"
)
)
- .withServeEventListener(
- "deleteState",
- Parameters.from(
- Map.of(
- "context", "queue",
- "list", Map.of("deleteFirst", true)
- )
- )
- )
-
);
}
}
\ No newline at end of file
diff --git a/src/test/java/org/wiremock/extensions/state/examples/StateExtensionQueueExampleTest.java b/src/test/java/org/wiremock/extensions/state/examples/StateExtensionQueueExampleTest.java
new file mode 100644
index 0000000..aed8c97
--- /dev/null
+++ b/src/test/java/org/wiremock/extensions/state/examples/StateExtensionQueueExampleTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2023 Dirk Bolte
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wiremock.extensions.state.examples;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
+import com.github.tomakehurst.wiremock.extension.Parameters;
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import com.github.tomakehurst.wiremock.store.Store;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.http.HttpStatus;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.parallel.Execution;
+import org.wiremock.extensions.state.CaffeineStore;
+import org.wiremock.extensions.state.StateExtension;
+
+import java.net.URI;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static io.restassured.RestAssured.given;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
+
+/**
+ * Sample test for creating a mock for a queue with java.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@Execution(SAME_THREAD)
+class StateExtensionQueueExampleTest {
+
+ private static final String TEST_URL = "/queue";
+ private static final Store store = new CaffeineStore();
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ @RegisterExtension
+ public static WireMockExtension wm = WireMockExtension.newInstance()
+ .options(
+ wireMockConfig().dynamicPort().dynamicHttpsPort().templatingEnabled(true).globalTemplating(true)
+ .extensions(new StateExtension(store))
+ .notifier(new ConsoleNotifier(true))
+ )
+ .build();
+
+
+ @BeforeEach
+ public void setup() throws JsonProcessingException {
+ RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+ createGetStub();
+ createPostStub();
+
+ wm.saveMappings();
+ }
+
+ @Test
+ public void testQueue() {
+ var firstNameOne = RandomStringUtils.randomAlphabetic(5);
+ var lastNameOne = RandomStringUtils.randomAlphabetic(5);
+ var firstNameTwo = RandomStringUtils.randomAlphabetic(5);
+ var lastNameTwo = RandomStringUtils.randomAlphabetic(5);
+
+ var idOne = given()
+ .accept(ContentType.JSON)
+ .body(Map.of("firstName", firstNameOne, "lastName", lastNameOne))
+ .post(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("id", Matchers.notNullValue())
+ .body("firstName", Matchers.equalTo(firstNameOne))
+ .body("lastName", Matchers.equalTo(lastNameOne))
+ .extract()
+ .body()
+ .jsonPath().get("id");
+ var idTwo = given()
+ .accept(ContentType.JSON)
+ .body(Map.of("firstName", firstNameTwo, "lastName", lastNameTwo))
+ .post(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("id", Matchers.notNullValue())
+ .body("firstName", Matchers.equalTo(firstNameTwo))
+ .body("lastName", Matchers.equalTo(lastNameTwo))
+ .extract()
+ .body()
+ .jsonPath().get("id");
+
+ given()
+ .accept(ContentType.JSON)
+ .get(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("id", Matchers.equalTo(idOne))
+ .body("firstName", Matchers.equalTo(firstNameOne))
+ .body("lastName", Matchers.equalTo(lastNameOne));
+ given()
+ .accept(ContentType.JSON)
+ .get(assertDoesNotThrow(() -> new URI(wm.getRuntimeInfo().getHttpBaseUrl() + TEST_URL)))
+ .then()
+ .statusCode(HttpStatus.SC_OK)
+ .body("id", Matchers.equalTo(idTwo))
+ .body("firstName", Matchers.equalTo(firstNameTwo))
+ .body("lastName", Matchers.equalTo(lastNameTwo));
+ }
+
+
+ private void createPostStub() throws JsonProcessingException {
+ wm.stubFor(
+ post(urlPathMatching(TEST_URL))
+ .willReturn(
+ WireMock.ok()
+ .withHeader("content-type", "application/json")
+ .withJsonBody(
+ mapper.readTree(
+ mapper.writeValueAsString(
+ Map.of(
+ "id", "{{randomValue length=32 type='ALPHANUMERIC' uppercase=false}}",
+ "firstName", "{{jsonPath request.body '$.firstName'}}",
+ "lastName", "{{jsonPath request.body '$.lastName'}}"
+ )
+ )
+ )
+ )
+ )
+ .withServeEventListener(
+ "recordState",
+ Parameters.from(
+ Map.of(
+ "context", "queue",
+ "list", Map.of(
+ "addLast", Map.of(
+ "id", "{{jsonPath response.body '$.id'}}",
+ "firstName", "{{jsonPath request.body '$.firstName'}}",
+ "lastName", "{{jsonPath request.body '$.lastName'}}"
+ )
+ )
+ )
+ )
+ )
+ );
+ }
+
+ private void createGetStub() throws JsonProcessingException {
+ wm.stubFor(
+ get(urlPathMatching(TEST_URL))
+ .willReturn(
+ WireMock.ok()
+ .withHeader("content-type", "application/json")
+ .withJsonBody(
+ mapper.readTree(
+ mapper.writeValueAsString(Map.of(
+ "id", "{{state context='queue' list='[0].id'}}",
+ "firstName", "{{state context='queue' list='[0].firstName'}}",
+ "lastName", "{{state context='queue' list='[0].lastName'}}"
+ )
+ )
+ )
+ )
+ )
+ .withServeEventListener(
+ "deleteState",
+ Parameters.from(
+ Map.of(
+ "context", "queue",
+ "list", Map.of("deleteFirst", true)
+ )
+ )
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/wiremock/extensions/state/functionality/StateTemplateHelperProviderExtensionTest.java b/src/test/java/org/wiremock/extensions/state/functionality/StateTemplateHelperProviderExtensionTest.java
index 9469638..4e1a743 100644
--- a/src/test/java/org/wiremock/extensions/state/functionality/StateTemplateHelperProviderExtensionTest.java
+++ b/src/test/java/org/wiremock/extensions/state/functionality/StateTemplateHelperProviderExtensionTest.java
@@ -20,8 +20,11 @@
import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.extension.Parameters;
import io.restassured.http.ContentType;
+import io.restassured.response.ValidatableResponse;
+import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.HttpStatus;
+import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -36,6 +39,7 @@
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -126,109 +130,6 @@ void test_unknownProperty_fail() throws JsonProcessingException {
);
}
- @Nested
- public class Property {
-
- @BeforeEach
- void setup() {
- createPostStub();
- createGetStub();
- }
-
- @Test
- void test_returnsStateFromPreviousRequest_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("state", contextValue, "one");
- getAndAssertContextValue("state", contextValue, contextValue, "one", "0");
- }
-
- @Test
- void test_defaults_returnsStateFromPreviousRequest_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("state", contextValue, "one");
- getAndAssertContextValue("state/default", contextValue, contextValue, "one", "0");
- }
-
- @Test
- void test_returnsFullBodyFromPreviousRequest_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("state", contextValue, "one");
- getAndAssertFullBody(contextValue);
- }
-
- @Test
- void test_differentStatesSupported_ok() {
- var contextValueOne = RandomStringUtils.randomAlphabetic(5);
- var contextValueTwo = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("state", contextValueOne, "one");
- postAndAssertContextValue("state", contextValueTwo, "one");
- getAndAssertContextValue("state", contextValueOne, contextValueOne, "one", "0");
- getAndAssertContextValue("state", contextValueTwo, contextValueTwo, "one", "0");
- }
-
-
- }
- @Nested
- public class List {
- @BeforeEach
- void setup() {
- createPostStub();
- createGetStub();
- }
-
- @Test
- void test_returnsListElement_oneItem_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("list", contextValue, "one");
-
- getAndAssertContextValue("list/0", contextValue, contextValue, "one", "1");
- }
-
- @Test
- void test_defaults_knownItem_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("list", contextValue, "one");
-
- getAndAssertContextValue("list/default/0", contextValue, contextValue, "one", "1");
- }
-
- @Test
- void test_defaults_unknownItem_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("list", contextValue, "one");
-
- getAndAssertContextValue("list/default/1", contextValue, "defaultStateValueOne", "defaultStateValueTwo", "1");
- }
-
- @Test
- void test_returnsListElement_multipleItems_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("list", contextValue, "one");
- postAndAssertContextValue("list", contextValue, "two");
- postAndAssertContextValue("list", contextValue, "three");
-
- getAndAssertContextValue("list/1", contextValue, contextValue, "two", "3");
- }
- @Test
- void test_returnsSingleListElement_lastItem_ok() {
- var contextValue = RandomStringUtils.randomAlphabetic(5);
-
- postAndAssertContextValue("list", contextValue, "one");
- postAndAssertContextValue("list", contextValue, "two");
- postAndAssertContextValue("list", contextValue, "three");
-
- getAndAssertContextValue("list/-1", contextValue, contextValue, "three", "3");
- }
-
- }
private void createGetStub() {
wm.stubFor(
get(urlPathMatching("/state/[^/]+"))
@@ -288,7 +189,7 @@ private void createGetStub() {
"valueOne", "{{state context=request.pathSegments.[2] list=(join '[' request.pathSegments.[1] '].stateValueOne' '')}}",
"valueTwo", "{{state context=request.pathSegments.[2] list=(join '[' request.pathSegments.[1] '].stateValueTwo' '')}}",
"listSize", "{{state context=request.pathSegments.[2] property='listSize'}}",
- "unknown", "{{state context=request.pathSegments.[1] property='unknown' default='defaultUnknown'}}"
+ "unknown", "{{state context=request.pathSegments.[2] property='unknown' default='defaultUnknown'}}"
)
)
)
@@ -296,6 +197,40 @@ private void createGetStub() {
)
);
+ wm.stubFor(
+ get(urlPathMatching("/list/allNoDefault/[^/]+"))
+ .willReturn(
+ WireMock.ok()
+ .withHeader("content-type", "application/json")
+ .withBody(
+ "[\n" +
+ "{{# each (state context=request.pathSegments.[2] property='list') }}" +
+ " {\n" +
+ " }{{#unless @last}},{{/unless}}\n" +
+ "{{/each}}" +
+ "]"
+ )
+ )
+ );
+
+ wm.stubFor(
+ get(urlPathMatching("/list/all/[^/]+"))
+ .willReturn(
+ WireMock.ok()
+ .withHeader("content-type", "application/json")
+ .withBody(
+ "[\n" +
+ "{{#each (state context=request.pathSegments.[2] property='list' default='[{\"stateValueOne\": \"defaultValueOne\",\"stateValueTwo\": \"defaultValueTwo\"}]') }}" +
+ " {\n" +
+ " \"valueOne\": \"{{stateValueOne}}\",\n" +
+ " \"valueTwo\": \"{{stateValueTwo}}\"" +
+ " }{{#unless @last}},{{/unless}}\n" +
+ "{{/each}}" +
+ "]"
+ )
+ )
+ );
+
wm.stubFor(
get(urlPathMatching("/list/default/[^/]+/[^/]+"))
.willReturn(
@@ -381,11 +316,7 @@ private void createPostStub() {
}
private void getAndAssertContextValue(String path, String context, String valueOne, String valueTwo, String listSize) {
- given()
- .accept(ContentType.JSON)
- .get(assertDoesNotThrow(() -> new URI(String.format("%s/%s/%s", wm.getRuntimeInfo().getHttpBaseUrl(), path, context))))
- .then()
- .statusCode(HttpStatus.SC_OK)
+ getAndAssertOk(path, context)
.body("valueOne", equalTo(valueOne))
.body("valueTwo", equalTo(valueTwo))
.body("listSize", equalTo(listSize))
@@ -393,6 +324,14 @@ private void getAndAssertContextValue(String path, String context, String valueO
.body("other", nullValue());
}
+ private ValidatableResponse getAndAssertOk(String path, String context) {
+ return given()
+ .accept(ContentType.JSON)
+ .get(assertDoesNotThrow(() -> new URI(String.format("%s/%s/%s", wm.getRuntimeInfo().getHttpBaseUrl(), path, context))))
+ .then()
+ .statusCode(HttpStatus.SC_OK);
+ }
+
private void getAndAssertFullBody(String contextValue) {
given()
.accept(ContentType.JSON)
@@ -416,4 +355,144 @@ private void postAndAssertContextValue(String path, String contextValueOne, Stri
.then()
.statusCode(HttpStatus.SC_OK);
}
+
+ @Nested
+ public class Property {
+
+ @BeforeEach
+ void setup() {
+ createPostStub();
+ createGetStub();
+ }
+
+ @Test
+ void test_returnsStateFromPreviousRequest_ok() {
+ var contextValue = RandomStringUtils.randomAlphabetic(5);
+
+ postAndAssertContextValue("state", contextValue, "one");
+ getAndAssertContextValue("state", contextValue, contextValue, "one", "0");
+ }
+
+ @Test
+ void test_defaults_returnsStateFromPreviousRequest_ok() {
+ var contextValue = RandomStringUtils.randomAlphabetic(5);
+
+ postAndAssertContextValue("state", contextValue, "one");
+ getAndAssertContextValue("state/default", contextValue, contextValue, "one", "0");
+ }
+
+ @Test
+ void test_returnsFullBodyFromPreviousRequest_ok() {
+ var contextValue = RandomStringUtils.randomAlphabetic(5);
+
+ postAndAssertContextValue("state", contextValue, "one");
+ getAndAssertFullBody(contextValue);
+ }
+
+ @Test
+ void test_differentStatesSupported_ok() {
+ var contextValueOne = RandomStringUtils.randomAlphabetic(5);
+ var contextValueTwo = RandomStringUtils.randomAlphabetic(5);
+
+ postAndAssertContextValue("state", contextValueOne, "one");
+ postAndAssertContextValue("state", contextValueTwo, "one");
+ getAndAssertContextValue("state", contextValueOne, contextValueOne, "one", "0");
+ getAndAssertContextValue("state", contextValueTwo, contextValueTwo, "one", "0");
+ }
+
+
+ }
+
+ @Nested
+ public class List {
+
+ private final String contextValue = RandomStringUtils.randomAlphabetic(5);
+
+ @BeforeEach
+ void setup() {
+ createPostStub();
+ createGetStub();
+ }
+
+ @Test
+ void test_returnsListElement_oneItem_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+
+ getAndAssertContextValue("list/0", contextValue, contextValue, "one", "1");
+ }
+
+ @Test
+ void test_defaults_knownItem_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+
+ getAndAssertContextValue("list/default/0", contextValue, contextValue, "one", "1");
+ }
+
+ @Test
+ void test_defaults_unknownItem_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+
+ getAndAssertContextValue("list/default/1", contextValue, "defaultStateValueOne", "defaultStateValueTwo", "1");
+ }
+
+ @Test
+ void test_returnsListElement_multipleItems_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+ postAndAssertContextValue("list", contextValue, "two");
+ postAndAssertContextValue("list", contextValue, "three");
+
+ getAndAssertContextValue("list/1", contextValue, contextValue, "two", "3");
+ }
+
+ @Test
+ void test_returnsSingleListElement_lastItem_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+ postAndAssertContextValue("list", contextValue, "two");
+ postAndAssertContextValue("list", contextValue, "three");
+
+ getAndAssertContextValue("list/-1", contextValue, contextValue, "three", "3");
+ }
+
+ @Nested
+ public class CompleteList {
+ @Test
+ void test_returnsCompleteList_noContext_emptyList() {
+ getAndAssertOk("list/allNoDefault", contextValue)
+ .body("$", Matchers.empty());
+ }
+
+ @Test
+ void test_returnsCompleteList_defaultFilled_ok() {
+ getAndAssertOk("list/all", contextValue)
+ .body("$", hasSize(1))
+ .body("[0].valueOne", equalTo("defaultValueOne"))
+ .body("[0].valueTwo", equalTo("defaultValueTwo"));
+ }
+
+ @Test
+ void test_returnsCompleteList_emptyList_ok() {
+ postAndAssertContextValue("state", contextValue, "one");
+
+ getAndAssertContextValue("state", contextValue, contextValue, "one", "0");
+ getAndAssertOk("list/all", contextValue)
+ .body("$", Matchers.empty());
+ }
+
+ @Test
+ void test_returnsCompleteList_entries_ok() {
+ postAndAssertContextValue("list", contextValue, "one");
+ postAndAssertContextValue("list", contextValue, "two");
+ postAndAssertContextValue("list", contextValue, "three");
+
+ getAndAssertOk("list/all", contextValue)
+ .body("$", hasSize(3))
+ .body("[0].valueOne", equalTo(contextValue))
+ .body("[0].valueTwo", equalTo("one"))
+ .body("[1].valueOne", equalTo(contextValue))
+ .body("[1].valueTwo", equalTo("two"))
+ .body("[2].valueOne", equalTo(contextValue))
+ .body("[2].valueTwo", equalTo("three"));
+ }
+ }
+ }
}
\ No newline at end of file