Skip to content

Commit a4ef05b

Browse files
authored
state template helper default property feature (#19)
- adding default for state helper - extend documentation ## References #18 <!-- References to relevant GitHub issues and pull requests, esp. upstream and downstream changes --> ## Submitter checklist - [ ] The PR request is well described and justified, including the body and the references - [ ] The PR title represents the desired changelog entry - [ ] The repository's code style is followed (see the contributing guide) - [ ] Test coverage that demonstrates that the change works as expected - [ ] For new features, there's necessary documentation in this pull request or in a subsequent PR to [wiremock.org](https://github.com/wiremock/wiremock.org) <!-- Put an `x` into the [ ] to show you have filled the information. The template comes from https://github.com/wiremock/.github/blob/main/.github/pull_request_template.md You can override it by creating .github/pull_request_template.md in your own repository -->
1 parent 4e40006 commit a4ef05b

File tree

3 files changed

+201
-94
lines changed

3 files changed

+201
-94
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ The handler has the following parameters:
654654
- getting the first state in the list: `list='[0].myProperty`
655655
- getting the last state in the list: `list='[-1].myProperty`
656656
- getting an element based on a path segment:: `list=(join '[' request.pathSegments.[1] '].myProperty' '')`
657+
- `default` (Optional): value to return in case the context or property wasn't found. Without a default value, an error message would be returned instead.
657658

658659
You have to choose either `property` or `list` (otherwise, you will get a configuration error).
659660

@@ -674,6 +675,8 @@ Example response with error:
674675
}
675676
```
676677

678+
To avoid errors, you can specify a `default` for the state helper: `"clientId": "{{state context=request.pathSegments.[1] property='firstname' default='John'}}",`
679+
677680
# Debugging
678681

679682
- EventListeners and Matchers report errors with WireMock-internal exceptions. Additionally, errors are logged.

src/main/java/org/wiremock/extensions/state/extensions/StateHandlerbarHelper.java

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
*/
1616
package org.wiremock.extensions.state.extensions;
1717

18-
import com.fasterxml.jackson.core.JsonParser;
1918
import com.github.jknack.handlebars.Options;
2019
import com.github.tomakehurst.wiremock.extension.responsetemplating.helpers.HandlebarsHelper;
2120
import com.jayway.jsonpath.JsonPath;
21+
import com.jayway.jsonpath.PathNotFoundException;
2222
import org.apache.commons.lang3.StringUtils;
2323
import org.wiremock.extensions.state.internal.ContextManager;
2424

25+
import java.util.Optional;
26+
27+
import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier;
28+
2529
/**
2630
* Response templating helper to access state.
2731
* <p>
@@ -42,6 +46,7 @@ public Object apply(Object o, Options options) {
4246
String contextName = options.hash("context");
4347
String property = options.hash("property");
4448
String list = options.hash("list");
49+
String defaultValue = options.hash("default");
4550
if (StringUtils.isEmpty(contextName)) {
4651
return handleError("'context' cannot be empty");
4752
}
@@ -52,28 +57,46 @@ public Object apply(Object o, Options options) {
5257
return handleError("Either 'property' or 'list' has to be set");
5358
}
5459
if (StringUtils.isNotBlank(property)) {
55-
return getProperty(contextName, property);
60+
return getProperty(contextName, property)
61+
.orElseGet(() ->
62+
Optional
63+
.ofNullable(defaultValue)
64+
.orElse(handleError(String.format("No state for context %s, property %s found", contextName, property)))
65+
);
5666
} else {
57-
return getList(contextName, list);
67+
return getList(contextName, list)
68+
.orElseGet(() ->
69+
Optional
70+
.ofNullable(defaultValue)
71+
.orElse(handleError(String.format("No state for context %s, list %s found", contextName, list)))
72+
);
73+
5874
}
5975
}
6076

61-
private Object getProperty(String contextName, String property) {
77+
private Optional<Object> getProperty(String contextName, String property) {
6278
return contextManager.getContext(contextName)
6379
.map(context -> {
64-
if ("updateCount".equals(property)) {
80+
if ("updateCount" .equals(property)) {
6581
return context.getUpdateCount();
66-
} else if ("listSize".equals(property)) {
82+
} else if ("listSize" .equals(property)) {
6783
return context.getList().size();
6884
} else {
6985
return context.getProperties().get(property);
7086
}
7187
}
72-
).orElse(handleError(String.format("No state for context %s, property %s found", contextName, property)));
88+
);
7389
}
74-
private Object getList(String contextName, String list) {
90+
91+
private Optional<Object> getList(String contextName, String list) {
7592
return contextManager.getContext(contextName)
76-
.map(context -> JsonPath.read(context.getList(), list))
77-
.orElse(handleError(String.format("No state for context %s, list %s found", contextName, list)));
93+
.flatMap(context -> {
94+
try {
95+
return Optional.of(JsonPath.read(context.getList(), list));
96+
} catch (PathNotFoundException e) {
97+
notifier().info("Path query failed: " + e.getMessage());
98+
return Optional.empty();
99+
}
100+
});
78101
}
79102
}

src/test/java/org/wiremock/extensions/state/StateTemplateHelperProviderExtensionTest.java

Lines changed: 165 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,22 @@ void test_unknownContext_fail() {
7777
String.format("[ERROR: No state for context %s, property stateValueOne found]", context),
7878
String.format("[ERROR: No state for context %s, property stateValueTwo found]", context),
7979
String.format("[ERROR: No state for context %s, property listSize found]", context)
80-
);
80+
);
81+
}
82+
83+
@Test
84+
void test_unknownContext_useDefault() {
85+
createPostStub();
86+
createGetStub();
87+
88+
String context = RandomStringUtils.randomAlphabetic(5);
89+
getAndAssertContextValue(
90+
"state/default",
91+
context,
92+
"defaultStateValueOne",
93+
"defaultStateValueTwo",
94+
"defaultListSize"
95+
);
8196
}
8297

8398
@Test
@@ -94,7 +109,8 @@ void test_unknownProperty_fail() throws JsonProcessingException {
94109
mapper.readTree(
95110
mapper.writeValueAsString(Map.of(
96111
"valueOne", "{{state context=request.pathSegments.[1] property='unknownValue'}}",
97-
"valueTwo", "{{state context=request.pathSegments.[1] property='unknownValue'}}"
112+
"valueTwo", "{{state context=request.pathSegments.[1] property='unknownValue'}}",
113+
"unknown", "{{state context=request.pathSegments.[1] property='unknown' default='defaultUnknown'}}"
98114
)))
99115
)
100116
)
@@ -110,6 +126,109 @@ void test_unknownProperty_fail() throws JsonProcessingException {
110126
);
111127
}
112128

129+
@Nested
130+
public class Property {
131+
132+
@BeforeEach
133+
void setup() {
134+
createPostStub();
135+
createGetStub();
136+
}
137+
138+
@Test
139+
void test_returnsStateFromPreviousRequest_ok() {
140+
var contextValue = RandomStringUtils.randomAlphabetic(5);
141+
142+
postAndAssertContextValue("state", contextValue, "one");
143+
getAndAssertContextValue("state", contextValue, contextValue, "one", "0");
144+
}
145+
146+
@Test
147+
void test_defaults_returnsStateFromPreviousRequest_ok() {
148+
var contextValue = RandomStringUtils.randomAlphabetic(5);
149+
150+
postAndAssertContextValue("state", contextValue, "one");
151+
getAndAssertContextValue("state/default", contextValue, contextValue, "one", "0");
152+
}
153+
154+
@Test
155+
void test_returnsFullBodyFromPreviousRequest_ok() {
156+
var contextValue = RandomStringUtils.randomAlphabetic(5);
157+
158+
postAndAssertContextValue("state", contextValue, "one");
159+
getAndAssertFullBody(contextValue);
160+
}
161+
162+
@Test
163+
void test_differentStatesSupported_ok() {
164+
var contextValueOne = RandomStringUtils.randomAlphabetic(5);
165+
var contextValueTwo = RandomStringUtils.randomAlphabetic(5);
166+
167+
postAndAssertContextValue("state", contextValueOne, "one");
168+
postAndAssertContextValue("state", contextValueTwo, "one");
169+
getAndAssertContextValue("state", contextValueOne, contextValueOne, "one", "0");
170+
getAndAssertContextValue("state", contextValueTwo, contextValueTwo, "one", "0");
171+
}
172+
173+
174+
}
175+
@Nested
176+
public class List {
177+
@BeforeEach
178+
void setup() {
179+
createPostStub();
180+
createGetStub();
181+
}
182+
183+
@Test
184+
void test_returnsListElement_oneItem_ok() {
185+
var contextValue = RandomStringUtils.randomAlphabetic(5);
186+
187+
postAndAssertContextValue("list", contextValue, "one");
188+
189+
getAndAssertContextValue("list/0", contextValue, contextValue, "one", "1");
190+
}
191+
192+
@Test
193+
void test_defaults_knownItem_ok() {
194+
var contextValue = RandomStringUtils.randomAlphabetic(5);
195+
196+
postAndAssertContextValue("list", contextValue, "one");
197+
198+
getAndAssertContextValue("list/default/0", contextValue, contextValue, "one", "1");
199+
}
200+
201+
@Test
202+
void test_defaults_unknownItem_ok() {
203+
var contextValue = RandomStringUtils.randomAlphabetic(5);
204+
205+
postAndAssertContextValue("list", contextValue, "one");
206+
207+
getAndAssertContextValue("list/default/1", contextValue, "defaultStateValueOne", "defaultStateValueTwo", "1");
208+
}
209+
210+
@Test
211+
void test_returnsListElement_multipleItems_ok() {
212+
var contextValue = RandomStringUtils.randomAlphabetic(5);
213+
214+
postAndAssertContextValue("list", contextValue, "one");
215+
postAndAssertContextValue("list", contextValue, "two");
216+
postAndAssertContextValue("list", contextValue, "three");
217+
218+
getAndAssertContextValue("list/1", contextValue, contextValue, "two", "3");
219+
}
220+
@Test
221+
void test_returnsSingleListElement_lastItem_ok() {
222+
var contextValue = RandomStringUtils.randomAlphabetic(5);
223+
224+
postAndAssertContextValue("list", contextValue, "one");
225+
postAndAssertContextValue("list", contextValue, "two");
226+
postAndAssertContextValue("list", contextValue, "three");
227+
228+
getAndAssertContextValue("list/-1", contextValue, contextValue, "three", "3");
229+
}
230+
231+
}
113232
private void createGetStub() {
114233
wm.stubFor(
115234
get(urlPathMatching("/state/[^/]+"))
@@ -122,7 +241,27 @@ private void createGetStub() {
122241
Map.of(
123242
"valueOne", "{{state context=request.pathSegments.[1] property='stateValueOne'}}",
124243
"valueTwo", "{{state context=request.pathSegments.[1] property='stateValueTwo'}}",
125-
"listSize", "{{state context=request.pathSegments.[1] property='listSize'}}"
244+
"listSize", "{{state context=request.pathSegments.[1] property='listSize'}}",
245+
"unknown", "{{state context=request.pathSegments.[1] property='unknown' default='defaultUnknown'}}"
246+
)
247+
)
248+
)
249+
)
250+
)
251+
);
252+
wm.stubFor(
253+
get(urlPathMatching("/state/default/[^/]+"))
254+
.willReturn(
255+
WireMock.ok()
256+
.withHeader("content-type", "application/json")
257+
.withJsonBody(
258+
Json.node(
259+
Json.write(
260+
Map.of(
261+
"valueOne", "{{state context=request.pathSegments.[2] property='stateValueOne' default='defaultStateValueOne'}}",
262+
"valueTwo", "{{state context=request.pathSegments.[2] property='stateValueTwo' default='defaultStateValueTwo'}}",
263+
"listSize", "{{state context=request.pathSegments.[2] property='listSize' default='defaultListSize'}}",
264+
"unknown", "{{state context=request.pathSegments.[2] property='unknown' default='defaultUnknown'}}"
126265
)
127266
)
128267
)
@@ -148,7 +287,28 @@ private void createGetStub() {
148287
Map.of(
149288
"valueOne", "{{state context=request.pathSegments.[2] list=(join '[' request.pathSegments.[1] '].stateValueOne' '')}}",
150289
"valueTwo", "{{state context=request.pathSegments.[2] list=(join '[' request.pathSegments.[1] '].stateValueTwo' '')}}",
151-
"listSize", "{{state context=request.pathSegments.[2] property='listSize'}}"
290+
"listSize", "{{state context=request.pathSegments.[2] property='listSize'}}",
291+
"unknown", "{{state context=request.pathSegments.[1] property='unknown' default='defaultUnknown'}}"
292+
)
293+
)
294+
)
295+
)
296+
)
297+
);
298+
299+
wm.stubFor(
300+
get(urlPathMatching("/list/default/[^/]+/[^/]+"))
301+
.willReturn(
302+
WireMock.ok()
303+
.withHeader("content-type", "application/json")
304+
.withJsonBody(
305+
Json.node(
306+
Json.write(
307+
Map.of(
308+
"valueOne", "{{state context=request.pathSegments.[3] list=(join '[' request.pathSegments.[2] '].stateValueOne' '') default='defaultStateValueOne'}}",
309+
"valueTwo", "{{state context=request.pathSegments.[3] list=(join '[' request.pathSegments.[2] '].stateValueTwo' '') default='defaultStateValueTwo'}}",
310+
"listSize", "{{state context=request.pathSegments.[3] property='listSize' default='defaultListSize'}}",
311+
"unknown", "{{state context=request.pathSegments.[3] property='unknown' default='defaultUnknown'}}"
152312
)
153313
)
154314
)
@@ -229,6 +389,7 @@ private void getAndAssertContextValue(String path, String context, String valueO
229389
.body("valueOne", equalTo(valueOne))
230390
.body("valueTwo", equalTo(valueTwo))
231391
.body("listSize", equalTo(listSize))
392+
.body("unknown", equalTo("defaultUnknown"))
232393
.body("other", nullValue());
233394
}
234395

@@ -255,84 +416,4 @@ private void postAndAssertContextValue(String path, String contextValueOne, Stri
255416
.then()
256417
.statusCode(HttpStatus.SC_OK);
257418
}
258-
259-
@Nested
260-
public class Property {
261-
262-
@BeforeEach
263-
void setup() {
264-
createPostStub();
265-
createGetStub();
266-
}
267-
268-
@Test
269-
void test_returnsStateFromPreviousRequest_ok() {
270-
var contextValue = RandomStringUtils.randomAlphabetic(5);
271-
272-
postAndAssertContextValue("state", contextValue, "one");
273-
getAndAssertContextValue("state", contextValue, contextValue, "one", "0");
274-
}
275-
276-
@Test
277-
void test_returnsFullBodyFromPreviousRequest_ok() {
278-
var contextValue = RandomStringUtils.randomAlphabetic(5);
279-
280-
postAndAssertContextValue("state", contextValue, "one");
281-
getAndAssertFullBody(contextValue);
282-
}
283-
284-
@Test
285-
void test_differentStatesSupported_ok() {
286-
var contextValueOne = RandomStringUtils.randomAlphabetic(5);
287-
var contextValueTwo = RandomStringUtils.randomAlphabetic(5);
288-
289-
postAndAssertContextValue("state", contextValueOne, "one");
290-
postAndAssertContextValue("state", contextValueTwo, "one");
291-
getAndAssertContextValue("state", contextValueOne, contextValueOne, "one", "0");
292-
getAndAssertContextValue("state", contextValueTwo, contextValueTwo, "one", "0");
293-
}
294-
}
295-
296-
@Nested
297-
public class List {
298-
299-
@BeforeEach
300-
void setup() {
301-
createPostStub();
302-
createGetStub();
303-
}
304-
305-
@Test
306-
void test_returnsListElement_oneItem_ok() {
307-
var contextValue = RandomStringUtils.randomAlphabetic(5);
308-
309-
postAndAssertContextValue("list", contextValue, "one");
310-
311-
getAndAssertContextValue("list/0", contextValue, contextValue, "one", "1");
312-
}
313-
314-
@Test
315-
void test_returnsListElement_multipleItems_ok() {
316-
var contextValue = RandomStringUtils.randomAlphabetic(5);
317-
318-
postAndAssertContextValue("list", contextValue, "one");
319-
postAndAssertContextValue("list", contextValue, "two");
320-
postAndAssertContextValue("list", contextValue, "three");
321-
322-
getAndAssertContextValue("list/1", contextValue, contextValue, "two", "3");
323-
}
324-
325-
@Test
326-
void test_returnsSingleListElement_lastItem_ok() {
327-
var contextValue = RandomStringUtils.randomAlphabetic(5);
328-
329-
postAndAssertContextValue("list", contextValue, "one");
330-
postAndAssertContextValue("list", contextValue, "two");
331-
postAndAssertContextValue("list", contextValue, "three");
332-
333-
getAndAssertContextValue("list/-1", contextValue, contextValue, "three", "3");
334-
}
335-
336-
337-
}
338419
}

0 commit comments

Comments
 (0)