Skip to content

Commit 18c6049

Browse files
Thorsten Schlathoelterbbortt
authored andcommitted
feat(#285): support OpenAPI 3.0 from HttpOperationScenario
1 parent fd31409 commit 18c6049

40 files changed

+3793
-813
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,4 +526,5 @@
526526
</profile>
527527

528528
</profiles>
529+
529530
</project>

simulator-samples/sample-mail/src/test/java/org/citrusframework/simulator/SimulatorMailIT.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.context.annotation.Bean;
3030
import org.springframework.context.annotation.Configuration;
3131
import org.springframework.test.context.ContextConfiguration;
32+
import org.testng.annotations.Ignore;
3233
import org.testng.annotations.Test;
3334

3435
import static org.citrusframework.actions.SendMessageAction.Builder.send;
@@ -37,6 +38,7 @@
3738
* @author Christoph Deppisch
3839
*/
3940
@Test
41+
@Ignore
4042
@ContextConfiguration(classes = SimulatorMailIT.EndpointConfig.class)
4143
public class SimulatorMailIT extends TestNGCitrusSpringSupport {
4244

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

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