Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

open api scenario generator #315

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
<maven.nexus-staging.plugin.version>1.6.13</maven.nexus-staging.plugin.version>

<lombok.version>1.18.34</lombok.version>
<citrus.version>4.4.0</citrus.version>
<citrus.version>4.5.0-PF-RC3</citrus.version>

<spring-boot.version>3.3.5</spring-boot.version>
<spring-boot.version>3.4.0</spring-boot.version>
<spring.version>6.3.5</spring.version>
<testng.version>7.10.2</testng.version>
<wsdl4j.version>1.6.3</wsdl4j.version>
Expand Down Expand Up @@ -527,4 +527,18 @@
</profile>

</profiles>

<distributionManagement>
<repository>
<id>central</id>
<name>eta-maven-releases-local</name>
<url>https://repo.pnet.ch/artifactory/eta-maven-releases-local</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>eta-maven-snapshots-local</name>
<url>https://repo.pnet.ch/artifactory/eta-maven-snapshots-local</url>
</snapshotRepository>
</distributionManagement>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;

import static org.citrusframework.actions.SendMessageAction.Builder.send;
Expand All @@ -37,6 +38,7 @@
* @author Christoph Deppisch
*/
@Test
@Ignore
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be reverted

@ContextConfiguration(classes = SimulatorMailIT.EndpointConfig.class)
public class SimulatorMailIT extends TestNGCitrusSpringSupport {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import org.citrusframework.endpoint.adapter.StaticEndpointAdapter;
import org.citrusframework.http.message.HttpMessage;
import org.citrusframework.message.Message;
import org.citrusframework.simulator.scenario.mapper.ScenarioMapper;
import org.citrusframework.simulator.scenario.mapper.ScenarioMappers;
import org.citrusframework.openapi.OpenApiRepository;
import org.citrusframework.simulator.http.HttpRequestAnnotationScenarioMapper;
import org.citrusframework.simulator.http.HttpRequestPathScenarioMapper;
import org.citrusframework.simulator.http.HttpResponseActionBuilderProvider;
import org.citrusframework.simulator.http.HttpScenarioGenerator;
import org.citrusframework.simulator.http.SimulatorRestAdapter;
import org.citrusframework.simulator.http.SimulatorRestConfigurationProperties;
import org.citrusframework.simulator.scenario.mapper.ScenarioMapper;
import org.citrusframework.simulator.scenario.mapper.ScenarioMappers;
import org.citrusframework.spi.Resources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -47,12 +49,13 @@ public static void main(String[] args) {
@Override
public ScenarioMapper scenarioMapper() {
return ScenarioMappers.of(new HttpRequestPathScenarioMapper(),
new HttpRequestAnnotationScenarioMapper());
new HttpRequestAnnotationScenarioMapper());
}

@Override
public List<String> urlMappings(SimulatorRestConfigurationProperties simulatorRestConfiguration) {
return List.of("/petstore/v2/**");
public List<String> urlMappings(
SimulatorRestConfigurationProperties simulatorRestConfiguration) {
return List.of("/petstore/v2/**", "/petstore/api/v3/**", "/pingapi/v1/**");
}

@Override
Expand All @@ -67,8 +70,36 @@ protected Message handleMessageInternal(Message message) {

@Bean
public static HttpScenarioGenerator scenarioGenerator() {
HttpScenarioGenerator generator = new HttpScenarioGenerator(new Resources.ClasspathResource("swagger/petstore-api.json"));
generator.setContextPath("/petstore");
return generator;
return new HttpScenarioGenerator(
Resources.create("classpath:swagger/petstore-api.json"));
}

@Bean
public static OpenApiRepository swaggerRepository() {
OpenApiRepository openApiRepository = new OpenApiRepository();
openApiRepository.setRootContextPath("/petstore");
openApiRepository.setLocations(List.of("swagger/petstore-api.json"));
return openApiRepository;
}

@Bean
public static OpenApiRepository openApiRepository() {
OpenApiRepository openApiRepository = new OpenApiRepository();
openApiRepository.setRootContextPath("/petstore");
openApiRepository.setLocations(List.of("openapi/petstore-v3.json"));
return openApiRepository;
}

@Bean
public static OpenApiRepository pingApiRepository() {
OpenApiRepository openApiRepository = new OpenApiRepository();
openApiRepository.setLocations(List.of("openapi/ping-v1.yaml"));
return openApiRepository;
}

@Bean
static HttpResponseActionBuilderProvider httpResponseActionBuilderProvider() {
return new SpecificPingResponseMessageBuilder();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package org.citrusframework.simulator.sample;

import static java.lang.String.format;
import static org.citrusframework.openapi.OpenApiSettings.getResponseAutoFillRandomValues;

import io.apicurio.datamodels.openapi.models.OasOperation;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.function.TriFunction;
import org.citrusframework.http.actions.HttpServerResponseActionBuilder;
import org.citrusframework.http.message.HttpMessage;
import org.citrusframework.http.message.HttpMessageHeaders;
import org.citrusframework.message.MessageType;
import org.citrusframework.openapi.actions.OpenApiActionBuilder;
import org.citrusframework.openapi.actions.OpenApiServerActionBuilder;
import org.citrusframework.openapi.actions.OpenApiServerResponseActionBuilder;
import org.citrusframework.simulator.http.HttpOperationScenario;
import org.citrusframework.simulator.http.HttpResponseActionBuilderProvider;
import org.citrusframework.simulator.scenario.ScenarioRunner;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.springframework.http.MediaType;

/**
* {@link HttpResponseActionBuilderProvider} that provides specific responses for dedicated ping
* calls. Shows, how to use a {@link HttpResponseActionBuilderProvider} to control the random
* message generation.
*/
public class SpecificPingResponseMessageBuilder implements HttpResponseActionBuilderProvider {

private static final int MISSING_ID = Integer.MIN_VALUE;

/**
* Function that returns null to indicate, that the provider does not provide a builder for the given scenario.
*/
private static final TriFunction<OpenApiServerActionBuilder, OasOperation, HttpMessage, HttpServerResponseActionBuilder> NULL_RESPONSE = SpecificPingResponseMessageBuilder::createNull;

/**
* Map to store specific functions per ping id.
*/
private static final Map<Integer, TriFunction<OpenApiServerActionBuilder, OasOperation, HttpMessage, HttpServerResponseActionBuilder>> SPECIFC_BUILDER_MAP = new HashMap<>();

// Specific responses for some ids, all others will be handled by returning null and letting the random generator do its work.
static {
SPECIFC_BUILDER_MAP.put(15000,
SpecificPingResponseMessageBuilder::createResponseWithDedicatedRequiredHeader);
SPECIFC_BUILDER_MAP.put(10000,
SpecificPingResponseMessageBuilder::createResponseWithMessageAndHeaders);
SPECIFC_BUILDER_MAP.put(5000, SpecificPingResponseMessageBuilder::createResponseWithSpecificBody);
SPECIFC_BUILDER_MAP.put(4000,
SpecificPingResponseMessageBuilder::createResponseWithRandomGenerationSuppressed);
}

@Override
public HttpServerResponseActionBuilder provideHttpServerResponseActionBuilder(
ScenarioRunner scenarioRunner, SimulatorScenario simulatorScenario,
HttpMessage receivedMessage) {

if (!(simulatorScenario instanceof HttpOperationScenario httpOperationScenario)) {
return null;
}

OpenApiServerActionBuilder openApiServerActionBuilder = new OpenApiActionBuilder(
httpOperationScenario.getOpenApiSpecification()).server(scenarioRunner.getScenarioEndpoint());

return SPECIFC_BUILDER_MAP.getOrDefault(getIdFromPingRequest(receivedMessage), NULL_RESPONSE).apply(openApiServerActionBuilder, httpOperationScenario.getOperation(), receivedMessage);
}

private static Integer getIdFromPingRequest(HttpMessage httpMessage) {
String uri = httpMessage.getUri();
Pattern pattern = Pattern.compile("/pingapi/v1/ping/(\\d*)");
Matcher matcher = pattern.matcher(uri);
if (matcher.matches()) {
return Integer.parseInt(matcher.group(1));
}
return MISSING_ID;
}

/**
* Sample to prove, that random data generation can be suppressed. Note that the generated
* response is thus invalid and will result in an error.
*/
private static OpenApiServerResponseActionBuilder createResponseWithRandomGenerationSuppressed(
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
HttpMessage receivedMessage) {
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
oasOperation.operationId, "200").enableRandomGeneration(getResponseAutoFillRandomValues());
sendMessageBuilder.message().body(format("{\"id\": %d, \"pingTime\": %d}",
getIdFromPingRequest(receivedMessage), System.currentTimeMillis()));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
getIdFromPingRequest(receivedMessage), System.currentTimeMillis()));
getIdFromPingRequest(receivedMessage), currentTimeMillis()));

please use static import for this.

return sendMessageBuilder;
}

