allTokens = getTestCaseTokens(stepJson);
-
- return allTokens.toString().contains(JSON_PAYLOAD_FILE) || allTokens.toString().contains(YAML_PAYLOAD_FILE);
- }
-
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeParameterizedProcessorImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeParameterizedProcessorImpl.java
index 63466366f..7c899c664 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeParameterizedProcessorImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeParameterizedProcessorImpl.java
@@ -4,17 +4,19 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.univocity.parsers.csv.CsvParser;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.jsmart.zerocode.core.domain.ScenarioSpec;
+import org.slf4j.Logger;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
-import org.apache.commons.lang.text.StrSubstitutor;
-import org.jsmart.zerocode.core.domain.ScenarioSpec;
-import org.slf4j.Logger;
-import static org.jsmart.zerocode.core.di.provider.CsvParserProvider.LINE_SEPARATOR;
import static org.jsmart.zerocode.core.constants.ZerocodeConstants.DSL_FORMAT;
+import static org.jsmart.zerocode.core.di.provider.CsvParserProvider.LINE_SEPARATOR;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -25,17 +27,17 @@
*
* Parameters can be
* "parameterized": [
- * 200,
- * "Hello",
- * true
+ * 200,
+ * "Hello",
+ * true
* ]
*
* -or-
*
* "parameterizedCsv": [
- * "1, 2, 200",
- * "11, 22, 400",
- * "21, 31, 500"
+ * "1, 2, 200",
+ * "11, 22, 400",
+ * "21, 31, 500"
* ]
*
* In each the above cases, the step will execute 3 times.
@@ -64,7 +66,7 @@ public ZeroCodeParameterizedProcessorImpl(ObjectMapper objectMapper, CsvParser c
@Override
public ScenarioSpec resolveParameterized(ScenarioSpec scenario, int iteration) {
- if(scenario.getParameterized() == null){
+ if (scenario.getParameterized() == null) {
return scenario;
@@ -72,7 +74,7 @@ public ScenarioSpec resolveParameterized(ScenarioSpec scenario, int iteration) {
return resolveParamsValues(scenario, iteration);
- } else if (scenario.getParameterized().getCsvSource() != null) {
+ } else if (CollectionUtils.isNotEmpty(scenario.getParameterized().getCsvSource())) {
return resolveParamsCsv(scenario, iteration);
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/tokens/ZeroCodeValueTokens.java b/core/src/main/java/org/jsmart/zerocode/core/engine/tokens/ZeroCodeValueTokens.java
index 496fda747..f73b0b072 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/tokens/ZeroCodeValueTokens.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/tokens/ZeroCodeValueTokens.java
@@ -31,11 +31,16 @@ 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:";
+ public static final String MASKED = "MASKED:";
+ public static final String MASKED_STR = "***masked***";
+
public static Map globalTokenCache = new HashMap<>();
public static List getKnownTokens() {
return asList(
+ MASKED,
PREFIX_ASU,
RANDOM_NUMBER,
GLOBAL_RANDOM_NUMBER,
diff --git a/core/src/main/java/org/jsmart/zerocode/core/httpclient/utils/FileUploadUtils.java b/core/src/main/java/org/jsmart/zerocode/core/httpclient/utils/FileUploadUtils.java
index 8774de198..3c86220fe 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/httpclient/utils/FileUploadUtils.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/httpclient/utils/FileUploadUtils.java
@@ -57,7 +57,7 @@ public static void buildOtherRequestParams(Map fileFieldNameValu
if (entry.getKey().equals(FILES_FIELD) || entry.getKey().equals(BOUNDARY_FIELD)) {
continue;
}
- multipartEntityBuilder.addPart(entry.getKey(), new StringBody((String) entry.getValue(), TEXT_PLAIN));
+ multipartEntityBuilder.addPart(entry.getKey(), new StringBody(entry.getValue().toString(), TEXT_PLAIN));
}
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/ConsumerLocalConfigs.java b/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/ConsumerLocalConfigs.java
index 865992518..86b49df10 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/ConsumerLocalConfigs.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/ConsumerLocalConfigs.java
@@ -23,6 +23,9 @@ public class ConsumerLocalConfigs {
private final String protoClassType;
private final Boolean cacheByTopic;
private final String filterByJsonPath;
+ private final String seekEpoch;
+ private final SeekTimestamp seekTimestamp;
+
@JsonCreator
public ConsumerLocalConfigs(
@@ -36,9 +39,11 @@ public ConsumerLocalConfigs(
@JsonProperty("pollingTime") Long pollingTime,
@JsonProperty("cacheByTopic") Boolean cacheByTopic,
@JsonProperty("filterByJsonPath") String filterByJsonPath,
- @JsonProperty("seek") String seek) {
+ @JsonProperty("seek") String seek,
+ @JsonProperty("seekEpoch") String seekEpoch,
+ @JsonProperty("seekTimestamp") SeekTimestamp seekTimestamp) {
this.recordType = recordType;
- this.protoClassType= protobufMessageClassType;
+ this.protoClassType = protobufMessageClassType;
this.fileDumpTo = fileDumpTo;
this.commitAsync = commitAsync;
this.commitSync = commitSync;
@@ -48,39 +53,45 @@ public ConsumerLocalConfigs(
this.cacheByTopic = cacheByTopic;
this.filterByJsonPath = filterByJsonPath;
this.seek = seek;
+ this.seekEpoch = seekEpoch;
+ this.seekTimestamp = seekTimestamp;
}
-
+
public ConsumerLocalConfigs(
- String recordType,
- String fileDumpTo,
- Boolean commitAsync,
+ String recordType,
+ String fileDumpTo,
+ Boolean commitAsync,
Boolean commitSync,
Boolean showRecordsConsumed,
Integer maxNoOfRetryPollsOrTimeouts,
Long pollingTime,
Boolean cacheByTopic,
String filterByJsonPath,
- String seek) {
- this(recordType, null,
+ String seek,
+ String seekEpoch,
+ SeekTimestamp seekTimestamp) {
+ this(recordType, null,
fileDumpTo,
commitAsync,
commitSync,
showRecordsConsumed,
maxNoOfRetryPollsOrTimeouts,
- pollingTime,
+ pollingTime,
cacheByTopic,
filterByJsonPath,
- seek);
+ seek,
+ seekEpoch,
+ seekTimestamp);
}
public String getRecordType() {
return recordType != null ? recordType : RAW;
}
-
- public String getProtoClassType() {
- return protoClassType;
- }
+
+ public String getProtoClassType() {
+ return protoClassType;
+ }
public String getFileDumpTo() {
@@ -119,6 +130,14 @@ public String getSeek() {
return seek;
}
+ public String getSeekEpoch() {
+ return seekEpoch;
+ }
+
+ public SeekTimestamp getSeekTimestamp() {
+ return seekTimestamp;
+ }
+
@JsonIgnore
public String[] getSeekTopicPartitionOffset() {
return seek.split(",");
@@ -130,7 +149,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ConsumerLocalConfigs that = (ConsumerLocalConfigs) o;
return Objects.equals(recordType, that.recordType) &&
- Objects.equals(protoClassType, that.protoClassType) &&
+ Objects.equals(protoClassType, that.protoClassType) &&
Objects.equals(fileDumpTo, that.fileDumpTo) &&
Objects.equals(commitAsync, that.commitAsync) &&
Objects.equals(commitSync, that.commitSync) &&
@@ -139,13 +158,14 @@ public boolean equals(Object o) {
Objects.equals(pollingTime, that.pollingTime) &&
Objects.equals(filterByJsonPath, that.filterByJsonPath) &&
Objects.equals(cacheByTopic, that.cacheByTopic) &&
- Objects.equals(seek, that.seek);
+ Objects.equals(seek, that.seek) &&
+ Objects.equals(seekEpoch, that.seekEpoch);
}
@Override
public int hashCode() {
- return Objects.hash(recordType, fileDumpTo, commitAsync, commitSync, showRecordsConsumed, maxNoOfRetryPollsOrTimeouts, pollingTime,cacheByTopic, filterByJsonPath, seek);
+ return Objects.hash(recordType, fileDumpTo, commitAsync, commitSync, showRecordsConsumed, maxNoOfRetryPollsOrTimeouts, pollingTime, cacheByTopic, filterByJsonPath, seek);
}
@Override
@@ -162,6 +182,8 @@ public String toString() {
", cacheByTopic=" + cacheByTopic +
", filterByJsonPath=" + filterByJsonPath +
", seek=" + seek +
+ ", seekEpoch=" + seekEpoch +
+ ", seekTimestamp=" + seekTimestamp +
'}';
}
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/SeekTimestamp.java b/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/SeekTimestamp.java
new file mode 100644
index 000000000..6381ee252
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/kafka/consume/SeekTimestamp.java
@@ -0,0 +1,38 @@
+package org.jsmart.zerocode.core.kafka.consume;
+
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class SeekTimestamp {
+
+ private final String timestamp;
+ private final String format;
+
+
+ @JsonCreator
+ public SeekTimestamp(
+ @JsonProperty("timestamp") String timestamp,
+ @JsonProperty("format") String format) {
+ this.timestamp = timestamp;
+ this.format = format;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ @Override
+ public String toString() {
+ return "SeekTimestamp{" +
+ "timestamp='" + timestamp + '\'' +
+ ", format='" + format + '\'' +
+ '}';
+ }
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/kafka/helper/KafkaConsumerHelper.java b/core/src/main/java/org/jsmart/zerocode/core/kafka/helper/KafkaConsumerHelper.java
index 1e5a76ae2..0ea2d4424 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/kafka/helper/KafkaConsumerHelper.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/kafka/helper/KafkaConsumerHelper.java
@@ -1,9 +1,32 @@
package org.jsmart.zerocode.core.kafka.helper;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Resources;
+import com.google.gson.Gson;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Message;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.protobuf.util.JsonFormat;
+import com.jayway.jsonpath.JsonPath;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isEmpty;
+import static org.apache.commons.lang3.StringUtils.isNumeric;
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.clients.consumer.OffsetAndTimestamp;
+import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.header.Header;
+import org.apache.kafka.common.header.Headers;
+import org.jsmart.zerocode.core.di.provider.GsonSerDeProvider;
+import org.jsmart.zerocode.core.di.provider.ObjectMapperProvider;
+import org.jsmart.zerocode.core.kafka.KafkaConstants;
import static org.jsmart.zerocode.core.kafka.KafkaConstants.AVRO;
import static org.jsmart.zerocode.core.kafka.KafkaConstants.DEFAULT_POLLING_TIME_MILLI_SEC;
import static org.jsmart.zerocode.core.kafka.KafkaConstants.JSON;
@@ -11,58 +34,44 @@
import static org.jsmart.zerocode.core.kafka.KafkaConstants.PROTO;
import static org.jsmart.zerocode.core.kafka.KafkaConstants.RAW;
import static org.jsmart.zerocode.core.kafka.common.KafkaCommonUtils.resolveValuePlaceHolders;
+import org.jsmart.zerocode.core.kafka.consume.ConsumerLocalConfigs;
+import org.jsmart.zerocode.core.kafka.consume.ConsumerLocalConfigsWrap;
+import org.jsmart.zerocode.core.kafka.consume.SeekTimestamp;
+import org.jsmart.zerocode.core.kafka.receive.ConsumerCommonConfigs;
+import org.jsmart.zerocode.core.kafka.receive.message.ConsumerJsonRecord;
+import org.jsmart.zerocode.core.kafka.receive.message.ConsumerJsonRecords;
+import org.jsmart.zerocode.core.kafka.receive.message.ConsumerRawRecords;
import static org.jsmart.zerocode.core.utils.SmartUtils.prettyPrintJson;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.Set;
-
-import com.jayway.jsonpath.JsonPath;
-import org.apache.kafka.clients.consumer.Consumer;
-import org.apache.kafka.clients.consumer.ConsumerRecord;
-import org.apache.kafka.clients.consumer.ConsumerRecords;
-import org.apache.kafka.clients.consumer.KafkaConsumer;
-import org.apache.kafka.common.TopicPartition;
-import org.apache.kafka.common.header.Header;
-import org.apache.kafka.common.header.Headers;
-import org.jsmart.zerocode.core.di.provider.GsonSerDeProvider;
-import org.jsmart.zerocode.core.di.provider.ObjectMapperProvider;
-import org.jsmart.zerocode.core.kafka.KafkaConstants;
-import org.jsmart.zerocode.core.kafka.consume.ConsumerLocalConfigs;
-import org.jsmart.zerocode.core.kafka.consume.ConsumerLocalConfigsWrap;
-import org.jsmart.zerocode.core.kafka.receive.ConsumerCommonConfigs;
-import org.jsmart.zerocode.core.kafka.receive.message.ConsumerJsonRecord;
-import org.jsmart.zerocode.core.kafka.receive.message.ConsumerJsonRecords;
-import org.jsmart.zerocode.core.kafka.receive.message.ConsumerRawRecords;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.io.Resources;
-import com.google.gson.Gson;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.Message;
-import com.google.protobuf.MessageOrBuilder;
-import com.google.protobuf.util.JsonFormat;
+import java.util.function.Function;
+import java.util.stream.Collectors;
public class KafkaConsumerHelper {
+ public static final String CONSUMER = "CONSUMER";
private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumerHelper.class);
private static final Gson gson = new GsonSerDeProvider().get();
private static final ObjectMapper objectMapper = new ObjectMapperProvider().get();
- public static final String CONSUMER = "CONSUMER";
public static Map consumerCacheByTopicMap = new HashMap<>();
public static Consumer createConsumer(String bootStrapServers, String consumerPropertyFile, String topic, Boolean consumerToBeCached) {
@@ -81,9 +90,9 @@ public static Consumer createConsumer(String bootStrapServers, String consumerPr
final Consumer consumer = new KafkaConsumer(properties);
consumer.subscribe(Collections.singletonList(topic));
- if(consumerToBeCached == true){
+ if (consumerToBeCached) {
consumerCacheByTopicMap.forEach((xTopic, xConsumer) -> {
- if(!xTopic.equals(topic)){
+ if (!xTopic.equals(topic)) {
// close the earlier consumer if in the same group for safety.
// (even if not in the same group, closing it anyway will not do any harm)
// Otherwise rebalance will fail while rejoining/joining the same group for a new consumer
@@ -111,18 +120,18 @@ public static Consumer createConsumer(String bootStrapServers, String consumerPr
public static ConsumerRecords initialPollWaitingForConsumerGroupJoin(Consumer consumer, ConsumerLocalConfigs effectiveLocalConfigs) {
- for (int run = 0; run < 50; run++) {
- if (!consumer.assignment().isEmpty()) {
- LOGGER.debug("==> WaitingForConsumerGroupJoin - Partition now assigned. No records yet consumed");
- return new ConsumerRecords(new HashMap());
- }
- LOGGER.debug("==> WaitingForConsumerGroupJoin - Partition not assigned. Polling once");
- ConsumerRecords records = consumer.poll(Duration.of(getPollTime(effectiveLocalConfigs), ChronoUnit.MILLIS));
- LOGGER.debug("==> WaitingForConsumerGroupJoin - polled records length={}", records.count());
- if (!records.isEmpty()) {
- return records;
- }
+ for (int run = 0; run < 50; run++) {
+ if (!consumer.assignment().isEmpty()) {
+ LOGGER.debug("==> WaitingForConsumerGroupJoin - Partition now assigned. No records yet consumed");
+ return new ConsumerRecords(new HashMap());
+ }
+ LOGGER.debug("==> WaitingForConsumerGroupJoin - Partition not assigned. Polling once");
+ ConsumerRecords records = consumer.poll(Duration.of(getPollTime(effectiveLocalConfigs), ChronoUnit.MILLIS));
+ LOGGER.debug("==> WaitingForConsumerGroupJoin - polled records length={}", records.count());
+ if (!records.isEmpty()) {
+ return records;
}
+ }
throw new RuntimeException("\n********* Kafka Consumer unable to join in time - try increasing consumer polling time setting *********\n");
}
@@ -135,6 +144,31 @@ public static void validateLocalConfigs(ConsumerLocalConfigs localConfigs) {
validateCommitFlags(localCommitSync, localCommitAsync);
validateSeekConfig(localConfigs);
+ validateSeekToTimestamp(localConfigs);
+ }
+ }
+
+ private static void validateSeekToTimestamp(ConsumerLocalConfigs localConfigs) {
+ String seekToTimestamp = localConfigs.getSeekEpoch();
+ if (isEmpty(seekToTimestamp)) {
+ if (isNumeric(seekToTimestamp) && (Long.parseLong(seekToTimestamp) > System.currentTimeMillis() || Long.parseLong(seekToTimestamp) < 0L)) {
+ throw new RuntimeException("\n------> 'seekEpoch' is not a valid epoch/Unix timestamp");
+ }
+ if (!isEmpty(localConfigs.getSeek()) && Objects.nonNull(localConfigs.getSeekTimestamp())) {
+ throw new RuntimeException("Only one of 'seek', 'seekEpoch' and 'seekTimestamp' should be provided, but not both. Please fix and rerun");
+ }
+ }
+ if (Objects.nonNull(localConfigs.getSeekTimestamp())) {
+ DateFormat dateFormat = new SimpleDateFormat(localConfigs.getSeekTimestamp().getFormat());
+ try {
+ Date date = dateFormat.parse(localConfigs.getSeekTimestamp().getTimestamp());
+ long epochMillis = date.toInstant().toEpochMilli();
+ if (epochMillis > System.currentTimeMillis() || epochMillis < 0L) {
+ throw new RuntimeException("\n------> 'seekTimestamp' is not a valid epoch/Unix timestamp " + epochMillis);
+ }
+ } catch (ParseException e) {
+ throw new RuntimeException("Timestamp and format provided in 'seekTimestamp' cannot be parsed ", e);
+ }
}
}
@@ -163,7 +197,9 @@ public static ConsumerLocalConfigs createEffective(ConsumerCommonConfigs consume
consumerCommon.getPollingTime(),
consumerCommon.getCacheByTopic(),
consumerCommon.getFilterByJsonPath(),
- consumerCommon.getSeek());
+ null,
+ null,
+ null);
}
// Handle recordType
@@ -188,9 +224,6 @@ public static ConsumerLocalConfigs createEffective(ConsumerCommonConfigs consume
// Handle pollingTime
String filterByJsonPath = ofNullable(consumerLocal.getFilterByJsonPath()).orElse(consumerCommon.getFilterByJsonPath());
- // Handle pollingTime
- String effectiveSeek = ofNullable(consumerLocal.getSeek()).orElse(consumerCommon.getSeek());
-
// Handle consumerCache by topic
Boolean effectiveConsumerCacheByTopic = ofNullable(consumerLocal.getCacheByTopic())
.orElse(consumerCommon.getCacheByTopic());
@@ -222,7 +255,9 @@ public static ConsumerLocalConfigs createEffective(ConsumerCommonConfigs consume
effectivePollingTime,
effectiveConsumerCacheByTopic,
filterByJsonPath,
- effectiveSeek);
+ consumerLocal.getSeek(),
+ consumerLocal.getSeekEpoch(),
+ consumerLocal.getSeekTimestamp());
}
public static ConsumerLocalConfigs readConsumerLocalTestProperties(String requestJsonWithConfigWrapped) {
@@ -264,7 +299,7 @@ public static void readJson(List jsonRecords,
Object key = thisRecord.key();
Object valueObj = thisRecord.value();
Headers headers = thisRecord.headers();
- String keyStr = thisRecord.key() != null ? thisRecord.key().toString() : "";
+ String keyStr = thisRecord.key() != null ? thisRecord.key().toString() : "";
String valueStr = consumerLocalConfig != null && KafkaConstants.PROTO.equalsIgnoreCase(consumerLocalConfig.getRecordType()) ? convertProtobufToJson(thisRecord, consumerLocalConfig) : valueObj.toString();
LOGGER.debug("\nRecord Key - {} , Record value - {}, Record partition - {}, Record offset - {}, Headers - {}",
key, valueStr, thisRecord.partition(), thisRecord.offset(), headers);
@@ -289,7 +324,7 @@ private static String convertProtobufToJson(ConsumerRecord thisRecord, ConsumerL
throw new IllegalArgumentException(
"[protoClassType] is required consumer config for recordType PROTO.");
}
- MessageOrBuilder builderOrMessage = (MessageOrBuilder) createMessageOrBuilder(
+ MessageOrBuilder builderOrMessage = createMessageOrBuilder(
consumerLocalConfig.getProtoClassType(), (byte[]) thisRecord.value());
try {
return JsonFormat.printer().includingDefaultValueFields().preservingProtoFieldNames().print(builderOrMessage);
@@ -301,10 +336,10 @@ private static String convertProtobufToJson(ConsumerRecord thisRecord, ConsumerL
private static MessageOrBuilder createMessageOrBuilder(String messageClass, byte[] value) {
try {
Class msgClass = (Class) Class.forName(messageClass);
- Method method = msgClass.getMethod("parseFrom", new Class[]{byte[].class});
+ Method method = msgClass.getMethod("parseFrom", byte[].class);
return (MessageOrBuilder) method.invoke(null, value);
} catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException | SecurityException
- | IllegalArgumentException | InvocationTargetException e) {
+ | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
@@ -316,7 +351,7 @@ public static String prepareResult(ConsumerLocalConfigs testConfigs,
String result;
- if (testConfigs != null && testConfigs.getShowRecordsConsumed() == false) {
+ if (testConfigs != null && !testConfigs.getShowRecordsConsumed()) {
int size = jsonRecords.size();
result = prettyPrintJson(gson.toJson(new ConsumerRawRecords(size == 0 ? rawRecords.size() : size)));
@@ -326,7 +361,7 @@ public static String prepareResult(ConsumerLocalConfigs testConfigs,
} else if (testConfigs != null && (JSON.equals(testConfigs.getRecordType()) || PROTO.equalsIgnoreCase(testConfigs.getRecordType()) || AVRO.equalsIgnoreCase(testConfigs.getRecordType()))) {
result = prettyPrintJson(objectMapper.writeValueAsString(new ConsumerJsonRecords(jsonRecords)));
- }else {
+ } else {
result = "{\"error\" : \"recordType Undecided, Please chose recordType as JSON or RAW\"}";
}
@@ -363,10 +398,10 @@ public static void handleCommitSyncAsync(Consumer consumer,
effectiveCommitAsync = localCommitAsync;
}
- if (effectiveCommitSync != null && effectiveCommitSync == true) {
+ if (effectiveCommitSync != null && effectiveCommitSync) {
consumer.commitSync();
- } else if (effectiveCommitAsync != null && effectiveCommitAsync == true) {
+ } else if (effectiveCommitAsync != null && effectiveCommitAsync) {
consumer.commitAsync();
} else {
@@ -378,32 +413,78 @@ public static void handleCommitSyncAsync(Consumer consumer,
// --------------------------------------------------------
}
- public static void handleSeekOffset(ConsumerLocalConfigs effectiveLocal, Consumer consumer) {
+ public static void handleSeek(ConsumerLocalConfigs effectiveLocal, Consumer consumer, String topicName) {
String seek = effectiveLocal.getSeek();
if (!isEmpty(seek)) {
- String[] seekParts = effectiveLocal.getSeekTopicPartitionOffset();
- String topic = seekParts[0];
- int partition = parseInt(seekParts[1]);
- long offset = parseLong(seekParts[2]);
+ handleSeekByOffset(effectiveLocal, consumer);
+ } else if (!isEmpty(effectiveLocal.getSeekEpoch())) {
+ handleSeekByEpoch(Long.parseLong(effectiveLocal.getSeekEpoch()), consumer, topicName);
+ } else if (Objects.nonNull(effectiveLocal.getSeekTimestamp())) {
+ handleSeekByTimestamp(effectiveLocal.getSeekTimestamp(), consumer, topicName);
+ }
+ }
+
+ private static void handleSeekByTimestamp(SeekTimestamp seekTimestamp, Consumer consumer, String topicName) {
+ if (Objects.nonNull(seekTimestamp)) {
+ DateFormat dateFormat = new SimpleDateFormat(seekTimestamp.getFormat());
+ Date date = null;
+ try {
+ date = dateFormat.parse(seekTimestamp.getTimestamp());
+ } catch (ParseException e) {
+ throw new RuntimeException("Could not parse timestamp", e);
+ }
+ handleSeekByEpoch(date.toInstant().toEpochMilli(), consumer, topicName);
+ }
+ }
+
+ private static void handleSeekByEpoch(Long epoch, Consumer consumer, String topicName) {
+ if (Objects.nonNull(epoch)) {
+ List partitionInfos = consumer.partitionsFor(topicName);
- TopicPartition topicPartition = new TopicPartition(topic, partition);
- Set topicPartitions = new HashSet<>();
- topicPartitions.add(topicPartition);
+ //fetch partitions on topic
+ List topicPartitions = partitionInfos.stream()
+ .map(info -> new TopicPartition(info.topic(), info.partition()))
+ .collect(Collectors.toList());
+ //fetch offsets for each partition-timestamp pair
+ Map topicPartitionTimestampMap = topicPartitions.stream()
+ .collect(Collectors.toMap(Function.identity(), ignore -> epoch));
+ Map topicPartitionOffsetAndTimestampMap = consumer.offsetsForTimes(topicPartitionTimestampMap);
+
+ //assign to fetched partitions
consumer.unsubscribe();
- consumer.assign(topicPartitions);
+ consumer.assign(topicPartitionOffsetAndTimestampMap.keySet());
- if (offset <= -1) {
- consumer.seekToEnd(topicPartitions);
- consumer.seek(topicPartition, consumer.position(topicPartition) + offset);
- } else {
- consumer.seek(topicPartition, offset);
+ //seek to fetched offsets for partitions
+ for (Map.Entry topicOffsetEntry : topicPartitionOffsetAndTimestampMap.entrySet()) {
+ consumer.seek(topicOffsetEntry.getKey(), topicOffsetEntry.getValue().offset());
}
}
}
+ private static void handleSeekByOffset(ConsumerLocalConfigs effectiveLocal, Consumer consumer) {
+ String[] seekParts = effectiveLocal.getSeekTopicPartitionOffset();
+ String topic = seekParts[0];
+ int partition = parseInt(seekParts[1]);
+ long offset = parseLong(seekParts[2]);
+
+ TopicPartition topicPartition = new TopicPartition(topic, partition);
+ Set topicPartitions = new HashSet<>();
+ topicPartitions.add(topicPartition);
+
+ consumer.unsubscribe();
+ consumer.assign(topicPartitions);
+
+ if (offset <= -1) {
+ consumer.seekToEnd(topicPartitions);
+ consumer.seek(topicPartition, consumer.position(topicPartition) + offset);
+ } else {
+ consumer.seek(topicPartition, offset);
+ }
+ }
+
private static void validateCommitFlags(Boolean commitSync, Boolean commitAsync) {
- if ((commitSync != null && commitAsync != null) && commitSync == true && commitAsync == true) {
+ if ((commitSync != null && commitAsync != null) && commitSync && commitAsync) {
throw new RuntimeException("\n********* Both commitSync and commitAsync can not be true *********\n");
}
}
@@ -419,7 +500,7 @@ private static void validateSeekConfig(ConsumerLocalConfigs localConfigs) {
}
private static Consumer getCachedConsumer(String topic, Boolean consumerToBeCached) {
- if(consumerToBeCached){
+ if (consumerToBeCached) {
return consumerCacheByTopicMap.get(topic);
}
return null;
diff --git a/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/ConsumerCommonConfigs.java b/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/ConsumerCommonConfigs.java
index 2b6640d4e..d34fe087c 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/ConsumerCommonConfigs.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/ConsumerCommonConfigs.java
@@ -49,11 +49,6 @@ public class ConsumerCommonConfigs {
@Named("consumer.filterByJsonPath")
private String filterByJsonPath;
- // TODO- Remove this from Global properties, as it doesn't make sense
- @Inject(optional = true)
- @Named("consumer.seek")
- private String seek;
-
public ConsumerCommonConfigs() {
}
@@ -66,8 +61,7 @@ public ConsumerCommonConfigs(Boolean commitSync,
Integer maxNoOfRetryPollsOrTimeouts,
Long pollingTime,
Boolean cacheByTopic,
- String filterByJsonPath,
- String seek
+ String filterByJsonPath
) {
this.commitSync = commitSync;
@@ -80,7 +74,6 @@ public ConsumerCommonConfigs(Boolean commitSync,
this.pollingTime = pollingTime;
this.cacheByTopic = cacheByTopic;
this.filterByJsonPath = filterByJsonPath;
- this.seek = seek;
}
public ConsumerCommonConfigs(Boolean commitSync,
@@ -91,8 +84,7 @@ public ConsumerCommonConfigs(Boolean commitSync,
Integer maxNoOfRetryPollsOrTimeouts,
Long pollingTime,
Boolean cacheByTopic,
- String filterByJsonPath,
- String seek
+ String filterByJsonPath
) {
this(commitSync,
@@ -104,8 +96,7 @@ public ConsumerCommonConfigs(Boolean commitSync,
maxNoOfRetryPollsOrTimeouts,
pollingTime,
cacheByTopic,
- filterByJsonPath,
- seek);
+ filterByJsonPath);
}
public Boolean getCommitSync() {
@@ -136,9 +127,6 @@ public String getRecordType() {
return recordType;
}
- public String getSeek() {
- return seek;
- }
public String getProtoClassType() {
return protoClassType;
}
@@ -165,7 +153,6 @@ public String toString() {
", pollingTime=" + pollingTime +
", cacheByTopic=" + cacheByTopic +
", filterByJsonPath=" + filterByJsonPath +
- ", seek=" + seek +
'}';
}
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/KafkaReceiver.java b/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/KafkaReceiver.java
index 37f893161..327250242 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/KafkaReceiver.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/kafka/receive/KafkaReceiver.java
@@ -25,7 +25,7 @@
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.getMaxTimeOuts;
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.getPollTime;
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.handleCommitSyncAsync;
-import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.handleSeekOffset;
+import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.handleSeek;
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.initialPollWaitingForConsumerGroupJoin;
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.prepareResult;
import static org.jsmart.zerocode.core.kafka.helper.KafkaConsumerHelper.readConsumerLocalTestProperties;
@@ -62,7 +62,7 @@ public String receive(String kafkaServers, String topicName, String requestJsonW
int noOfTimeOuts = 0;
- handleSeekOffset(effectiveLocal, consumer);
+ handleSeek(effectiveLocal, consumer, topicName);
LOGGER.debug("initial polling to trigger ConsumerGroupJoin");
diff --git a/core/src/main/java/org/jsmart/zerocode/core/logbuilder/ZerocodeCorrelationshipLogger.java b/core/src/main/java/org/jsmart/zerocode/core/logbuilder/ZerocodeCorrelationshipLogger.java
index 974ab29fa..53c807b7c 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/logbuilder/ZerocodeCorrelationshipLogger.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/logbuilder/ZerocodeCorrelationshipLogger.java
@@ -16,6 +16,7 @@
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.RESULT_FAIL;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.RESULT_PASS;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TEST_STEP_CORRELATION_ID;
+import static org.jsmart.zerocode.core.utils.TokenUtils.getMasksReplaced;
public class ZerocodeCorrelationshipLogger {
private static final String DISPLAY_DEMARCATION_ = "\n--------- " + TEST_STEP_CORRELATION_ID + " %s ---------";
@@ -139,11 +140,12 @@ public void print() {
buildResponseDelay();
String customLog = responseLogBuilder.getCustomLog();
+ String assertionsWithMaskRemoved = getMasksReplaced(responseLogBuilder.getAssertion());
logger.warn(format("%s %s \n*Response delay:%s milli-secs \n%s \n%s \n-done-\n",
requestLogBuilder.toString(),
responseLogBuilder.toString(),
responseDelay,
- "---------> Expected Response: <----------\n" + responseLogBuilder.getAssertion(),
+ "---------> Expected Response: <----------\n" + assertionsWithMaskRemoved,
customLog == null ? "" : "---------> Custom Log: <----------\n" +customLog
)
);
diff --git a/core/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java b/core/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
index db5a3d4af..e80fb0760 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/report/ZeroCodeReportGeneratorImpl.java
@@ -12,12 +12,7 @@
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.google.inject.Inject;
import com.google.inject.name.Named;
-import org.apache.commons.lang.StringUtils;
-import org.jsmart.zerocode.core.domain.builders.ExtentReportsFactory;
-import org.jsmart.zerocode.core.domain.builders.HighChartColumnHtmlBuilder;
-import org.jsmart.zerocode.core.domain.builders.ZeroCodeChartKeyValueArrayBuilder;
-import org.jsmart.zerocode.core.domain.builders.ZeroCodeChartKeyValueBuilder;
-import org.jsmart.zerocode.core.domain.builders.ZeroCodeCsvReportBuilder;
+import org.jsmart.zerocode.core.domain.builders.*;
import org.jsmart.zerocode.core.domain.reports.ZeroCodeExecResult;
import org.jsmart.zerocode.core.domain.reports.ZeroCodeReport;
import org.jsmart.zerocode.core.domain.reports.ZeroCodeReportStep;
@@ -35,21 +30,8 @@
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
-import static org.apache.commons.lang.StringUtils.substringBetween;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.AUTHOR_MARKER_NEW;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.CATEGORY_MARKER;
+import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.*;
import static org.jsmart.zerocode.core.domain.builders.ExtentReportsFactory.getReportName;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.ANONYMOUS_CAT;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.AUTHOR_MARKER_OLD;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.DEFAULT_REGRESSION_CATEGORY;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.HIGH_CHART_HTML_FILE_NAME;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.LINK_LABEL_NAME;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.RESULT_PASS;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TARGET_FILE_NAME;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TARGET_FULL_REPORT_CSV_FILE_NAME;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TARGET_FULL_REPORT_DIR;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TARGET_REPORT_DIR;
-import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.TEST_STEP_CORRELATION_ID;
public class ZeroCodeReportGeneratorImpl implements ZeroCodeReportGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeReportGeneratorImpl.class);
@@ -115,10 +97,21 @@ public void generateExtentReport() {
thisReport.getResults().forEach(thisScenario -> {
ExtentTest test = extentReports.createTest(thisScenario.getScenarioName());
+
+ // Assign Category
test.assignCategory(DEFAULT_REGRESSION_CATEGORY); //Super set
- test.assignCategory(optionalCategory(thisScenario.getScenarioName())); //Sub sets
+ String[] hashTagsArray = optionalCategories(thisScenario.getScenarioName()).toArray(new String[0]);
+ if(hashTagsArray.length > 0) {
+ test.assignCategory(hashTagsArray); //Sub categories
+ }
+
+ // Assign Authors
+ test.assignAuthor(DEFAULT_REGRESSION_AUTHOR); //Super set
+ String[] authorsArray = optionalAuthors(thisScenario.getScenarioName()).toArray(new String[0]);
+ if(authorsArray.length > 0) {
+ test.assignAuthor(authorsArray); //Sub authors
+ }
- test.assignAuthor(optionalAuthor(thisScenario.getScenarioName()));
List thisScenarioUniqueSteps = getUniqueSteps(thisScenario.getSteps());
thisScenarioUniqueSteps.forEach(thisStep -> {
test.getModel().setStartTime(utilDateOf(thisStep.getRequestTimeStamp()));
@@ -166,42 +159,33 @@ public void linkToSpikeChartIfEnabled() {
/**
* @param scenarioName String containing a name of an author
- * @return author of the test scenario
+ * @return authors of the test scenario
*/
- protected String optionalAuthor(String scenarioName) {
- String authorName = deriveName(scenarioName, AUTHOR_MARKER_OLD);
- authorName = ANONYMOUS_CAT.equals(authorName) ? deriveName(scenarioName, AUTHOR_MARKER_NEW) : authorName;
- return authorName;
+ protected List optionalAuthors(String scenarioName) {
+ return deriveNames(scenarioName, AUTHOR_MARKER_NEW);
}
/**
- * @param scenarioName String containing hash tags of a category
- * @return category of the test scenario
+ * @param scenarioName String containing hashtags of a category
+ * @return hashtags aka categories of the test scenario
*/
- protected String optionalCategory(String scenarioName) {
- return deriveName(scenarioName, CATEGORY_MARKER);
+ protected List optionalCategories(String scenarioName) {
+ return deriveNames(scenarioName, CATEGORY_MARKER);
}
- private String deriveName(String scenarioName, String marker) {
- String authorName = substringBetween(scenarioName, marker, marker);
-
- if (authorName == null) {
- authorName = substringBetween(scenarioName, marker, ",");
- }
-
- if (authorName == null) {
- authorName = substringBetween(scenarioName, marker, " ");
- }
-
- if (authorName == null) {
- authorName = scenarioName.substring(scenarioName.lastIndexOf(marker) + marker.length());
- }
-
- if (scenarioName.lastIndexOf(marker) == -1 || StringUtils.isEmpty(authorName)) {
- authorName = ANONYMOUS_CAT;
+ private List deriveNames(String scenarioName, String marker) {
+ List nameList = new ArrayList<>();
+ for(String thisName : scenarioName.trim().split(" ")){
+ if(thisName.startsWith(marker) && !thisName.startsWith(AUTHOR_MARKER_OLD)){
+ nameList.add(thisName);
+ }
+ // Depreciated, but still supports. Remove this via a new ticket
+ if(thisName.startsWith(AUTHOR_MARKER_OLD)){
+ nameList.add(thisName);
+ }
}
+ return nameList;
- return authorName;
}
protected String onlyScenarioName(String scenarioName) {
diff --git a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
index 4f7e7d05b..3bef7ba2d 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
@@ -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 thisSteps = extFileProcessor.createFromStepFile(thisStep, thisStep.getId());
if(null == thisSteps || thisSteps.isEmpty()) thisSteps.add(thisStep);
Boolean wasExecSuccess = null;
@@ -412,6 +414,13 @@ private String executeApi(String logPrefixRelationshipId,
// --------------------------------
url = zeroCodeAssertionsProcessor.resolveStringJson(url, scenarioExecutionState.getResolvedScenarioState());
+ // ------------------------------------------------
+ // 1) Removed the MASKED wrapper for API execution (For logging)
+ // 2) Replace the MASKED field with masked content (For API executions)
+ // ------------------------------------------------
+ String resolvedRequestJsonMaskRemoved = zeroCodeAssertionsProcessor.fieldMasksRemoved(resolvedRequestJson);
+ String resolvedRequestJsonMaskApplied = zeroCodeAssertionsProcessor.fieldMasksApplied(resolvedRequestJson);
+
final LocalDateTime requestTimeStamp = LocalDateTime.now();
String executionResult;
@@ -426,9 +435,9 @@ private String executeApi(String logPrefixRelationshipId,
.url(url)
.method(operationName)
.id(stepId)
- .request(prettyPrintJson(resolvedRequestJson));
+ .request(prettyPrintJson(resolvedRequestJsonMaskApplied));
- executionResult = apiExecutor.executeHttpApi(url, operationName, resolvedRequestJson);
+ executionResult = apiExecutor.executeHttpApi(url, operationName, resolvedRequestJsonMaskRemoved);
break;
case JAVA_CALL:
@@ -439,10 +448,10 @@ private String executeApi(String logPrefixRelationshipId,
.id(stepId)
.url(url)
.method(operationName)
- .request(prettyPrintJson(resolvedRequestJson));
+ .request(prettyPrintJson(resolvedRequestJsonMaskApplied));
url = apiTypeUtils.getQualifiedJavaApi(url);
- executionResult = apiExecutor.executeJavaOperation(url, operationName, resolvedRequestJson);
+ executionResult = apiExecutor.executeJavaOperation(url, operationName, resolvedRequestJsonMaskRemoved);
break;
case KAFKA_CALL:
@@ -457,10 +466,10 @@ private String executeApi(String logPrefixRelationshipId,
.url(url)
.method(operationName.toUpperCase())
.id(stepId)
- .request(prettyPrintJson(resolvedRequestJson));
+ .request(prettyPrintJson(resolvedRequestJsonMaskApplied));
String topicName = url.substring(KAFKA_TOPIC.length());
- executionResult = apiExecutor.executeKafkaService(kafkaServers, topicName, operationName, resolvedRequestJson, scenarioExecutionState);
+ executionResult = apiExecutor.executeKafkaService(kafkaServers, topicName, operationName, resolvedRequestJsonMaskRemoved, scenarioExecutionState);
break;
case NONE:
@@ -471,14 +480,14 @@ private String executeApi(String logPrefixRelationshipId,
.id(stepId)
.url(url)
.method(operationName)
- .request(prettyPrintJson(resolvedRequestJson));
+ .request(prettyPrintJson(resolvedRequestJsonMaskApplied));
- executionResult = prettyPrintJson(resolvedRequestJson);
+ executionResult = prettyPrintJson(resolvedRequestJsonMaskApplied);
break;
default:
throw new RuntimeException("Oops! API Type Undecided. If it is intentional, " +
- "then keep the value as empty to receive the request in the response");
+ "then keep the value as empty to receive the request as response");
}
return executionResult;
@@ -532,6 +541,8 @@ private int deriveScenarioLoopTimes(ScenarioSpec scenario) {
private List compareStepResults(Step thisStep, String actualResult, String expectedResult, String resolvedScenarioState) {
List failureResults = new ArrayList<>();
+ expectedResult = zeroCodeAssertionsProcessor.fieldMasksRemoved(expectedResult);
+
// --------------------
// Validators (pyrest)
// --------------------
diff --git a/core/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java b/core/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
index 528659a26..819746ff0 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
@@ -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;
@@ -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;
@@ -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 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 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 allTokens = getTestCaseTokens(valueString);
+ if (allTokens != null && !allTokens.isEmpty()) {
+ return allTokens.get(0);
+ }
+ }
+ return null;
+ }
+
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java b/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
index e54a5036d..367c1ff42 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
@@ -19,23 +19,7 @@
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.StringEscapeUtils.escapeJava;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.ABS_PATH;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.GLOBAL_RANDOM_NUMBER;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.GQL_FILE;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.LOCALDATETIME_NOW;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.LOCALDATE_TODAY;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_NUMBER;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_NUMBER_FIXED;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_STRING_ALPHA;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_STRING_ALPHA_NUMERIC;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_UU_ID;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.RANDOM_UU_ID_FIXED;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.STATIC_ALPHABET;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.SYSTEM_ENV;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.SYSTEM_PROPERTY;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.XML_FILE;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.getKnownTokens;
-import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.globalTokenCache;
+import static org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens.*;
public class TokenUtils {
@@ -162,6 +146,29 @@ public static List getTestCaseTokens(String aString) {
return keyTokens;
}
+ public static String getMasksReplaced(String aString) {
+ String regex = "\\$\\{MASKED:([^\\}]*)\\}";
+ Matcher maskMatcher = Pattern.compile(regex).matcher(aString);
+ while(maskMatcher.find()) {
+ String foundMatch = maskMatcher.group(0);
+ aString = aString.replace(foundMatch, MASKED_STR);
+ }
+
+ return aString;
+ }
+
+ public static String getMasksRemoved(String aString) {
+ String regex = "\\$\\{MASKED:([^\\}]*)\\}";
+ Matcher maskMatcher = Pattern.compile(regex).matcher(aString);
+ while(maskMatcher.find()) {
+ String foundFullMatch = maskMatcher.group(0);
+ String innerContent = maskMatcher.group(1);
+ aString = aString.replace(foundFullMatch, innerContent);
+ }
+
+ return aString;
+ }
+
public static String createRandomAlphaString(int length) {
return randomAlphabetic(length);
}
diff --git a/core/src/test/java/org/jsmart/zerocode/core/domain/ParameterizedTest.java b/core/src/test/java/org/jsmart/zerocode/core/domain/ParameterizedTest.java
index a2be5436d..09569bd21 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/domain/ParameterizedTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/domain/ParameterizedTest.java
@@ -10,7 +10,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.hamcrest.CoreMatchers.hasItem;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
@@ -58,4 +60,33 @@ public void testSerDe_csvSource() throws Exception {
assertThat(parameterized.getCsvSource(), hasItem("11, 22, 400"));
}
+ @Test
+ public void shouldReadCsvSourceFromCsvFile() throws IOException {
+ //given
+ String jsonDocumentAsString =
+ smartUtils.getJsonDocumentAsString("unit_test_files/engine_unit_test_jsons/08.1_parameterized_csv_source_from_file.json");
+
+ //when
+ Parameterized parameterized = mapper.readValue(jsonDocumentAsString, Parameterized.class);
+
+ //then
+ assertThat(parameterized.getCsvSource(), hasItem("octocat,The Octocat,San Francisco,583231"));
+ assertThat(parameterized.getCsvSource(), hasItem("siddhagalaxy,Sidd,UK,33847730"));
+ }
+
+ @Test
+ public void shouldReadCsvSourceFromCsvFileIgnoringHeader() throws IOException {
+ //given
+ String jsonDocumentAsString =
+ smartUtils.getJsonDocumentAsString("unit_test_files/engine_unit_test_jsons/08.2_parameterized_csv_source_from_file_containing_header.json");
+
+ //when
+ Parameterized parameterized = mapper.readValue(jsonDocumentAsString, Parameterized.class);
+
+ //then
+ assertThat(parameterized.getCsvSource(), hasItem("octocat,The Octocat,San Francisco,583231"));
+ assertThat(parameterized.getCsvSource(), hasItem("siddhagalaxy,Sidd,UK,33847730"));
+ assertThat(parameterized.getCsvSource(), everyItem(not(is("user,name,city,userid"))));//assert header is ignored
+ }
+
}
\ No newline at end of file
diff --git a/core/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeAssertionsProcessorImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeAssertionsProcessorImplTest.java
index 531878755..8d569eb74 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeAssertionsProcessorImplTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeAssertionsProcessorImplTest.java
@@ -1,26 +1,40 @@
package org.jsmart.zerocode.core.engine.preprocessor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.jayway.jsonpath.JsonPath;
+
+import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.hamcrest.core.Is;
import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
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.jsmart.zerocode.core.engine.assertion.JsonAsserter;
+import org.jsmart.zerocode.core.engine.tokens.ZeroCodeValueTokens;
import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import static com.jayway.jsonpath.JsonPath.read;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.jsmart.zerocode.core.utils.SmartUtils.checkDigNeeded;
+import static org.jsmart.zerocode.core.utils.SmartUtils.readJsonAsString;
import static org.jsmart.zerocode.core.utils.TokenUtils.getTestCaseTokens;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
@@ -1449,4 +1463,175 @@ public void testLeafValuesArray_badIndex() {
expectedException.expect(IndexOutOfBoundsException.class);
jsonPreProcessor.resolveLeafOnlyNodeValue(scenarioState, paramMap, thisPath);
}
+
+ @Test(expected = RuntimeException.class)
+ public void test_wrongJsonPathBy_JSONCONTENT_Exception() throws JsonProcessingException {
+ String jsonAsString = readJsonAsString("unit_test_files/json_content_unit_test/json_step_test_wrong_json_path.json");
+ Map map = mapper.readValue(jsonAsString, new TypeReference