diff --git a/docs/src/guide/after-recording.md b/docs/src/guide/after-recording.md index 1337e0e..eb43c86 100644 --- a/docs/src/guide/after-recording.md +++ b/docs/src/guide/after-recording.md @@ -75,6 +75,10 @@ If you select more than one, the plugin will use the union of the dynamic values By default, the latest version of a Template is selected, but you can change that by clicking on the field in the Version column. ![Select Correlation Templates](./assets/select-correlation-template.png) +::: tip +Since version [v2.5.1](https://github.com/Blazemeter/CorrelationRecorder/releases/tag/v2.5.1) you can quickly test your pre-existing rules in the Correlation rules tab by selecting the *Draft* template. +As long as you have rules underdevelopment, the Draft template will be accessible. +::: ::: warning diff --git a/docs/src/guide/assets/select-correlation-template.png b/docs/src/guide/assets/select-correlation-template.png index 84c0ce0..521f254 100644 Binary files a/docs/src/guide/assets/select-correlation-template.png and b/docs/src/guide/assets/select-correlation-template.png differ diff --git a/docs/src/guide/installation-guide.md b/docs/src/guide/installation-guide.md index 4b9bdfb..c0bdff5 100644 --- a/docs/src/guide/installation-guide.md +++ b/docs/src/guide/installation-guide.md @@ -45,7 +45,7 @@ Once JMeter restarts, the Auto Correlation Recorder plugin will be installed. ### Manually -1. Go to the [Auto Correlation Recorder plugin page](https://jmeter-plugins.org/?search=BlazeMeter%20-%20Correlation%20Recorder%20Plugin) and download the +1. Go to the [Auto Correlation Recorder plugin page](https://jmeter-plugins.org/?search=Blazemeter%20-%20Auto%20Correlation%20Recorder%20Plugin) and download the latest version of the plugin, with the dependencies. 1. Place the Plugin jar in the ext folder of your JMeter installation. The ext folder is usually located in `/lib/ext`. diff --git a/pom.xml b/pom.xml index 5b179a6..00d84e4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.blazemeter jmeter-bzm-correlation-recorder jar - 2.5.1 + 2.6 Auto Correlation Recorder Auto Correlation Recorder as JMeter Plugin https://github.com/Blazemeter/CorrelationRecorder diff --git a/releases.json b/releases.json index a5b7995..5cf424d 100644 --- a/releases.json +++ b/releases.json @@ -12,5 +12,19 @@ "json>=20190722", "maven-artifact>=3.8.4" ] + }, + { + "version": "2.6", + "what_is_new": "Encode and decode automatic correlation", + "dependencies": [ + "jackson-dataformat-xml>=2.10.3", + "jackson-annotations>=2.13.3", + "jackson-databind>=2.10.3", + "jackson-core>=2.13.3", + "jackson-module-jaxb-annotations>=2.10.3", + "jmeter-bzm-commons>=0.2.1", + "json>=20190722", + "maven-artifact>=3.8.4" + ] } ] diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/JMeterElementUtils.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/JMeterElementUtils.java index 173846e..f92c40d 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/JMeterElementUtils.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/JMeterElementUtils.java @@ -208,7 +208,7 @@ public static HashTree getNormalizedTestPlan(JMeterTreeModel model) { * @param testPlan the HashTree object to be converted. * @return a fully functional JMeterTreeModel instance. * @throws IllegalUserActionException when some elements of the JMeterTreeNode are not an - * AbstractConfigGui and no instance of TestPlan subTree. + * AbstractConfigGui and no instance of TestPlan subTree. */ public static JMeterTreeModel convertToTreeModel(HashTree testPlan) throws IllegalUserActionException { @@ -659,9 +659,9 @@ private static JMeterTreeModel getTreeModel() { /** * Adds a new PostProcessor to the JMeter's tree node. * - * @param destNode the node to add the PostProcessor to. + * @param destNode the node to add the PostProcessor to. * @param postProcessor the PostProcessor to add. - * @param model the JMeter's tree model. + * @param model the JMeter's tree model. */ public static void addPostProcessorToNode(JMeterTreeNode destNode, AbstractTestElement postProcessor, @@ -868,7 +868,7 @@ public ReplayReport getReplayErrors(String originalRecordingFilepath, * Replay the test plan from the given filepath. * * @param filepath The filepath of the test plan to replay. The test plan must be a JMeter test - * plan. + * plan. * @return The result collector that was used to collect the results of the replay. */ @VisibleForTesting diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResponseAnalyzer.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResponseAnalyzer.java index 90e066a..38ac9e1 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResponseAnalyzer.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResponseAnalyzer.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.samplers.SampleResult; /** @@ -35,14 +36,14 @@ public class ResponseAnalyzer { * @return The location of the argument in the response * @see ExtractionStrategy */ - public LocationType identifyArgumentLocation(SampleResult response, String value) { + public Pair identifyArgumentLocation(SampleResult response, String value) { for (ExtractionStrategy strategy : strategies) { - LocationType location = strategy.identifyLocationInResponse(response, value); - if (location != LocationType.UNKNOWN) { + Pair location = strategy.identifyLocationInResponse(response, value); + if (location.getLeft() != LocationType.UNKNOWN) { return location; } } - return LocationType.UNKNOWN; + return Pair.of(LocationType.UNKNOWN, ""); } /** diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResultsExtraction.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResultsExtraction.java index d8c3137..7084c70 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResultsExtraction.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/ResultsExtraction.java @@ -133,19 +133,24 @@ private static HTTPSamplerProxy parseToHttpSampler(HTTPSampleResult httpResult) parameters = getParameterListFromQuery(httpResult); } - for (Pair param : parameters) { - String value = param.getValue(); - String decode = JMeterElementUtils.decode(value); - if (!value.equals(decode)) { - try { - sampler.addEncodedArgument(param.getKey(), value); - } catch (IllegalArgumentException e) { - LOG.warn("Error adding argument to the sampler, {}", httpResult.getSampleLabel(), e); - e.printStackTrace(); + if (isRawBody(parameters)) { + sampler.addArgument(parameters.get(0).getKey(), parameters.get(0).getValue()); + sampler.setPostBodyRaw(true); + } else { + for (Pair param : parameters) { + String value = param.getValue(); + String decode = JMeterElementUtils.decode(value); + if (!value.equals(decode)) { + try { + sampler.addEncodedArgument(param.getKey(), value); + } catch (IllegalArgumentException e) { + LOG.warn("Error adding argument to the sampler, {}", httpResult.getSampleLabel(), e); + e.printStackTrace(); + } + continue; } - continue; + sampler.addArgument(param.getKey(), value); } - sampler.addArgument(param.getKey(), value); } if (isJson) { @@ -160,6 +165,10 @@ private static HTTPSamplerProxy parseToHttpSampler(HTTPSampleResult httpResult) return sampler; } + private static boolean isRawBody(List> parameters) { + return parameters.size() == 1 && parameters.get(0).getKey().isEmpty(); + } + private static List> getParametersFromJsonBody(HTTPSampleResult httpResult, ContentType contentType) { String body = httpResult.getQueryString(); diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/BodyExtractionStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/BodyExtractionStrategy.java index ab34c0d..21105b7 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/BodyExtractionStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/BodyExtractionStrategy.java @@ -1,19 +1,16 @@ package com.blazemeter.jmeter.correlation.core.automatic.extraction.location; -import com.blazemeter.jmeter.correlation.core.automatic.ExtractorGenerator; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.samplers.SampleResult; public class BodyExtractionStrategy implements ExtractionStrategy { + @Override - public LocationType identifyLocationInResponse(SampleResult response, String value) { + public Pair identifyLocationInResponse(SampleResult response, + String value) { String bodyResponse = response.getDataType().equals("bin") ? "" : response.getResponseDataAsString(); - boolean isInBody = bodyResponse.contains(value); - String encodedValue = ExtractorGenerator.encodeValue(value); - boolean isInBodyEncoded = !isInBody && bodyResponse.contains(encodedValue); - if (isInBody || isInBodyEncoded) { - return LocationType.BODY; - } - return LocationType.UNKNOWN; + return ExtractionStrategy.getLocationAndValue(bodyResponse, value, + LocationType.BODY); } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/CookieExtractionStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/CookieExtractionStrategy.java index 9ecfd3b..c660292 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/CookieExtractionStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/CookieExtractionStrategy.java @@ -1,26 +1,21 @@ package com.blazemeter.jmeter.correlation.core.automatic.extraction.location; -import com.blazemeter.jmeter.correlation.core.automatic.ExtractorGenerator; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.samplers.SampleResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CookieExtractionStrategy implements ExtractionStrategy { + private static final Logger LOG = LoggerFactory.getLogger(CookieExtractionStrategy.class); @Override - public LocationType identifyLocationInResponse(SampleResult response, String value) { + public Pair identifyLocationInResponse(SampleResult response, + String value) { String setCookiesFromHeaders = getCleanedResponseHeaders(response.getResponseHeaders()); // We get the cookies from the response - boolean isInSetCookie = setCookiesFromHeaders.contains(value); - String encodedValue = ExtractorGenerator.encodeValue(value); - boolean isInSetCookieEncoded = !isInSetCookie && setCookiesFromHeaders.contains(encodedValue); - - if (isInSetCookie || isInSetCookieEncoded) { - return LocationType.COOKIE; - } - - return LocationType.UNKNOWN; // Placeholder + return ExtractionStrategy.getLocationAndValue(setCookiesFromHeaders, value, + LocationType.COOKIE); } private static String getCleanedResponseHeaders(String responseHeadersRaw) { diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategy.java index b470dda..49f87ca 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategy.java @@ -1,7 +1,24 @@ package com.blazemeter.jmeter.correlation.core.automatic.extraction.location; +import com.blazemeter.jmeter.correlation.core.automatic.replacement.method.ReplacementString; +import java.util.Arrays; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.samplers.SampleResult; public interface ExtractionStrategy { - LocationType identifyLocationInResponse(SampleResult response, String value); + + Pair identifyLocationInResponse(SampleResult response, String value); + + static Pair getLocationAndValue(String content, String value, + LocationType location) { + // Lambda need a final mutable variable, we use the string array for that + final String[] appliedValue = new String[1]; + return Arrays.stream(ReplacementString.values()) + .filter(r -> { + appliedValue[0] = r.applyFunction(value); + return content.contains(appliedValue[0]); + }).findFirst() + .map(string -> Pair.of(location, appliedValue[0])) + .orElseGet(() -> Pair.of(LocationType.UNKNOWN, "")); + } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/HeaderExtractionStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/HeaderExtractionStrategy.java index fd85dca..e4d0898 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/HeaderExtractionStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/HeaderExtractionStrategy.java @@ -1,22 +1,18 @@ package com.blazemeter.jmeter.correlation.core.automatic.extraction.location; -import com.blazemeter.jmeter.correlation.core.automatic.ExtractorGenerator; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.samplers.SampleResult; public class HeaderExtractionStrategy implements ExtractionStrategy { + @Override - public LocationType identifyLocationInResponse(SampleResult response, String value) { + public Pair identifyLocationInResponse(SampleResult response, + String value) { // Logic to determine if the argument is in the headers String headersWithoutSetCookies = getCleanedResponseHeaders(String.valueOf(response.getResponseHeaders())); - boolean isInRawHeaders = headersWithoutSetCookies.contains(value); - String encodedValue = ExtractorGenerator.encodeValue(value); - boolean isInEncodedHeaders = !isInRawHeaders && headersWithoutSetCookies.contains(encodedValue); - // Return LocationType.HEADER if found, otherwise return LocationType.UNKNOWN - if (isInRawHeaders || isInEncodedHeaders) { - return LocationType.HEADER; - } - return LocationType.UNKNOWN; // Placeholder + return ExtractionStrategy.getLocationAndValue(headersWithoutSetCookies, value, + LocationType.HEADER); } private static String getCleanedResponseHeaders(String responseHeadersRaw) { diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategy.java index 84f51f9..8fac40a 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategy.java @@ -4,6 +4,7 @@ import com.blazemeter.jmeter.correlation.core.automatic.JsonUtils; import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; import com.blazemeter.jmeter.correlation.core.replacements.JsonCorrelationReplacement; +import com.blazemeter.jmeter.correlation.core.suggestions.method.ComparisonMethod.ReplacementParameters; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -18,7 +19,7 @@ public class ReplacementJsonStrategy implements ReplacementStrategy { @Override public CorrelationReplacement generateReplacement(TestElement usage, Appearances appearance, - String referenceName) { + ReplacementParameters replacementParameters) { if (!(usage instanceof HTTPSamplerBase)) { return null; } @@ -40,8 +41,12 @@ public CorrelationReplacement generateReplacement(TestElement usage, Appearan return null; } path = "$" + path; - JsonCorrelationReplacement replacement = new JsonCorrelationReplacement<>(path); - replacement.setVariableName(referenceName); + JsonCorrelationReplacement replacement = + replacementParameters.getReplacementString() == ReplacementString.NONE + ? new JsonCorrelationReplacement<>(path) : new JsonCorrelationReplacement<>(path, + replacementParameters.getReplacementString().getExpression( + replacementParameters.getRefName()), Boolean.toString(false)); + replacement.setVariableName(replacementParameters.getRefName()); return replacement; } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementRegexStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementRegexStrategy.java index aebde42..98c5a11 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementRegexStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementRegexStrategy.java @@ -3,16 +3,21 @@ import com.blazemeter.jmeter.correlation.core.automatic.Appearances; import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; import com.blazemeter.jmeter.correlation.core.replacements.RegexCorrelationReplacement; +import com.blazemeter.jmeter.correlation.core.suggestions.method.ComparisonMethod.ReplacementParameters; import org.apache.jmeter.testelement.TestElement; public class ReplacementRegexStrategy implements ReplacementStrategy { @Override public CorrelationReplacement generateReplacement(TestElement usage, Appearances appearance, - String referenceName) { + ReplacementParameters replacementParameters) { String regex = ReplacementRegex.match(appearance.getName(), appearance.getSource()); - RegexCorrelationReplacement replacement = new RegexCorrelationReplacement<>(regex); - replacement.setVariableName(referenceName); + RegexCorrelationReplacement replacement = + replacementParameters.getReplacementString() == ReplacementString.NONE + ? new RegexCorrelationReplacement<>(regex) : new RegexCorrelationReplacement<>(regex, + replacementParameters.getReplacementString() + .getExpression(replacementParameters.getRefName()), Boolean.toString(false)); + replacement.setVariableName(replacementParameters.getRefName()); return replacement; } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementStrategy.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementStrategy.java index c298b54..cf4fc67 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementStrategy.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementStrategy.java @@ -2,11 +2,12 @@ import com.blazemeter.jmeter.correlation.core.automatic.Appearances; import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; +import com.blazemeter.jmeter.correlation.core.suggestions.method.ComparisonMethod.ReplacementParameters; import org.apache.jmeter.testelement.TestElement; public interface ReplacementStrategy { CorrelationReplacement generateReplacement(TestElement usage, Appearances appearance, - String referenceName); + ReplacementParameters replacementParameters); } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementString.java b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementString.java new file mode 100644 index 0000000..71aa13c --- /dev/null +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementString.java @@ -0,0 +1,45 @@ +package com.blazemeter.jmeter.correlation.core.automatic.replacement.method; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.function.Function; + +public enum ReplacementString { + NONE("", s -> s), + URL_DECODE("__urldecode", s -> { + try { + return URLDecoder.decode(s, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }), + URL_ENCODE("__urlencode", s -> { + try { + return URLEncoder.encode(s, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }); + + private final String replacementString; + private final Function replacementFunction; + + ReplacementString(String replacementString, Function replacementFunction) { + this.replacementString = replacementString; + this.replacementFunction = replacementFunction; + } + + public String getExpression(String refName) { + if (replacementString.isEmpty()) { + return ""; + } else { + return String.format("${%s(${%s})}", this.replacementString, refName); + } + } + + public String applyFunction(String value) { + return replacementFunction.apply(value); + } +} diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/extractors/RegexCorrelationExtractor.java b/src/main/java/com/blazemeter/jmeter/correlation/core/extractors/RegexCorrelationExtractor.java index aa90b12..deb7b30 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/extractors/RegexCorrelationExtractor.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/extractors/RegexCorrelationExtractor.java @@ -211,7 +211,7 @@ public void process(HTTPSamplerBase sampler, List children, SampleR String match = regexMatcher.findMatch(field, matchNr); String varName = multiValued ? generateVariableName() : variableName; if (match != null && !match.equals(vars.get(varName))) { - analyze(match, sampler, varName); + analyze(match, sampler, variableName); addVarAndChildPostProcessor(match, varName, createPostProcessor(varName, matchNr)); } @@ -220,7 +220,7 @@ public void process(HTTPSamplerBase sampler, List children, SampleR if (matches.size() == 1) { String varName = multiValued ? generateVariableName() : variableName; String match = matches.get(0); - analyze(match, sampler, varName); + analyze(match, sampler, variableName); addVarAndChildPostProcessor(match, varName, createPostProcessor(varName, 1)); } else if (matches.size() > 1) { @@ -229,7 +229,7 @@ public void process(HTTPSamplerBase sampler, List children, SampleR } String value = String.valueOf(matches.size()); String varName = multiValued ? generateVariableName() : variableName; - analyze(value, sampler, varName); + analyze(value, sampler, variableName); addVarAndChildPostProcessor(value, varName + "_matchNr", createPostProcessor(varName, matchNr)); diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/CorrelationReplacement.java b/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/CorrelationReplacement.java index 75b1fe9..a0f9d8d 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/CorrelationReplacement.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/CorrelationReplacement.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.function.Function; import org.apache.jmeter.config.Argument; import org.apache.jmeter.config.ConfigTestElement; @@ -115,11 +116,12 @@ public void updateTestElem(CorrelationRuleTestElement testElem) { * *

Both the properties in the recorded sampler and its children will be processed * - * @param sampler recorded sampler containing the information of the request + * @param sampler recorded sampler containing the information of the request * @param children list of children added to the sampler (if the condition is matched, components - * will be added to it to correlate the obtained values) - * @param result result containing information about request and associated response from server - * @param vars stored variables shared between requests during recording + * will be added to it to correlate the obtained values) + * @param result result containing information about request and associated response from + * server + * @param vars stored variables shared between requests during recording */ public void process(HTTPSamplerBase sampler, List children, SampleResult result, JMeterVariables vars) { @@ -140,7 +142,7 @@ public void process(HTTPSamplerBase sampler, List children, SampleR * Replacement, the value will be replaced in the String as ${referenceVariableName}, * as many times as the logic in the condition allows it. * - * @param el test element to check and match the properties + * @param el test element to check and match the properties * @param vars stored variables from the recording */ private void replaceTestElementProperties(TestElement el, JMeterVariables vars) { @@ -211,7 +213,7 @@ private JMeterProperty replaceSimpleProp(JMeterProperty prop, JMeterVariables va * implemented. * * @param input property's string to check and replace - * @param vars stored variables shared between request during the recording + * @param vars stored variables shared between request during the recording * @return the resultant input after been processed */ protected abstract String replaceString(String input, JMeterVariables vars); @@ -295,4 +297,21 @@ String buildReplacementStringForMultivalued(String varNameMatch) { return replacementString; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CorrelationReplacement)) { + return false; + } + CorrelationReplacement that = (CorrelationReplacement) o; + return Objects.equals(variableName, that.variableName) && Objects.equals( + replacementString, that.replacementString); + } + + @Override + public int hashCode() { + return Objects.hash(variableName, replacementString); + } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacement.java b/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacement.java index 59f66a9..ea446ab 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacement.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacement.java @@ -1,6 +1,5 @@ package com.blazemeter.jmeter.correlation.core.replacements; -import static com.blazemeter.jmeter.correlation.core.automatic.JMeterElementUtils.classIsNumberOrBoolean; import static com.blazemeter.jmeter.correlation.core.automatic.JMeterElementUtils.jsonFindMatches; import com.blazemeter.jmeter.correlation.core.BaseCorrelationContext; @@ -30,8 +29,7 @@ import org.slf4j.LoggerFactory; /** - * Correlation Replacements that applies the replacement using Json Xpath and the captured - * values. + * Correlation Replacements that applies the replacement using Json Xpath and the captured values. * * @param correlation context that can be used to store and share values during replay */ @@ -41,8 +39,7 @@ public class JsonCorrelationReplacement extend protected static final String JSONPATH_DEFAULT_VALUE = "$.jsonpath.expression"; protected static final String REPLACEMENT_JSON_PROPERTY_NAME = PROPERTIES_PREFIX + "jsonpath"; - protected static final String REPLACEMENT_JSON_PROPERTY_DESCRIPTION = - "JSONPath expression"; + protected static final String REPLACEMENT_JSON_PROPERTY_DESCRIPTION = "JSONPath expression"; private static final Logger LOG = LoggerFactory.getLogger(RegexCorrelationReplacement.class); private static final boolean IGNORE_VALUE_DEFAULT = false; @@ -92,11 +89,9 @@ public List getParamsDefinition() { new ParameterDefinition.TextParameterDefinition(REPLACEMENT_JSON_PROPERTY_NAME, REPLACEMENT_JSON_PROPERTY_DESCRIPTION, JSONPATH_DEFAULT_VALUE), new ParameterDefinition.TextParameterDefinition(REPLACEMENT_STRING_PROPERTY_NAME, - "Replacement string", - REPLACEMENT_STRING_DEFAULT_VALUE, true), + "Replacement string", REPLACEMENT_STRING_DEFAULT_VALUE, true), new ParameterDefinition.CheckBoxParameterDefinition(REPLACEMENT_IGNORE_VALUE_PROPERTY_NAME, - "Ignore Value", - IGNORE_VALUE_DEFAULT, true)); + "Ignore Value", IGNORE_VALUE_DEFAULT, true)); } @Override @@ -104,8 +99,7 @@ protected String replaceString(String input, JMeterVariables vars) { // https://github.com/json-path/JsonPath?tab=readme-ov-file#set-a-value // Skip empty inputs if (input == null || input.isEmpty() || jsonpath == null || jsonpath.isEmpty() - || variableName == null - || variableName.isEmpty()) { + || variableName == null || variableName.isEmpty()) { return input; } // For previous replaced matches with variables, escape the unquoted variables @@ -114,38 +108,31 @@ protected String replaceString(String input, JMeterVariables vars) { HashSet> valuesReplaced = new HashSet(); // Test if the path of the jsonpath match - Pair> result = - jsonFindMatches(inputProcessed, jsonpath); + Pair> result = jsonFindMatches(inputProcessed, jsonpath); String updatedInput = inputProcessed; Class resultType = result.getLeft(); ArrayList matches = result.getRight(); if (matches.size() > 0) { // Ok, match, try to each match get the path and replace - Function expressionProvider = replaceExpressionProvider(); - String currentVariableName = variableName; for (int varNr = 0; varNr < matches.size(); varNr++) { String valueStr = matches.get(varNr); - String varMatched = searchVariable(vars, valueStr); - currentVariableName = varMatched; + String varMatched = searchVariable(vars, valueStr, replacementString); String replaceExpression = null; // When ignore value, use the replacement string if (!replacementString.isEmpty() && ignoreValue) { replaceExpression = replacementString; - } else if (varMatched != null && replacementString.isEmpty()) { - // When no replacement string - replaceExpression = expressionProvider.apply(varMatched); - } else if (varMatched != null) { // When replacement string, use replacement string - replaceExpression = expressionProvider.apply( - buildReplacementStringForMultivalued(varMatched)); + } else if (varMatched != null) { + replaceExpression = varMatched; } if (replaceExpression != null) { boolean inArray = resultType == JSONArray.class; String updatedJsonPath = inArray ? jsonpath + "[" + varNr + "]" : jsonpath; - Pair> toUpdateMatches = - jsonFindMatches(updatedInput, updatedJsonPath); + Pair> toUpdateMatches = jsonFindMatches(updatedInput, + updatedJsonPath); if (toUpdateMatches.getRight() != null) { Class updateResultType = toUpdateMatches.getLeft(); - boolean originIsUnQuoted = classIsNumberOrBoolean(updateResultType); + boolean originIsUnQuoted = JMeterElementUtils.classIsNumberOrBoolean( + updateResultType); if (originIsUnQuoted) { // When value is needed to put in the json structure without the quotes // this not is allowed by jayway because generate an invalid json with free text @@ -155,22 +142,18 @@ protected String replaceString(String input, JMeterVariables vars) { replaceExpression = ESCAPE_QUOTE_LEFT + replaceExpression + ESCAPE_QUOTE_RIGHT; } try { - updatedInput = - JsonPath.parse(updatedInput).set(updatedJsonPath, replaceExpression) - .jsonString(); + updatedInput = JsonPath.parse(updatedInput) + .set(updatedJsonPath, replaceExpression) + .jsonString(); // Store the values matched and used in the replacement valuesReplaced.add(Pair.of(valueStr, variableName)); } catch (InvalidPathException e) { - LOG.debug( - "JSONPath used to update target value doesn't match in the set: " + - "value:{} jsonpath={}", - valueStr, updatedJsonPath); + LOG.debug("JSONPath used to update target value doesn't match in the set: " + + "value:{} jsonpath={}", valueStr, updatedJsonPath); } } else { - LOG.debug( - "JSONPath used to update target value doesn't match in the get: " + - "value:{} jsonpath={}", - valueStr, updatedJsonPath); + LOG.debug("JSONPath used to update target value doesn't match in the get: " + + "value:{} jsonpath={}", valueStr, updatedJsonPath); } } } @@ -183,7 +166,6 @@ protected String replaceString(String input, JMeterVariables vars) { // Replace the start and end marks used for the values without quotes // This is needed to recover the original format updatedInput = unescapeQuotedVariablesWithMarks(updatedInput); - if (AnalysisReporter.canCorrelate()) { return updatedInput; } else { @@ -200,22 +182,30 @@ private void analysis(String literalMatched, String currentVariableName) { AnalysisReporter.report(this, literalMatched, currentSampler, currentVariableName); } - private String searchVariable(JMeterVariables vars, String value) { + private String searchVariable(JMeterVariables vars, String value, String replacementString) { int varNr = 0; + Function expressionProvider = replaceExpressionProvider(); while (varNr <= context.getVariableCount(variableName)) { String varName = varNr == 0 ? variableName : variableName + "#" + varNr; String varMatchesCount = vars.get(varName + "_matchNr"); int matchNr = varMatchesCount == null ? 0 : Integer.parseInt(varMatchesCount); if (matchNr == 0) { - if (vars.get(varName) != null && vars.get(varName).equals(value)) { - return varName; + String computedVal = + replacementString.isEmpty() ? vars.get(varName) : computeStringReplacement(varName); + if (computedVal != null && computedVal.equals(value)) { + return replacementString.isEmpty() ? expressionProvider.apply(varName) + : expressionProvider.apply(buildReplacementStringForMultivalued(varName)); } } int varMatch = 1; while (varMatch <= matchNr) { String varNameMatch = varName + "_" + varMatch; - if (vars.get(varNameMatch) != null && vars.get(varNameMatch).equals(value)) { - return varNameMatch; + String computedVal = + replacementString.isEmpty() ? vars.get(varNameMatch) + : computeStringReplacement(varNameMatch); + if (computedVal != null && computedVal.equals(value)) { + return replacementString.isEmpty() ? expressionProvider.apply(varNameMatch) + : expressionProvider.apply(buildReplacementStringForMultivalued(varNameMatch)); } varMatch += 1; } @@ -228,7 +218,7 @@ private String escapeUnquotedVariablesWithMarks(String json) { // Try to escape unquoted variable/function in json // this try to allow a json parse without error for json path evaluation - String regex = "(\\$\\{[^}]+\\})(?=(?:[^\"\\}]*\\})|(?:[^\"\\]]*\\]))"; + String regex = "[^(\"](\\$\\{.+?\\})(?=[^)\"])"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(json); @@ -274,8 +264,9 @@ public void updateTestElem(CorrelationRuleTestElement testElem) { } @Override - public void process(HTTPSamplerBase sampler, List children, SampleResult result, - JMeterVariables vars) { + public void process(HTTPSamplerBase sampler, List children, SampleResult + result, + JMeterVariables vars) { if (jsonpath.isEmpty()) { return; } @@ -292,10 +283,8 @@ public void update(CorrelationRuleTestElement testElem) { @Override public String toString() { - return "RegexCorrelationReplacement{" - + ", jsonpath='" + jsonpath + "'" - + ", replacementString='" + replacementString + "'" - + ", ignoreValue=" + ignoreValue + return "RegexCorrelationReplacement{" + ", jsonpath='" + jsonpath + "'" + + ", replacementString='" + replacementString + "'" + ", ignoreValue=" + ignoreValue + '}'; } @@ -304,16 +293,19 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof JsonCorrelationReplacement)) { + return false; + } + if (!super.equals(o)) { return false; } JsonCorrelationReplacement that = (JsonCorrelationReplacement) o; - return Objects.equals(jsonpath, that.jsonpath); + return ignoreValue == that.ignoreValue && Objects.equals(jsonpath, that.jsonpath); } @Override public int hashCode() { - return Objects.hash(jsonpath); + return Objects.hash(super.hashCode(), jsonpath, ignoreValue); } @VisibleForTesting diff --git a/src/main/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethod.java b/src/main/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethod.java index 7700f7b..599a58d 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethod.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethod.java @@ -13,17 +13,21 @@ import com.blazemeter.jmeter.correlation.core.automatic.extraction.method.Extractor; import com.blazemeter.jmeter.correlation.core.automatic.extraction.method.ExtractorFactory; import com.blazemeter.jmeter.correlation.core.automatic.replacement.method.ReplacementContext; +import com.blazemeter.jmeter.correlation.core.automatic.replacement.method.ReplacementString; import com.blazemeter.jmeter.correlation.core.extractors.CorrelationExtractor; import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; import com.blazemeter.jmeter.correlation.core.suggestions.context.ComparisonContext; import com.blazemeter.jmeter.correlation.core.suggestions.context.CorrelationContext; import com.helger.commons.annotation.VisibleForTesting; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.TestElement; @@ -85,10 +89,10 @@ private static boolean hasOrphans(CorrelationSuggestion suggestion) { * @return the populated ExtractionSuggestion. */ private static ExtractionSuggestion generateCandidateExtractor(SampleResult result, - Appearances appearance, - CorrelationExtractor extractor, - StructureType structureType, - String name) { + Appearances appearance, + CorrelationExtractor extractor, + StructureType structureType, + String name) { ExtractionSuggestion suggestion = new ExtractionSuggestion(extractor, result); suggestion.setSource(structureType.name()); suggestion.setValue(appearance.getValue()); @@ -167,7 +171,7 @@ private void loadFromDynamicElements(DynamicElement replayCandidate) { * @return the populated CorrelationSuggestion. */ private CorrelationSuggestion populateSuggestion(DynamicElement element, - CorrelationSuggestion suggestion) { + CorrelationSuggestion suggestion) { addMultivaluedExtractor(element, suggestion, results, valueToReferenceName); addMultivaluedReplacement(element, suggestion, valueToReferenceName); return suggestion; @@ -188,8 +192,8 @@ private CorrelationSuggestion populateSuggestion(DynamicElement element, * extraction suggestions. */ private void addMultivaluedExtractor(DynamicElement element, - CorrelationSuggestion suggestion, List results, - Map valueToReferenceName) { + CorrelationSuggestion suggestion, List results, + Map valueToReferenceName) { for (SampleResult result : results) { //We use both the "original" and the "other" appearances since the map can come from either @@ -220,8 +224,8 @@ private void addMultivaluedExtractor(DynamicElement element, * suggestions. */ private void addExtractorSuggestions(Map valueToReferenceName, - CorrelationSuggestion suggestion, SampleResult result, - List appearances) { + CorrelationSuggestion suggestion, SampleResult result, + List appearances) { structureTypeCache.clear(); // Flowing fields declared beforehand for performance proposes StructureType structureType; @@ -244,21 +248,21 @@ && getConfiguration().getMaxNumberOfAppearances() != -1) { } // If we reach the sampler that uses the value, we don't need to extract it. - if (reachedUsageSampler(result, appearance.getList()) - || valueInUse(result, appearance.getValue())) { + if (reachedUsageSampler(result, appearance.getList())) { continue; } - LocationType locationType = analyzer.identifyArgumentLocation(result, appearance.getValue()); - if (locationType == LocationType.UNKNOWN) { + Pair location = analyzer.identifyArgumentLocation(result, + appearance.getValue()); + if (location.getLeft() == LocationType.UNKNOWN) { // "Couldn't associate a location for the param in the responses. // Skipping this value. continue; } - structureType = getStructureType(result, structureTypeCache, locationType, analyzer); - extractor = getExtractor(locationType, structureType, extractorCache, ef); + structureType = getStructureType(result, structureTypeCache, location.getLeft(), analyzer); + extractor = getExtractor(location.getLeft(), structureType, extractorCache, ef); List> extractors = extractor - .getCorrelationExtractors(result, appearance.getValue(), name); + .getCorrelationExtractors(result, location.getRight(), name); if (extractors == null || extractors.isEmpty()) { continue; @@ -274,7 +278,7 @@ && getConfiguration().getMaxNumberOfAppearances() != -1) { suggestion.addExtractionSuggestion(extractionSuggestion); suggestion.addAppearances(result); - valueToReferenceName.putIfAbsent(appearance.getValue(), + valueToReferenceName.putIfAbsent(location.getRight(), suggestion.getExtractionParamName()); } } @@ -343,7 +347,8 @@ private String appearancesToString(List appearances) { */ private boolean valueInUse(SampleResult result, String value) { String queryString = this.resultQueryString.apply(result); - return queryString.contains(value); + return Arrays.stream(ReplacementString.values()) + .anyMatch(r -> queryString.contains(r.applyFunction(value))); } /** @@ -376,7 +381,7 @@ private boolean reachedUsageSampler(SampleResult result, List appea * @return true if the replacement suggestion is already present in the list, false otherwise. */ private boolean isRepeated(CorrelationSuggestion suggestion, - CorrelationReplacement replacementSuggestion) { + CorrelationReplacement replacementSuggestion) { return suggestion.getExtractionSuggestionsString().contains(replacementSuggestion.toString()); } @@ -393,7 +398,7 @@ private boolean isRepeated(CorrelationSuggestion suggestion, * @return true if the extraction suggestion is already present in the list, false otherwise. */ private boolean isRepeated(CorrelationSuggestion suggestion, - ExtractionSuggestion extractionSuggestion) { + ExtractionSuggestion extractionSuggestion) { return suggestion.getExtractionSuggestionsString().contains(extractionSuggestion.toString()); } @@ -412,8 +417,8 @@ private boolean isRepeated(CorrelationSuggestion suggestion, * replacement suggestions. */ private void addMultivaluedReplacement(DynamicElement element, - CorrelationSuggestion suggestion, - Map valueToReferenceName) { + CorrelationSuggestion suggestion, + Map valueToReferenceName) { List originalAppearances = element.getOriginalAppearance(); List otherAppearances = element.getOtherAppearance(); @@ -439,22 +444,24 @@ private void addMultivaluedReplacement(DynamicElement element, * suggestions. */ private void addReplacementSuggestions(CorrelationSuggestion suggestion, - Map valueToReferenceName, - List originalAppearances) { + Map valueToReferenceName, + List originalAppearances) { String name = suggestion.getParamName(); for (Appearances appearance : originalAppearances) { - String referenceName = valueToReferenceName.get(appearance.getValue()); - if (referenceName == null) { - continue; - } String source = appearance.getSource(); if (source.contains(Sources.RESPONSE) || source.contains(Sources.RESPONSE_BODY_JSON_NUMERIC) || source.contains(Sources.RESPONSE_BODY_JSON)) { continue; } + ReplacementParameters replacementParameters = getReplacementParameters(valueToReferenceName, + appearance.getValue()); + if (replacementParameters.getRefName() == null || replacementParameters.getRefName() + .isEmpty()) { + continue; + } for (TestElement usage : appearance.getList()) { CorrelationReplacement replacement = ReplacementContext.getStrategy(source) - .generateReplacement(usage, appearance, referenceName); + .generateReplacement(usage, appearance, replacementParameters); if (replacement == null || isRepeated(suggestion, replacement)) { continue; } @@ -468,6 +475,21 @@ private void addReplacementSuggestions(CorrelationSuggestion suggestion, } } + private static ReplacementParameters getReplacementParameters( + Map valueToReferenceName, + String value) { + for (ReplacementString r : ReplacementString.values()) { + Optional key = valueToReferenceName.keySet().stream() + .filter(k -> r.applyFunction(k).equals(value)).findFirst(); + if (key.isPresent()) { + String refName = valueToReferenceName.get(key.get()); + refName = refName.contains("#") ? refName.substring(0, refName.indexOf("#")) : refName; + return new ReplacementParameters(refName, r); + } + } + return new ReplacementParameters("", ReplacementString.NONE); + } + /** * This method retrieves the Configuration object from the current ComparisonContext. The * Configuration object contains the settings and parameters used for generating correlation @@ -494,4 +516,23 @@ public void setContext(ComparisonContext context) { public void applySuggestions(List suggestions) { // Do nothing. Suggestions are applied using the same mechanism as the AnalysisMethod. } + + public static class ReplacementParameters { + + private final String refName; + private final ReplacementString replacementString; + + public ReplacementParameters(String refName, ReplacementString replacementString) { + this.refName = refName; + this.replacementString = replacementString; + } + + public String getRefName() { + return refName; + } + + public ReplacementString getReplacementString() { + return replacementString; + } + } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/gui/TestPlanTemplatesRepository.java b/src/main/java/com/blazemeter/jmeter/correlation/gui/TestPlanTemplatesRepository.java index b8e2f4f..9025632 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/gui/TestPlanTemplatesRepository.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/gui/TestPlanTemplatesRepository.java @@ -48,7 +48,7 @@ public void setRootFolder(String rootFolder) { } public void addCorrelationRecorderTemplate(String templateFileName, String templatesFolderPath, - String descriptionFileName, String templateName) { + String descriptionFileName, String templateName) { copyTemplateFile(templateFileName, templatesFolderPath); addTemplateDescription(descriptionFileName, templateName); addFailExtractorAssertion(templateFileName); @@ -127,21 +127,25 @@ private void removeDeprecatedTemplatesDescription(String filePath) { List patterns = Stream.of("Correlation Recorder", "bzm - Correlation Recorder") .map(name -> Pattern.compile( - "()", - Pattern.DOTALL)) + "()", Pattern.DOTALL)) .collect(Collectors.toList()); + boolean updated = false; for (Pattern pattern : patterns) { Matcher matcher = pattern.matcher(content); - if (!matcher.find()) { - LOG.debug("Old Correlation Template not found."); - continue; + while (matcher.find()) { + content = content.replace(matcher.group(), ""); + updated = true; } - + } + if (updated) { try (FileWriter fileWriter = new FileWriter(filePath)) { - fileWriter.write(content.replace(matcher.group(), "")); + fileWriter.write(content); } catch (IOException e) { LOG.error("Error trying to write the file {}", filePath); } + } else { + LOG.debug("Old Correlation Template not found."); } } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationMethodPanel.java b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationMethodPanel.java index 35fb39f..dad97e3 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationMethodPanel.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationMethodPanel.java @@ -91,6 +91,7 @@ public void actionPerformed(ActionEvent e) { } private void continueToNextStep() { + this.getWizard().setVisible(false); if (correlateByRules.getState()) { displayTemplateSelectionPanel(); } else { diff --git a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationSuggestionsPanel.java b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationSuggestionsPanel.java index bbecbfa..b013443 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationSuggestionsPanel.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/CorrelationSuggestionsPanel.java @@ -172,6 +172,7 @@ private JPanel makeCorrelationPanel() { buttonsPanel.add(builder.withAction(CORRELATE) .withName("correlate") .withText("Apply") + .withToolTip("Add suggestions in tesplan") .build()); buttonsPanel.add(builder.withAction(EXPORT_AS_RULES) @@ -459,7 +460,6 @@ public void triggerSuggestionsGeneration(int totalErrors) { List suggestions = generator.generateSuggestions(context); loadSuggestions(suggestions); - toggleWizardVisibility(); showColumn(3, 0); } diff --git a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/WizardStepPanel.java b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/WizardStepPanel.java index a41fc8a..80f3c6a 100644 --- a/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/WizardStepPanel.java +++ b/src/main/java/com/blazemeter/jmeter/correlation/gui/automatic/WizardStepPanel.java @@ -109,4 +109,8 @@ public void setReplayTestPlan(Runnable replayTestPlan) { public void replayTestPlan() { replayTestPlan.run(); } + + protected CorrelationWizard getWizard() { + return this.wizard; + } } diff --git a/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategyTest.java b/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategyTest.java new file mode 100644 index 0000000..1cb0992 --- /dev/null +++ b/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/extraction/location/ExtractionStrategyTest.java @@ -0,0 +1,34 @@ +package com.blazemeter.jmeter.correlation.core.automatic.extraction.location; + +import static org.junit.Assert.assertEquals; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ExtractionStrategyTest { + + @Test + public void shouldReturnedUrlDecodedResultWhenSearchValue() { + + Pair result = + ExtractionStrategy.getLocationAndValue("Hello World", "Hello%20World", + LocationType.COOKIE); + Pair expected = new ImmutablePair<>(LocationType.COOKIE, "Hello World"); + assertEquals(expected, result); + } + + @Test + public void shouldReturnedUrlEncodedResultWhenSearchValue() { + // Note: java.net.URLEncode use the old "plus" (+) for spaces and not the %20 + // JMeter use the same implementation + Pair result = + ExtractionStrategy.getLocationAndValue("Hello+World", "Hello World", + LocationType.COOKIE); + Pair expected = new ImmutablePair<>(LocationType.COOKIE, "Hello+World"); + assertEquals(expected, result); + } +} diff --git a/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategyTest.java b/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategyTest.java index d0d9238..114e883 100644 --- a/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategyTest.java +++ b/src/test/java/com/blazemeter/jmeter/correlation/core/automatic/replacement/method/ReplacementJsonStrategyTest.java @@ -8,6 +8,8 @@ import com.blazemeter.jmeter.correlation.core.automatic.extraction.method.JsonBodyExtractor; import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; import com.blazemeter.jmeter.correlation.core.replacements.JsonCorrelationReplacement; +import com.blazemeter.jmeter.correlation.core.suggestions.method.ComparisonMethod; +import com.blazemeter.jmeter.correlation.core.suggestions.method.ComparisonMethod.ReplacementParameters; import java.io.IOException; import java.util.Collections; import org.apache.jmeter.protocol.http.control.HeaderManager; @@ -33,7 +35,8 @@ public void shouldReturnNullWhenCantFindPath() throws IOException { HTTPSamplerBase sampler = new HTTPSampler(); sampler.addArgument("", json); Appearances appearances = new Appearances("NotValid", "", sampler); - CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(sampler, appearances, "csrftoken"); + CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(sampler, + appearances, new ReplacementParameters("csrftoken", ReplacementString.NONE)); assertThat(actual).isNull(); } @@ -42,13 +45,15 @@ public void shouldReturnNullWhenUsesSamplerWithArguments() { HTTPSamplerBase sampler = new HTTPSampler(); sampler.addArgument("Key1", "Value1"); sampler.addArgument("Key2", "Value2"); - CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(new HeaderManager(), null, "csrftoken"); + CorrelationReplacement actual = replacementJsonStrategy.generateReplacement( + new HeaderManager(), null, new ReplacementParameters("csrftoken", ReplacementString.NONE)); assertThat(actual).isNull(); } @Test public void shouldReturnNullWhenSamplerIsNotHTTPSampler() { - CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(new HeaderManager(), null, "csrftoken"); + CorrelationReplacement actual = replacementJsonStrategy.generateReplacement( + new HeaderManager(), null, new ReplacementParameters("csrftoken", ReplacementString.NONE)); assertThat(actual).isNull(); } @@ -58,10 +63,10 @@ public void shouldReturnCorrelationReplacementWhenFindPath() throws IOException HTTPSamplerBase sampler = new HTTPSampler(); sampler.addArgument("", json); Appearances appearances = new Appearances("a6e9ad8f-cc16-42b0-b189-8dd1ea531cf4", "", sampler); - CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(sampler, appearances, "csrftoken"); + CorrelationReplacement actual = replacementJsonStrategy.generateReplacement(sampler, + appearances, new ReplacementParameters("csrftoken", ReplacementString.NONE)); JsonCorrelationReplacement expected = new JsonCorrelationReplacement<>("$.csrftoken"); expected.setVariableName("csrftoken"); assertThat(actual).isEqualTo(expected); } - } diff --git a/src/test/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacementTest.java b/src/test/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacementTest.java index e5dd3c6..6e5d495 100644 --- a/src/test/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacementTest.java +++ b/src/test/java/com/blazemeter/jmeter/correlation/core/replacements/JsonCorrelationReplacementTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; import com.blazemeter.jmeter.correlation.core.BaseCorrelationContext; import com.blazemeter.jmeter.correlation.core.extractors.JsonCorrelationExtractor; @@ -140,7 +141,6 @@ public void shouldReplaceMatchWhenNotIgnoreValueWithInnerVarsAndEvaluationMeets( createJsonExtractor(context, "1", true); String replacementString = "${__javaScript(${" + REFERENCE_NAME + "} + '3')}"; processJonReplacement(extractor, REQUEST_JSONPATH, replacementString, false); - Assertions.assertThat(getFirstArgumentValue()) .isEqualTo("{\"arg1\":\"" + escapeJsonValue(replacementString) + "\"}"); } @@ -150,9 +150,7 @@ public void shouldReplaceMatchWhenNotIgnoreValueWithMultiInnerVarAndEvaluationMe JMeterContext context = JMeterContextService.getContext(); JSONPostProcessor extractor = createJsonExtractor(context, "1", true); - - String replacementString = "${__javaScript(${" + REFERENCE_NAME + "} + '3')}"; - + String replacementString = "${__javaScript(${" + REFERENCE_NAME + "#1_2} + '3')}"; // Simulate multi value variables, overwrite context counter to 1 doReturn(1).when(replaceContext).getVariableCount(anyString()); JMeterVariables ctx_vars = context.getVariables(); @@ -362,9 +360,11 @@ private void processJonReplacement(JSONPostProcessor extractor, String jsonPath) } private void processJonReplacement(JSONPostProcessor extractor, String jsonPath, - String replacementString, boolean ignoreValue) { + String replacementString, boolean ignoreValue) { replacer = new JsonCorrelationReplacement<>(jsonPath, replacementString, Boolean.toString(ignoreValue)); + replacer.setExpressionEvaluator(expressionEvaluation); + when(expressionEvaluation.apply(replacementString)).thenReturn("123"); processJonReplacement(extractor); } @@ -376,7 +376,7 @@ private void processJonReplacement(JSONPostProcessor extractor) { } private static JSONPostProcessor createJsonExtractor(JMeterContext context, String matchNumbers, - boolean computeConcatenation) { + boolean computeConcatenation) { String VAR_NAME = "varName"; JSONPostProcessor processor = new JSONPostProcessor(); processor.setThreadContext(context); diff --git a/src/test/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethodTest.java b/src/test/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethodTest.java index b5e8076..21e69a9 100644 --- a/src/test/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethodTest.java +++ b/src/test/java/com/blazemeter/jmeter/correlation/core/suggestions/method/ComparisonMethodTest.java @@ -13,6 +13,9 @@ import com.blazemeter.jmeter.correlation.core.automatic.FileManagementUtils; import com.blazemeter.jmeter.correlation.core.automatic.ReplacementSuggestion; import com.blazemeter.jmeter.correlation.core.automatic.ResultFileParser; +import com.blazemeter.jmeter.correlation.core.automatic.ResultsExtraction; +import com.blazemeter.jmeter.correlation.core.replacements.CorrelationReplacement; +import com.blazemeter.jmeter.correlation.core.replacements.JsonCorrelationReplacement; import com.blazemeter.jmeter.correlation.core.replacements.RegexCorrelationReplacement; import com.blazemeter.jmeter.correlation.core.suggestions.context.AnalysisContext; import com.blazemeter.jmeter.correlation.core.suggestions.context.ComparisonContext; @@ -23,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.jmeter.protocol.http.control.Header; import org.apache.jmeter.protocol.http.control.HeaderManager; import org.apache.jmeter.samplers.SampleResult; @@ -162,4 +166,36 @@ public void shouldGenerateMatchingReplacementsWhenAddReplacementSuggestions() softly.assertThat(headerManager.getHeader(0)) .isEqualTo(new Header(PARAM_NAME, "${" + REFERENCE_NAME + "}")); } + + @Test + public void shouldGenerateSuggestionWithReplacementString() throws IOException { + Configuration configuration = new Configuration(); + when(context.getConfiguration()).thenReturn(configuration); + String path = TestUtils + .getFolderPath("/recordings/recordingTrace/recording-encode-decode.jtl", getClass()); + List results = new ResultFileParser(configuration) + .loadFromFile(new File(path), true); + ResultsExtraction resultsExtraction = new ResultsExtraction(configuration); + when(context.getRecordingSampleResults()).thenReturn(results); + when(context.getRecordingMap()).thenReturn(resultsExtraction.extractAppearanceMap(TestUtils + .getFolderPath("/recordings/recordingTrace/recording-encode-decode.jtl", getClass()) + .toString())); + when(context.getReplayMap()).thenReturn(resultsExtraction.extractAppearanceMap(TestUtils + .getFolderPath("/recordings/recordingTrace/replay-encode-decode.jtl", getClass()) + .toString())); + List suggestions = method.generateSuggestions(context); + List replacementSuggestions = + suggestions.stream().map(CorrelationSuggestion::getReplacementSuggestions) + .collect(Collectors.toList()).stream().flatMap(List::stream) + .collect(Collectors.toList()).stream() + .map(ReplacementSuggestion::getReplacementSuggestion).collect(Collectors.toList()); + JsonCorrelationReplacement expectedDecodeReplacement = new JsonCorrelationReplacement<>( + "$.decodeValue", "${__urlencode(${decodeValue})}", "false"); + expectedDecodeReplacement.setVariableName("decodeValue"); + JsonCorrelationReplacement expectedEncodeReplacement = new JsonCorrelationReplacement<>( + "$.encodedValue", "${__urldecode(${encodedValue})}", "false"); + expectedEncodeReplacement.setVariableName("encodedValue"); + softly.assertThat(replacementSuggestions).contains(expectedDecodeReplacement); + softly.assertThat(replacementSuggestions).contains(expectedEncodeReplacement); + } } diff --git a/src/test/resources/recordings/recordingTrace/recording-encode-decode.jtl b/src/test/resources/recordings/recordingTrace/recording-encode-decode.jtl new file mode 100644 index 0000000..c6043ee --- /dev/null +++ b/src/test/resources/recordings/recordingTrace/recording-encode-decode.jtl @@ -0,0 +1,50 @@ + + + + HTTP/1.1 200 OK +Content-Type: application/json +Matched-Stub-Id: 2312faf9-8ece-4f1f-902e-28dcc828abff +Connection: close +Server: Jetty(9.2.28.v20190418) + + Connection: close +Host: localhost:2525 +User-Agent: Java-http-client/11.0.23 + + { + "token": "3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "encodedValue": "%26valor%3Dalgo+con+espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "decodeValue": "&otro valor = otro valor con espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290" +} + + + GET + + http://localhost:2525/api/token + + + HTTP/1.1 200 OK +Content-Type: plain/text +Matched-Stub-Id: 49c8ad20-b705-4a90-a4b5-fc1655a16497 +Connection: close +Server: Jetty(9.2.28.v20190418) + + Connection: close +Host: localhost:2525 +Content-Type: application/json +User-Agent: Java-http-client/11.0.23 +Content-Length: 238 + + Valid token + + + POST + { + "token": "3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "encodedValue": "&valor=algo con espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "decodeValue": "%26otro+valor+%3D+otro+valor+con+espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290" +} + http://localhost:2525/api/data + + + diff --git a/src/test/resources/recordings/recordingTrace/replay-encode-decode.jtl b/src/test/resources/recordings/recordingTrace/replay-encode-decode.jtl new file mode 100644 index 0000000..5151974 --- /dev/null +++ b/src/test/resources/recordings/recordingTrace/replay-encode-decode.jtl @@ -0,0 +1,50 @@ + + + + HTTP/1.1 200 OK +Content-Type: application/json +Matched-Stub-Id: 2312faf9-8ece-4f1f-902e-28dcc828abff +Transfer-Encoding: chunked +Server: Jetty(9.2.28.v20190418) + + Connection: keep-alive +User-Agent: Java-http-client/11.0.23 +Host: localhost:2525 + + { + "token": "d1890763-8fd2-4072-bf2b-e683ff42914c", + "encodedValue": "%26valor%3Dalgo+con+espaciod1890763-8fd2-4072-bf2b-e683ff42914c", + "decodeValue": "&otro valor = otro valor con espaciod1890763-8fd2-4072-bf2b-e683ff42914c" +} + + + GET + + http://localhost:2525/api/token + + + HTTP/1.1 400 Bad Request +Content-Type: plain/text +Matched-Stub-Id: 49c8ad20-b705-4a90-a4b5-fc1655a16497 +Transfer-Encoding: chunked +Server: Jetty(9.2.28.v20190418) + + Connection: keep-alive +Content-Type: application/json +User-Agent: Java-http-client/11.0.23 +Content-Length: 242 +Host: localhost:2525 + + Invalid token + + + POST + { + "token": "3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "encodedValue": "&valor=algo con espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290", + "decodeValue": "%26otro+valor+%3D+otro+valor+con+espacio3dc42ef8-6bca-41a6-aae4-d0ba75d81290" +} + http://localhost:2525/api/data + + +