Skip to content

Commit

Permalink
chore: adjustments due to openapi feature changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorsten Schlathoelter authored and bbortt committed Nov 24, 2024
1 parent 7c34468 commit 58d37e5
Show file tree
Hide file tree
Showing 19 changed files with 426 additions and 112 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<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-SNAPSHOT</citrus.version>

<spring-boot.version>3.3.5</spring-boot.version>
<spring.version>6.3.5</spring.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ protected Message handleMessageInternal(Message message) {
public static HttpScenarioGenerator scenarioGenerator() {
HttpScenarioGenerator generator = new HttpScenarioGenerator(
new Resources.ClasspathResource("swagger/petstore-api.json"));
generator.setContextPath("/petstore");
generator.setContextPath("/petstore/v2");
return generator;
}

@Bean
public static OpenApiRepository petstoreRepository() {
OpenApiRepository openApiRepository = new OpenApiRepository();
openApiRepository.setRootContextPath("/petstore");
openApiRepository.setRootContextPath("/petstore/api/v3");
openApiRepository.setLocations(List.of("openapi/*.json"));
return openApiRepository;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"servers": [
{
"url": "/api/v3"
"url": "http://localhost/api/v3"
}
],
"tags": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;

/**
* @author Christoph Deppisch
*/
@Test
// TODO check why this fails. check petstore-v3 json. what about the server url inside it. i just added a valid host. without the host, it does not load properly. now i get security exceptions. maybe we run in security filter because the urls and rest adaper mappings changed?
@ContextConfiguration(classes = OpenApiIT.EndpointConfig.class)
public class OpenApiIT extends TestNGCitrusSpringSupport {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public class SimulatorConfigurationProperties implements EnvironmentAware, Initi
*/
private String outboundJsonDictionary = "outbound-json-dictionary.properties";

private SimulationResults simulationResults = new SimulationResults();

@Override
public void setEnvironment(Environment environment) {
inboundXmlDictionary = environment.getProperty(SIMULATOR_INBOUND_XML_DICTIONARY_PROPERTY, environment.getProperty(SIMULATOR_INBOUND_XML_DICTIONARY_ENV, inboundXmlDictionary));
Expand All @@ -115,4 +117,14 @@ public void afterPropertiesSet() {
logger.info("Using the simulator configuration: {}", this);
}

@Getter
@Setter
@ToString
public static class SimulationResults {

/**
* Specifies whether the test results shall be deletable or not. If you're working with a long-lived citrus-simulator and disable this, make sure to manually take care of housekeeping!
*/
private boolean resetEnabled = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import org.springframework.http.HttpStatus;

@Getter
public class HttpOperationScenario extends AbstractSimulatorScenario {
public class HttpOperationScenario extends AbstractSimulatorScenario implements HttpScenario {

private static final Logger logger = LoggerFactory.getLogger(HttpOperationScenario.class);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.citrusframework.simulator.http;

import jakarta.annotation.Nonnull;
import java.util.Comparator;

/**
* A comparator for HttpScenario objects that orders them based on the specificity
* of their paths. Paths that are more specific (having more segments and fewer
* variables or wildcards) are given higher priority in the ordering than less
* specific paths. This follows conventions used in REST API routing and matching.
*/
public class HttpPathSpecificityComparator implements
Comparator<HttpScenario> {

@Override
public int compare(@Nonnull HttpScenario scenario1, @Nonnull HttpScenario scenario2) {

Integer nullCompareResult = compareNullScenarios(scenario1, scenario2);
if (nullCompareResult != null) {
return nullCompareResult;
}

String path1 = scenario1.getPath();
String path2 = scenario2.getPath();

// Compare by segment count.
int segmentCountComparison = compareSegmentCount(path1, path2);
if (segmentCountComparison != 0) {
return segmentCountComparison;
}

// Compare by variable presence in segments.
int variableComparison = compareVariablesInSegments(path1, path2);
if (variableComparison != 0) {
return variableComparison;
}

// Finally, compare literally.
return path1.compareTo(path2);
}

private Integer compareNullScenarios(HttpScenario scenario1, HttpScenario scenario2) {
boolean path1IsNull = scenario1 == null || scenario1.getPath() == null;
boolean path2IsNull = scenario2 == null || scenario2.getPath() == null;

if (path1IsNull && path2IsNull) {
return 0;
} else if (path1IsNull) {
return 1;
} else if (path2IsNull) {
return -1;
}

// Neither scenario nor path is null; defer comparison to further processing
return null;
}

private int compareSegmentCount(String path1, String path2) {
int segmentCount1 = path1.split("/+", -1).length;
int segmentCount2 = path2.split("/+", -1).length;
return Integer.compare(segmentCount2, segmentCount1);
}

private int compareVariablesInSegments(String path1, String path2) {
String[] segments1 = path1.split("/+", -1);
String[] segments2 = path2.split("/+", -1);

for (int i = 0; i < segments1.length; i++) {
boolean isVariable1 = isVariableSegment(segments1[i]);
boolean isVariable2 = isVariableSegment(segments2[i]);

if (isVariable1 && !isVariable2) {
return 1; // Path1 is less specific than Path2.
} else if (!isVariable1 && isVariable2) {
return -1; // Path1 is more specific than Path2.
}
}
return 0;
}

private boolean isVariableSegment(String segment) {
return segment.startsWith("{") || segment.contains("*");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,50 @@

package org.citrusframework.simulator.http;

import jakarta.annotation.Nullable;
import static org.citrusframework.simulator.scenario.ScenarioUtils.getAnnotationFromClassHierarchy;

import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.Builder;
import org.citrusframework.http.message.HttpMessage;
import org.citrusframework.message.Message;
import org.citrusframework.simulator.config.SimulatorConfigurationProperties;
import org.citrusframework.simulator.events.ScenariosReloadedEvent;
import org.citrusframework.simulator.scenario.ScenarioListAware;
import org.citrusframework.simulator.scenario.SimulatorScenario;
import org.citrusframework.simulator.scenario.mapper.AbstractScenarioMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static org.citrusframework.simulator.scenario.ScenarioUtils.getAnnotationFromClassHierarchy;

/**
* Scenario mapper performs mapping logic on request mapping annotations on given scenarios. Scenarios match on request method as well as
* request path pattern matching.
* Scenario mapper performs mapping logic on request mapping annotations on given scenarios.
* Scenarios match on request method as well as request path pattern matching.
*
* @author Christoph Deppisch
*/
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public class HttpRequestAnnotationScenarioMapper extends AbstractScenarioMapper implements ScenarioListAware {
public class HttpRequestAnnotationScenarioMapper extends AbstractScenarioMapper implements
ScenarioListAware,
ApplicationContextAware, ApplicationListener<ScenariosReloadedEvent> {

private final HttpRequestAnnotationMatcher httpRequestAnnotationMatcher = HttpRequestAnnotationMatcher.instance();

@Autowired(required = false)
private @Nullable List<SimulatorScenario> scenarioList;
private final List<SimulatorScenario> scenarioList = new CopyOnWriteArrayList<>();

private ApplicationContext applicationContext;

@PostConstruct
public void init() {
setScenariosFromApplicationContext();
}

@Override
protected String getMappingKey(Message request) {
Expand All @@ -58,34 +71,42 @@ protected String getMappingKey(Message request) {
}

protected String getMappingKeyForHttpMessage(HttpMessage httpMessage) {
List<SimulatorScenario> nullSafeList = Optional.ofNullable(scenarioList).orElse(Collections.emptyList());

// First look for exact match
Optional<String> mapping = nullSafeList.stream()
Optional<String> mapping = scenarioList.stream()
.map(scenario -> EnrichedScenarioWithRequestMapping.builder()
.scenario(scenario)
.requestMapping(getAnnotationFromClassHierarchy(scenario, RequestMapping.class))
.build()
)
.filter(EnrichedScenarioWithRequestMapping::hasRequestMapping)
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestPathSupported(httpMessage, swrm.requestMapping(), true))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestMethodSupported(httpMessage, swrm.requestMapping()))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestQueryParamsSupported(httpMessage, swrm.requestMapping()))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestPathSupported(httpMessage,
swrm.requestMapping(), true))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestMethodSupported(httpMessage,
swrm.requestMapping()))
.filter(
swrm -> httpRequestAnnotationMatcher.checkRequestQueryParamsSupported(httpMessage,
swrm.requestMapping()))
.map(EnrichedScenarioWithRequestMapping::name)
.findFirst();

// If that didn't help, look for inecaxt match
if (mapping.isEmpty()) {
mapping = nullSafeList.stream()
mapping = scenarioList.stream()
.map(scenario -> EnrichedScenarioWithRequestMapping.builder()
.scenario(scenario)
.requestMapping(AnnotationUtils.findAnnotation(scenario.getClass(), RequestMapping.class))
.requestMapping(
AnnotationUtils.findAnnotation(scenario.getClass(), RequestMapping.class))
.build()
)
.filter(EnrichedScenarioWithRequestMapping::hasRequestMapping)
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestPathSupported(httpMessage, swrm.requestMapping(), false))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestMethodSupported(httpMessage, swrm.requestMapping()))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestQueryParamsSupported(httpMessage, swrm.requestMapping()))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestPathSupported(httpMessage,
swrm.requestMapping(), false))
.filter(
swrm -> httpRequestAnnotationMatcher.checkRequestMethodSupported(httpMessage,
swrm.requestMapping()))
.filter(swrm -> httpRequestAnnotationMatcher.checkRequestQueryParamsSupported(
httpMessage, swrm.requestMapping()))
.map(EnrichedScenarioWithRequestMapping::name)
.findFirst();
}
Expand All @@ -99,7 +120,7 @@ protected String getMappingKeyForHttpMessage(HttpMessage httpMessage) {
* @return
*/
public List<SimulatorScenario> getScenarios() {
return scenarioList;
return new ArrayList<>(scenarioList);
}

/**
Expand All @@ -108,12 +129,12 @@ public List<SimulatorScenario> getScenarios() {
* @param scenarios
*/
public void setScenarios(List<SimulatorScenario> scenarios) {
this.scenarioList = scenarios;
updateScenarioList(scenarios);
}

@Override
public void setScenarioList(List<SimulatorScenario> scenarioList) {
this.scenarioList = scenarioList;
updateScenarioList(scenarioList);
}

/**
Expand All @@ -134,8 +155,32 @@ public void setConfiguration(SimulatorConfigurationProperties configuration) {
this.setSimulatorConfigurationProperties(configuration);
}

@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@Override
public void onApplicationEvent(@NonNull ScenariosReloadedEvent event) {
setScenariosFromApplicationContext();
}

private void setScenariosFromApplicationContext() {
updateScenarioList(
applicationContext.getBeansOfType(SimulatorScenario.class).values().stream()
.toList());
}

private void updateScenarioList(List<SimulatorScenario> newScenarios) {
synchronized (this.scenarioList) {
scenarioList.clear();
scenarioList.addAll(newScenarios);
}
}

@Builder
private record EnrichedScenarioWithRequestMapping(SimulatorScenario scenario, RequestMapping requestMapping) {
private record EnrichedScenarioWithRequestMapping(SimulatorScenario scenario,
RequestMapping requestMapping) {

public boolean hasRequestMapping() {
return requestMapping != null;
Expand All @@ -145,4 +190,6 @@ public String name() {
return scenario.getName();
}
}


}
Loading

0 comments on commit 58d37e5

Please sign in to comment.