Skip to content

Commit 4f91c25

Browse files
Thorsten Schlathoelterbbortt
authored andcommitted
feat(#285): implement validation and refine scenario generation
1 parent 7f103c3 commit 4f91c25

File tree

24 files changed

+2553
-316
lines changed

24 files changed

+2553
-316
lines changed

simulator-samples/sample-swagger/src/main/java/org/citrusframework/simulator/sample/Simulator.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
import org.citrusframework.endpoint.adapter.StaticEndpointAdapter;
2222
import org.citrusframework.http.message.HttpMessage;
2323
import org.citrusframework.message.Message;
24-
import org.citrusframework.simulator.scenario.mapper.ScenarioMapper;
25-
import org.citrusframework.simulator.scenario.mapper.ScenarioMappers;
24+
import org.citrusframework.openapi.OpenApiRepository;
2625
import org.citrusframework.simulator.http.HttpRequestAnnotationScenarioMapper;
2726
import org.citrusframework.simulator.http.HttpRequestPathScenarioMapper;
27+
import org.citrusframework.simulator.http.HttpResponseActionBuilderProvider;
2828
import org.citrusframework.simulator.http.HttpScenarioGenerator;
2929
import org.citrusframework.simulator.http.SimulatorRestAdapter;
3030
import org.citrusframework.simulator.http.SimulatorRestConfigurationProperties;
31+
import org.citrusframework.simulator.scenario.mapper.ScenarioMapper;
32+
import org.citrusframework.simulator.scenario.mapper.ScenarioMappers;
3133
import org.citrusframework.spi.Resources;
3234
import org.springframework.boot.SpringApplication;
3335
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -47,12 +49,13 @@ public static void main(String[] args) {
4749
@Override
4850
public ScenarioMapper scenarioMapper() {
4951
return ScenarioMappers.of(new HttpRequestPathScenarioMapper(),
50-
new HttpRequestAnnotationScenarioMapper());
52+
new HttpRequestAnnotationScenarioMapper());
5153
}
5254

5355
@Override
54-
public List<String> urlMappings(SimulatorRestConfigurationProperties simulatorRestConfiguration) {
55-
return List.of("/petstore/v2/**");
56+
public List<String> urlMappings(
57+
SimulatorRestConfigurationProperties simulatorRestConfiguration) {
58+
return List.of("/petstore/v2/**", "/petstore/api/v3/**", "/pingapi/v1/**");
5659
}
5760

5861
@Override
@@ -67,8 +70,31 @@ protected Message handleMessageInternal(Message message) {
6770

6871
@Bean
6972
public static HttpScenarioGenerator scenarioGenerator() {
70-
HttpScenarioGenerator generator = new HttpScenarioGenerator(new Resources.ClasspathResource("swagger/petstore-api.json"));
73+
HttpScenarioGenerator generator = new HttpScenarioGenerator(
74+
new Resources.ClasspathResource("swagger/petstore-api.json"));
7175
generator.setContextPath("/petstore");
7276
return generator;
7377
}
78+
79+
@Bean
80+
public static OpenApiRepository petstoreRepository() {
81+
OpenApiRepository openApiRepository = new OpenApiRepository();
82+
openApiRepository.setRootContextPath("/petstore");
83+
openApiRepository.setLocations(List.of("openapi/*.json"));
84+
return openApiRepository;
85+
}
86+
87+
@Bean
88+
public static OpenApiRepository pingRepository() {
89+
OpenApiRepository openApiRepository = new OpenApiRepository();
90+
openApiRepository.setLocations(List.of("openapi/*.yaml"));
91+
return openApiRepository;
92+
}
93+
94+
@Bean
95+
static HttpResponseActionBuilderProvider httpResponseActionBuilderProvider() {
96+
return new SpecificPingResponseMessageBuilder();
97+
}
98+
99+
74100
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package org.citrusframework.simulator.sample;
2+
3+
import static java.lang.String.format;
4+
5+
import io.apicurio.datamodels.openapi.models.OasOperation;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
import org.apache.commons.lang3.function.TriFunction;
11+
import org.citrusframework.http.actions.HttpServerResponseActionBuilder;
12+
import org.citrusframework.http.message.HttpMessage;
13+
import org.citrusframework.http.message.HttpMessageHeaders;
14+
import org.citrusframework.message.MessageType;
15+
import org.citrusframework.openapi.actions.OpenApiActionBuilder;
16+
import org.citrusframework.openapi.actions.OpenApiServerActionBuilder;
17+
import org.citrusframework.openapi.actions.OpenApiServerResponseActionBuilder;
18+
import org.citrusframework.simulator.http.HttpOperationScenario;
19+
import org.citrusframework.simulator.http.HttpResponseActionBuilderProvider;
20+
import org.citrusframework.simulator.scenario.ScenarioRunner;
21+
import org.citrusframework.simulator.scenario.SimulatorScenario;
22+
import org.springframework.http.MediaType;
23+
24+
/**
25+
* {@link HttpResponseActionBuilderProvider} that provides specific responses for dedicated ping
26+
* calls. Shows, how to use a {@link HttpResponseActionBuilderProvider} to control the random
27+
* message generation.
28+
*/
29+
public class SpecificPingResponseMessageBuilder implements HttpResponseActionBuilderProvider {
30+
31+
private static final int MISSING_ID = Integer.MIN_VALUE;
32+
33+
/**
34+
* Function that returns null to indicate, that the provider does not provide a builder for the given scenario.
35+
*/
36+
private static final TriFunction<OpenApiServerActionBuilder, OasOperation, HttpMessage, HttpServerResponseActionBuilder> NULL_RESPONSE = SpecificPingResponseMessageBuilder::createNull;
37+
38+
/**
39+
* Map to store specific functions per ping id.
40+
*/
41+
private static final Map<Integer, TriFunction<OpenApiServerActionBuilder, OasOperation, HttpMessage, HttpServerResponseActionBuilder>> SPECIFC_BUILDER_MAP = new HashMap<>();
42+
43+
// Specific responses for some ids, all others will be handled by returning null and letting the random generator do its work.
44+
static {
45+
SPECIFC_BUILDER_MAP.put(15000,
46+
SpecificPingResponseMessageBuilder::createResponseWithDedicatedRequiredHeader);
47+
SPECIFC_BUILDER_MAP.put(10000,
48+
SpecificPingResponseMessageBuilder::createResponseWithMessageAndHeaders);
49+
SPECIFC_BUILDER_MAP.put(5000, SpecificPingResponseMessageBuilder::createResponseWithSpecificBody);
50+
SPECIFC_BUILDER_MAP.put(4000,
51+
SpecificPingResponseMessageBuilder::createResponseWithRandomGenerationSuppressed);
52+
}
53+
54+
@Override
55+
public HttpServerResponseActionBuilder provideHttpServerResponseActionBuilder(
56+
ScenarioRunner scenarioRunner, SimulatorScenario simulatorScenario,
57+
HttpMessage receivedMessage) {
58+
59+
if (!(simulatorScenario instanceof HttpOperationScenario httpOperationScenario)) {
60+
return null;
61+
}
62+
63+
OpenApiServerActionBuilder openApiServerActionBuilder = new OpenApiActionBuilder(
64+
httpOperationScenario.getOpenApiSpecification()).server(scenarioRunner.getScenarioEndpoint());
65+
66+
return SPECIFC_BUILDER_MAP.getOrDefault(getIdFromPingRequest(receivedMessage), NULL_RESPONSE).apply(openApiServerActionBuilder, httpOperationScenario.getOperation(), receivedMessage);
67+
}
68+
69+
private static Integer getIdFromPingRequest(HttpMessage httpMessage) {
70+
String uri = httpMessage.getUri();
71+
Pattern pattern = Pattern.compile("/pingapi/v1/ping/(\\d*)");
72+
Matcher matcher = pattern.matcher(uri);
73+
if (matcher.matches()) {
74+
return Integer.parseInt(matcher.group(1));
75+
}
76+
return MISSING_ID;
77+
}
78+
79+
/**
80+
* Sample to prove, that random data generation can be suppressed. Note that the generated
81+
* response is thus invalid and will result in an error.
82+
*/
83+
private static OpenApiServerResponseActionBuilder createResponseWithRandomGenerationSuppressed(
84+
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
85+
HttpMessage receivedMessage) {
86+
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
87+
oasOperation.operationId, "200").enableRandomGeneration(false);
88+
sendMessageBuilder.message().body(format("{\"id\": %d, \"pingTime\": %d}",
89+
getIdFromPingRequest(receivedMessage), System.currentTimeMillis()));
90+
return sendMessageBuilder;
91+
}
92+
93+
/**
94+
* Sample to prove, that the body content can be controlled, while headers will be generated by
95+
* random generator.
96+
*/
97+
private static OpenApiServerResponseActionBuilder createResponseWithSpecificBody(
98+
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
99+
HttpMessage receivedMessage) {
100+
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
101+
oasOperation.operationId, "200");
102+
sendMessageBuilder.message().body(format("{\"id\": %d, \"pingCount\": %d}",
103+
getIdFromPingRequest(receivedMessage), System.currentTimeMillis()));
104+
return sendMessageBuilder;
105+
}
106+
107+
/**
108+
* Sample to prove, that the status, response and headers can be controlled and are not
109+
* overwritten by random generator.
110+
*/
111+
private static OpenApiServerResponseActionBuilder createResponseWithMessageAndHeaders(
112+
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
113+
HttpMessage receivedMessage) {
114+
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
115+
oasOperation.operationId, "400", receivedMessage.getAccept());
116+
sendMessageBuilder.message().type(MessageType.PLAINTEXT)
117+
.header(HttpMessageHeaders.HTTP_CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
118+
.header("Ping-Time", "1").body("Requests with id == 10000 cannot be processed!");
119+
return sendMessageBuilder;
120+
}
121+
122+
/**
123+
* Sample to prove, that a preset header can be controlled, while generating a valid random
124+
* response.
125+
*/
126+
private static OpenApiServerResponseActionBuilder createResponseWithDedicatedRequiredHeader(
127+
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
128+
HttpMessage receivedMessage) {
129+
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
130+
oasOperation.operationId, "200", receivedMessage.getAccept());
131+
sendMessageBuilder.message().header("Ping-Time", "0");
132+
return sendMessageBuilder;
133+
}
134+
135+
private static OpenApiServerResponseActionBuilder createNull(
136+
OpenApiServerActionBuilder ignoreOpenApiServerActionBuilder,
137+
OasOperation ignoreOasOperation, HttpMessage ignoreReceivedMessage) {
138+
return null;
139+
}
140+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Note, that the petstore-v3.json has been slightly modified from its original version.
2+
OK messages have been added, where missing, to be able to activate the response validation feature.

0 commit comments

Comments
 (0)