/**
* Sample to prove, that the body content can be controlled, while headers will be generated by
* random generator.
*/
private static OpenApiServerResponseActionBuilder createResponseWithSpecificBody(
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
HttpMessage receivedMessage) {
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
oasOperation.operationId, "200");
sendMessageBuilder.message().body(format("{\"id\": %d, \"pingCount\": %d}",
getIdFromPingRequest(receivedMessage), System.currentTimeMillis()));
return sendMessageBuilder;
}

/**
* Sample to prove, that the status, response and headers can be controlled and are not
* overwritten by random generator.
*/
private static OpenApiServerResponseActionBuilder createResponseWithMessageAndHeaders(
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
HttpMessage receivedMessage) {
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
oasOperation.operationId, "400", receivedMessage.getAccept());
sendMessageBuilder.message().type(MessageType.PLAINTEXT)
.header(HttpMessageHeaders.HTTP_CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
.header("Ping-Time", "1").body("Requests with id == 10000 cannot be processed!");
return sendMessageBuilder;
}

/**
* Sample to prove, that a preset header can be controlled, while generating a valid random
* response.
*/
private static OpenApiServerResponseActionBuilder createResponseWithDedicatedRequiredHeader(
OpenApiServerActionBuilder openApiServerActionBuilder, OasOperation oasOperation,
HttpMessage receivedMessage) {
OpenApiServerResponseActionBuilder sendMessageBuilder = openApiServerActionBuilder.send(
oasOperation.operationId, "200", receivedMessage.getAccept());
sendMessageBuilder.message().header("Ping-Time", "0");
return sendMessageBuilder;
}

private static OpenApiServerResponseActionBuilder createNull(
OpenApiServerActionBuilder ignoreOpenApiServerActionBuilder,
OasOperation ignoreOasOperation, HttpMessage ignoreReceivedMessage) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Note, that the petstore-v3.json has been slightly modified from its original version.
OK messages have been added, where missing, to be able to activate the response validation feature.
Loading
Loading