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

ISSUE-601 # Resolve JSON.CONTENT as object or array #612

Merged
merged 12 commits into from
Jan 22, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jsmart.zerocode.core.engine.preprocessor;

import org.jsmart.zerocode.core.domain.Step;
import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;

Expand All @@ -18,4 +19,7 @@ public interface ZeroCodeAssertionsProcessor {
List<JsonAsserter> createJsonAsserters(String resolvedAssertionJson);

List<FieldAssertionMatcher> assertAllAndReturnFailed(List<JsonAsserter> asserters, String executionResult);

Step resolveJsonContent(Step thisStep, ScenarioExecutionState scenarioExecutionState);

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jsmart.zerocode.core.engine.preprocessor;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -20,22 +21,31 @@
import java.util.Properties;
import net.minidev.json.JSONArray;
import org.apache.commons.lang.text.StrSubstitutor;
import org.jsmart.zerocode.core.domain.Step;
import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
import org.jsmart.zerocode.core.engine.assertion.array.ArrayIsEmptyAsserterImpl;
import org.jsmart.zerocode.core.engine.assertion.array.ArraySizeAsserterImpl;
import org.jsmart.zerocode.core.engine.assertion.field.*;
import org.jsmart.zerocode.core.utils.SmartUtils;

import static java.lang.Integer.valueOf;
import static java.lang.String.format;
import static org.apache.commons.lang.StringEscapeUtils.escapeJava;
import static org.apache.commons.lang.StringUtils.substringBetween;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeAssertionTokens.*;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.$VALUE;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.JSON_CONTENT;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.JSON_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.YAML_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.utils.FieldTypeConversionUtils.deepTypeCast;
import static org.jsmart.zerocode.core.utils.FieldTypeConversionUtils.fieldTypes;
import static org.jsmart.zerocode.core.utils.PropertiesProviderUtils.loadAbsoluteProperties;
import static org.jsmart.zerocode.core.utils.SmartUtils.isValidAbsolutePath;
import static org.jsmart.zerocode.core.utils.SmartUtils.readJsonAsString;
import static org.jsmart.zerocode.core.utils.SmartUtils.readYamlAsString;
import static org.jsmart.zerocode.core.utils.SmartUtils.checkDigNeeded;;
import static org.jsmart.zerocode.core.utils.SmartUtils.getJsonFilePhToken;;
import static org.jsmart.zerocode.core.utils.TokenUtils.getTestCaseTokens;
import static org.jsmart.zerocode.core.utils.TokenUtils.populateParamMap;
import static org.slf4j.LoggerFactory.getLogger;
Expand Down Expand Up @@ -337,6 +347,39 @@ public List<FieldAssertionMatcher> assertAllAndReturnFailed(List<JsonAsserter> a
return failedReports;
}

/**
* Resolves JSON.CONTENT as object or array
*
* First the logic checks if dig-deep needed to avoid unwanted recursions. If not needed, the step definition is
* returned intact. Otherwise calls the dig deep method to perform the operation.
*
* @param thisStep
* @return The effective step definition
*/
@Override
public Step resolveJsonContent(Step thisStep, ScenarioExecutionState scenarioExecutionState) {
try {
if (!checkDigNeeded(mapper, thisStep, JSON_CONTENT)) {
return thisStep;
}

JsonNode stepNode = mapper.convertValue(thisStep, JsonNode.class);

Map<String, Object> stepMap = mapper.readValue(stepNode.toString(), new TypeReference<Map<String, Object>>() {
});

digReplaceContent(stepMap, scenarioExecutionState);

JsonNode jsonStepNode = mapper.valueToTree(stepMap);

return mapper.treeToValue(jsonStepNode, Step.class);

} catch (Exception e) {
LOGGER.error("Json content reading exception - {}", e.getMessage());
throw new RuntimeException("Json content reading exception. Details - " + e);
}
}

private void loadAnnotatedHostProperties() {
try {
if(isValidAbsolutePath(hostFileName)){
Expand Down Expand Up @@ -408,4 +451,39 @@ private boolean hasNoTypeCast(String resolvedJson) {
}


void digReplaceContent(Map<String, Object> map, ScenarioExecutionState scenarioExecutionState) {
map.entrySet().forEach(entry -> {
Object value = entry.getValue();

if (value instanceof Map) {
digReplaceContent((Map<String, Object>) value, scenarioExecutionState);
} else {
LOGGER.debug("Leaf node found = {}, checking for any json content...", value);
if (value != null && (value.toString().contains(JSON_CONTENT))) {
LOGGER.debug("Found JSON content place holder = {}. Replacing with content", value);
String valueString = value.toString();
String token = getJsonFilePhToken(valueString);

if (token != null && (token.startsWith(JSON_CONTENT))) {
try {
String resolvedRequestJson = resolveStringJson(
"${" + token.substring(JSON_CONTENT.length()) + "}",
scenarioExecutionState.getResolvedScenarioState());
resolvedRequestJson = resolvedRequestJson.replaceAll("\\\\", "");
try {
JsonNode jsonNode = mapper.readTree(resolvedRequestJson);
entry.setValue(jsonNode);
} catch (JsonParseException e) {
//value is not a json string, but a string value
entry.setValue(resolvedRequestJson);
}
} catch (Exception exx) {
LOGGER.error("External file reference exception - {}", exx.getMessage());
throw new RuntimeException(exx);
}
}
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@

import com.google.inject.name.Named;
import org.jsmart.zerocode.core.domain.Step;
import org.jsmart.zerocode.core.utils.SmartUtils;
import org.slf4j.Logger;

import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.JSON_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.YAML_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.utils.SmartUtils.readJsonAsString;
import static org.jsmart.zerocode.core.utils.SmartUtils.readYamlAsString;
import static org.jsmart.zerocode.core.utils.SmartUtils.checkDigNeeded;
import static org.jsmart.zerocode.core.utils.SmartUtils.getJsonFilePhToken;
import static org.jsmart.zerocode.core.utils.TokenUtils.getTestCaseTokens;
import static org.slf4j.LoggerFactory.getLogger;

Expand Down Expand Up @@ -69,7 +72,7 @@ public Step resolveExtJsonFile(Step thisStep) {

try {

if (!checkDigNeeded(thisStep)) {
if (!checkDigNeeded(objectMapper, thisStep, JSON_PAYLOAD_FILE, YAML_PAYLOAD_FILE)) {
return thisStep;
}

Expand Down Expand Up @@ -176,22 +179,4 @@ else if (token != null && token.startsWith(OTHER_FILE)) {
}
});
}

private String getJsonFilePhToken(String valueString) {
if (valueString != null) {
List<String> allTokens = getTestCaseTokens(valueString);
if (allTokens != null && !allTokens.isEmpty()) {
return allTokens.get(0);
}
}
return null;
}

boolean checkDigNeeded(Step thisStep) throws JsonProcessingException {
String stepJson = objectMapper.writeValueAsString(thisStep);
List<String> allTokens = getTestCaseTokens(stepJson);

return allTokens.toString().contains(JSON_PAYLOAD_FILE) || allTokens.toString().contains(YAML_PAYLOAD_FILE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class ZeroCodeValueTokens {
public static final String SYSTEM_ENV = "SYSTEM.ENV:";
public static final String $VALUE = ".$VALUE";
public static final String ABS_PATH = "ABS.PATH:";
public static final String JSON_CONTENT = "JSON.CONTENT:";
Copy link
Owner

Choose a reason for hiding this comment

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

Can you please add some Unit tests?

Then, add integration tests after that.



public static Map<String, Object> globalTokenCache = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ private Boolean executeRetryWithSteps(RunNotifier notifier,
ScenarioExecutionState scenarioExecutionState,
ScenarioSpec scenario, Step thisStep) {
thisStep = extFileProcessor.resolveExtJsonFile(thisStep);
thisStep = zeroCodeAssertionsProcessor.resolveJsonContent(thisStep, scenarioExecutionState);

List<Step> thisSteps = extFileProcessor.createFromStepFile(thisStep, thisStep.getId());
if(null == thisSteps || thisSteps.isEmpty()) thisSteps.add(thisStep);
Boolean wasExecSuccess = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.text.StrSubstitutor;
import org.jsmart.zerocode.core.di.provider.ObjectMapperProvider;
import org.jsmart.zerocode.core.domain.ScenarioSpec;
import org.jsmart.zerocode.core.domain.Step;
import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -39,7 +39,10 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher.aMatchingMessage;
import static org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher.aNotMatchingMessage;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.JSON_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.YAML_PAYLOAD_FILE;
import static org.jsmart.zerocode.core.utils.PropertiesProviderUtils.loadAbsoluteProperties;
import static org.jsmart.zerocode.core.utils.TokenUtils.getTestCaseTokens;
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static org.skyscreamer.jsonassert.JSONCompareMode.STRICT;

Expand Down Expand Up @@ -240,4 +243,42 @@ public static String getEnvPropertyValue(String envPropertyKey) {
return System.getenv(envPropertyKey);
}
}

/**
*
* @param thisStep --> Currently executing step
* @param tokenString --> JSON_PAYLAOD_FILE or JSON_CONTENT
* @return if there is a match for the token, then the json traversal will happen
* @throws JsonProcessingException
*/
public static boolean checkDigNeeded(ObjectMapper mapper, Step thisStep, String tokenString) throws JsonProcessingException {
String stepJson = mapper.writeValueAsString(thisStep);
List<String> allTokens = getTestCaseTokens(stepJson);

return allTokens.toString().contains(tokenString);
}
public static boolean checkDigNeeded(ObjectMapper mapper, Step thisStep, String tokenString, String alternateTokenString) throws JsonProcessingException {
String stepJson = mapper.writeValueAsString(thisStep);
List<String> allTokens = getTestCaseTokens(stepJson);

return allTokens.toString().contains(tokenString) || allTokens.toString().contains(alternateTokenString);
}

/**
* Retrieves the first token from the given value string that matches the format "${token}".
* Ph = Placeholder (e.g. ${JSON.FILE:unit_test_files/filebody_unit_test/common/common_content.json} )
*
* @param valueString The string from which to extract the jsonfile path
* @return The extracted token, or null if no token is found
*/
public static String getJsonFilePhToken(String valueString) {
if (valueString != null) {
List<String> allTokens = getTestCaseTokens(valueString);
if (allTokens != null && !allTokens.isEmpty()) {
return allTokens.get(0);
}
}
return null;
}

}
Loading
Loading