Skip to content

Commit c442f31

Browse files
authored
Allow accessing the current state in this extension's configuration options (#21)
- create common initialization for standalone and manual registration of this extension - register all handlers of this extension in the common template engine of this extension - extend documentation ## References none <!-- 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 a4ef05b commit c442f31

File tree

6 files changed

+90
-49
lines changed

6 files changed

+90
-49
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,46 @@ To append a state to a list:
397397
}
398398
```
399399

400+
### Accessing the previous state
401+
402+
You can use the `state` helper to temporarily access the previous state. Use the `state` helper in the same way as you would use it when you [retrieve a state](#retrieve-a-state).
403+
404+
**Note:** This extension does not keep a history in itself but it's an effect of the evaluation order.
405+
As templates are evaluated before the state is written, the state you access in `recordState` is the one before you store the new one
406+
(so there might be none - you might want to use `default` for these cases). In case you have multiple `recordState` `serveEventListeners`, you will have new states
407+
being created in between, thus the previous state is the last stored one (so: not the one before the request).
408+
409+
1. listener 1 is executed
410+
1. accesses state n
411+
2. stores state n+1
412+
2. listener 2 is executed
413+
1. accesses state n+1
414+
2. stores state n+2
415+
416+
The evaluation order of listeners within a stub as well as across stubs is not guaranteed.
417+
418+
```json
419+
{
420+
"request": {},
421+
"response": {},
422+
"serveEventListeners": [
423+
{
424+
"name": "recordState",
425+
"parameters": {
426+
"context": "{{jsonPath response.body '$.id'}}",
427+
"state": {
428+
"id": "{{jsonPath response.body '$.id'}}",
429+
"firstName": "{{jsonPath request.body '$.firstName'}}",
430+
"lastName": "{{jsonPath request.body '$.lastName'}}",
431+
"birthName": "{{state context='$.id' property='lastName' default=''}}"
432+
}
433+
}
434+
}
435+
]
436+
}
437+
```
438+
439+
400440
## Deleting a state
401441

402442
Similar to recording a state, its deletion can be initiated in `serveEventListeners` of a stub.
@@ -660,6 +700,9 @@ You have to choose either `property` or `list` (otherwise, you will get a config
660700

661701
To retrieve a full body, use: `{{{state context=request.pathSegments.[1] property='fullBody'}}}` .
662702

703+
When registering this extension, this helper is available via WireMock's [response templating](https://wiremock.org/3.x/docs/response-templating/) as well as
704+
in all configuration options of this extension.
705+
663706
### Error handling
664707

665708
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

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

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,16 @@
1515
*/
1616
package org.wiremock.extensions.state;
1717

18-
import com.github.tomakehurst.wiremock.extension.Extension;
19-
import com.github.tomakehurst.wiremock.extension.ExtensionFactory;
20-
import com.github.tomakehurst.wiremock.extension.WireMockServices;
21-
import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine;
22-
import com.github.tomakehurst.wiremock.store.Store;
23-
import org.wiremock.extensions.state.extensions.DeleteStateEventListener;
24-
import org.wiremock.extensions.state.extensions.RecordStateEventListener;
25-
import org.wiremock.extensions.state.extensions.StateRequestMatcher;
26-
import org.wiremock.extensions.state.extensions.StateTemplateHelperProviderExtension;
27-
import org.wiremock.extensions.state.internal.ContextManager;
28-
29-
import java.util.Collections;
30-
import java.util.List;
31-
3218
/**
3319
* Factory to register all extensions for handling state for standalone service.
34-
*
35-
* Uses {@link org.wiremock.extensions.state.CaffeineStore}.
20+
* <p>
21+
* Uses {@link org.wiremock.extensions.state.CaffeineStore} as store.
3622
*
3723
* @see CaffeineStore
3824
*/
39-
public class StandaloneStateExtension implements ExtensionFactory {
40-
41-
private final TemplateEngine templateEngine = new TemplateEngine(Collections.emptyMap(), null, Collections.emptySet(), false);
42-
43-
private final Store<String, Object> store = new CaffeineStore();
44-
45-
private final ContextManager contextManager = new ContextManager(store);;
25+
public class StandaloneStateExtension extends StateExtension {
4626

47-
@Override
48-
public List<Extension> create(WireMockServices services) {
49-
return List.of(
50-
new RecordStateEventListener(contextManager, templateEngine),
51-
new DeleteStateEventListener(contextManager, templateEngine),
52-
new StateRequestMatcher(contextManager, templateEngine),
53-
new StateTemplateHelperProviderExtension(contextManager)
54-
);
27+
public StandaloneStateExtension() {
28+
super(new CaffeineStore());
5529
}
5630
}

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,28 @@
4949
*/
5050
public class StateExtension implements ExtensionFactory {
5151

52-
private final TemplateEngine templateEngine = new TemplateEngine(Collections.emptyMap(), null, Collections.emptySet(), false);
53-
54-
private final ContextManager contextManager;
52+
private final StateTemplateHelperProviderExtension stateTemplateHelperProviderExtension;
53+
private final RecordStateEventListener recordStateEventListener;
54+
private final DeleteStateEventListener deleteStateEventListener;
55+
private final StateRequestMatcher stateRequestMatcher;
5556

5657
public StateExtension(Store<String, Object> store) {
57-
this.contextManager = new ContextManager(store);
58+
var contextManager = new ContextManager(store);
59+
this.stateTemplateHelperProviderExtension = new StateTemplateHelperProviderExtension(contextManager);
60+
var templateEngine = new TemplateEngine(stateTemplateHelperProviderExtension.provideTemplateHelpers(), null, Collections.emptySet(), false);
61+
62+
this.recordStateEventListener = new RecordStateEventListener(contextManager, templateEngine);
63+
this.deleteStateEventListener = new DeleteStateEventListener(contextManager, templateEngine);
64+
this.stateRequestMatcher = new StateRequestMatcher(contextManager, templateEngine);
5865
}
5966

6067
@Override
6168
public List<Extension> create(WireMockServices services) {
6269
return List.of(
63-
new RecordStateEventListener(contextManager, templateEngine),
64-
new DeleteStateEventListener(contextManager, templateEngine),
65-
new StateRequestMatcher(contextManager, templateEngine),
66-
new StateTemplateHelperProviderExtension(contextManager)
70+
recordStateEventListener,
71+
deleteStateEventListener,
72+
stateRequestMatcher,
73+
stateTemplateHelperProviderExtension
6774
);
6875
}
6976
}

src/main/java/org/wiremock/extensions/state/internal/Context.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
import java.util.HashMap;
1919
import java.util.LinkedList;
20-
import java.util.List;
2120
import java.util.Map;
21+
import java.util.stream.Collectors;
2222

2323
public class Context {
2424

@@ -30,6 +30,15 @@ public class Context {
3030
private Long updateCount = 1L;
3131
private Long matchCount = 0L;
3232

33+
public Context(Context other) {
34+
this.contextName = other.contextName;
35+
this.properties.putAll(other.properties);
36+
this.list.addAll(other.list.stream().map(HashMap::new).collect(Collectors.toList()));
37+
this.requests.addAll(other.requests);
38+
this.updateCount = other.updateCount;
39+
this.matchCount = other.matchCount;
40+
}
41+
3342
public Context(String contextName) {
3443
this.contextName = contextName;
3544
}

src/main/java/org/wiremock/extensions/state/internal/ContextManager.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,15 @@ public Object getState(String contextName, String property) {
3636
}
3737
}
3838

39+
/**
40+
* Searches for the context by the given name.
41+
*
42+
* @param contextName The context name to search for.
43+
* @return Optional with a copy of the context - or empty.
44+
*/
3945
public Optional<Context> getContext(String contextName) {
4046
synchronized (store) {
41-
return store.get(contextName).map(it -> (Context) it);
47+
return store.get(contextName).map(it -> (Context) it).map(Context::new);
4248
}
4349
}
4450

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ private void createStatePostStub() {
6969
"context", "{{request.pathSegments.[1]}}",
7070
"state", Map.of(
7171
"stateValueOne", "{{jsonPath request.body '$.contextValueOne'}}",
72-
"stateValueTwo", "{{jsonPath request.body '$.contextValueTwo'}}"
72+
"stateValueTwo", "{{jsonPath request.body '$.contextValueTwo'}}",
73+
"previousStateValueTwo", "{{state context=request.pathSegments.[1] property='stateValueTwo' default='noPrevious'}}"
7374
)
7475
)
7576
)
@@ -130,7 +131,7 @@ public void test_stateIsWritten_ok() {
130131
.pollInterval(Duration.ofMillis(10))
131132
.atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(contextManager.numUpdates(contextName)).isEqualTo(1));
132133

133-
assertContext(contextName, contextName, "one");
134+
assertContext(contextName, contextName, "one", "noPrevious");
134135
}
135136

136137
@Test
@@ -145,7 +146,7 @@ public void test_stateIsOverwritten_ok() {
145146
.pollInterval(Duration.ofMillis(10))
146147
.atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(contextManager.numUpdates(contextName)).isEqualTo(2));
147148

148-
assertContext(contextName, contextName, "two");
149+
assertContext(contextName, contextName, "two", "one");
149150
}
150151

151152
@Test
@@ -163,19 +164,20 @@ public void test_otherStateIsWritten_ok() {
163164
.pollInterval(Duration.ofMillis(10))
164165
.atMost(Duration.ofSeconds(5)).untilAsserted(() -> assertThat(contextManager.numUpdates(contextNameTwo)).isEqualTo(1));
165166

166-
assertContext(contextNameOne, contextNameOne, "one");
167-
assertContext(contextNameTwo, contextNameTwo, "two");
167+
assertContext(contextNameOne, contextNameOne, "one", "noPrevious");
168+
assertContext(contextNameTwo, contextNameTwo, "two", "noPrevious");
168169
}
169170

170-
private void assertContext(String contextNameTwo, String stateValueOne, String stateValueTwo) {
171+
private void assertContext(String contextNameTwo, String stateValueOne, String stateValueTwo, String statePrevious) {
171172
assertThat(contextManager.getContext(contextNameTwo))
172173
.isPresent()
173174
.hasValueSatisfying(it -> {
174175
assertThat(it.getList()).isEmpty();
175176
assertThat(it.getProperties())
176-
.hasSize(2)
177+
.hasSize(3)
177178
.containsEntry("stateValueOne", stateValueOne)
178-
.containsEntry("stateValueTwo", stateValueTwo);
179+
.containsEntry("stateValueTwo", stateValueTwo)
180+
.containsEntry("previousStateValueTwo", statePrevious);
179181
}
180182
);
181183
}

0 commit comments

Comments
 (0)