Repository: authorjapps/zerocode (java)

Issue:
[KAFKA]Cannot inject ZeroCode variables into Validators
Description:
===

## This is my usecase:
Details were already provided initially. Copy pasting it _again_
1. Consuming from Kafka multiple messages which are from different partitions.
2. Need to validate the ID field of each message consumed. This ID is Random.Number generated in a previous test (REST POST).
3. Trying to access this ID deterministically using validators and it is not working. 
Below is the validator being used. While consuming we are trying to validate the ID of the message consumed. 
```
{
  "name": "consume_from_test-topic",
      "url": "kafka-topic:test-topic",
      "operation": "consume",
      "request": {
        "consumerLocalConfigs": {
          "recordType": "JSON",
          "commitSync": false,
          "showRecordsConsumed": true,
          "maxNoOfRetryPollsOrTimeouts": 2    
        }
      },
"validators": [
			{
				"field": "$..journals[?(@.id==${$.send_valid_unique_identifiers.response.body.rawJournals.[0].id})]",
				"value": "${$.send_valid_unique_identifiers.response.body.rawJournals.[0].id}"
			}
]
}
```
## This is my actual request and actual response

Paste a sample or example here:
```JSON
Complete logs already attached, as previously requested.

```
## This is what I want to assert or validate

Paste a sample or example here:
```JSON
Need to validate the value of ID field of each message consumed. This ID is Random.UUID Number generated in a previous test (REST POST)
```


Further details:
===

@authorjapps  Scenario: 
1. Consuming from Kafka multiple messages which are from different partitions.
2. Need to validate the ID field of each message consumed. This ID is Random.Number generated in a previous test (REST POST).
3. Trying to access this ID deterministically using validators and it is not working. 
Below is the validator being used. While consuming we are trying to validate the ID of the message consumed. 
```
{
  "name": "consume_from_test-topic",
      "url": "kafka-topic:test-topic",
      "operation": "consume",
      "request": {
        "consumerLocalConfigs": {
          "recordType": "JSON",
          "commitSync": false,
          "showRecordsConsumed": true,
          "maxNoOfRetryPollsOrTimeouts": 2    
        }
      },
"validators": [
			{
				"field": "$..journals[?(@.id==${$.send_valid_unique_identifiers.response.body.rawJournals.[0].id})]",
				"value": "${$.send_valid_unique_identifiers.response.body.rawJournals.[0].id}"
			}
]
}

Pull request:
fix(validators): support of Zerocode expressions and jsonpath
Close #436

# <Feature Title>

PR Branch
**_#ADD LINK TO THE PR BRANCH_**

## Motivation and Context

See issue #436 discussions

## Checklist:

* [x] Unit tests added

* [ ] Integration tests added

* [x] Test names are meaningful

* [x] Feature manually tested

* [ ] Branch build passed

* [x] No 'package.*' in the imports

* [ ] Relevant Wiki page updated with clear instruction for the end user
  * [x] Not applicable. This was only a refactor change, no functional or behaviour changes were introduced

* [ ] Http test added to `http-testing` module(if applicable) ?
  * [x] Not applicable. The changes did not affect HTTP automation flow

* [x] Kafka test added to `kafka-testing` module(if applicable) ?
  * [ ] Not applicable. The changes did not affect Kafka automation flow


Patch:
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java
index b8887fca..96badb8c 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java
@@ -6,7 +6,7 @@ import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
 
 public interface ZeroCodeValidator {
 
-    List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult);
+    List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult, String resolvedScenarioState);
 
     List<FieldAssertionMatcher> validateStrict(String expectedResult, String actualResult);
 
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java
index a8dfca8a..cbbac10c 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java
@@ -9,7 +9,6 @@ import org.jsmart.zerocode.core.domain.Step;
 import org.jsmart.zerocode.core.domain.Validator;
 import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
 import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
-import org.jsmart.zerocode.core.engine.assertion.field.FieldHasExactValueAsserter;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessor;
 import org.slf4j.Logger;
 
@@ -27,16 +26,20 @@ public class ZeroCodeValidatorImpl implements ZeroCodeValidator {
     }
 
     @Override
-    public List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult) {
+    public List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult, String resolvedScenarioState) {
         LOGGER.info("Comparing results via flat validators");
 
         List<FieldAssertionMatcher> failureResults = new ArrayList<>();
         List<Validator> validators = thisStep.getValidators();
 
         for (Validator validator : validators) {
-            String josnPath = validator.getField();
+            String jsonPath = validator.getField();
+
+            String transformed = zeroCodeAssertionsProcessor.resolveStringJson(jsonPath, resolvedScenarioState);
+
             JsonNode expectedValue = validator.getValue();
-            Object actualValue = JsonPath.read(actualResult, josnPath);
+
+            Object actualValue = JsonPath.read(actualResult, transformed);
 
             List<JsonAsserter> asserters = zeroCodeAssertionsProcessor.createJsonAsserters(expectedValue.toString());
 
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 78a969d9..36f7e51f 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
@@ -247,17 +247,17 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                 // -----------------
                 // logging assertion
                 // -----------------
-                List<FieldAssertionMatcher> failureResults = compareStepResults(thisStep, executionResult, resolvedAssertionJson);
+                List<FieldAssertionMatcher> failureResults = compareStepResults(thisStep, executionResult, resolvedAssertionJson, scenarioExecutionState.getResolvedScenarioState());
 
                 if (!failureResults.isEmpty()) {
                     StringBuilder builder = new StringBuilder();
 
                     // Print expected Payload along with assertion errors
-                    builder.append("Assumed Payload: \n" + prettyPrintJson(resolvedAssertionJson) + "\n");
+                    builder.append("Assumed Payload: \n").append(prettyPrintJson(resolvedAssertionJson)).append("\n");
                     builder.append("Assertion Errors: \n");
 
                     failureResults.forEach(f -> {
-                        builder.append(f.toString() + "\n");
+                        builder.append(f.toString()).append("\n");
                     });
                     correlLogger.assertion(resolvedAssertionJson != null ? builder.toString() : expectedValidatorsAsJson(thisStep));
                 } else {
@@ -509,14 +509,14 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
         return scenarioLoopTimes;
     }
 
-    private List<FieldAssertionMatcher> compareStepResults(Step thisStep, String actualResult, String expectedResult) {
+    private List<FieldAssertionMatcher> compareStepResults(Step thisStep, String actualResult, String expectedResult, String resolvedScenarioState) {
         List<FieldAssertionMatcher> failureResults = new ArrayList<>();
 
         // --------------------
         //  Validators (pyrest)
         // --------------------
         if (ofNullable(thisStep.getValidators()).orElse(null) != null) {
-            failureResults = validator.validateFlat(thisStep, actualResult);
+            failureResults = validator.validateFlat(thisStep, actualResult, resolvedScenarioState);
         }
 
         // ------------------------


Test patch:
diff --git a/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java
index 630e9c8c..1d3eee44 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java
@@ -3,12 +3,19 @@ package org.jsmart.zerocode.core.engine.validators;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.text.StrSubstitutor;
 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.preprocessor.ScenarioExecutionState;
+import org.jsmart.zerocode.core.engine.preprocessor.StepExecutionState;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessorImpl;
 import org.jsmart.zerocode.core.utils.SmartUtils;
 import org.junit.Before;
@@ -54,11 +61,54 @@ public class ZeroCodeValidatorImplTest {
                 "                }\n" +
                 "            }";
 
-        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult);
+        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult, "resolvedScenarioState");
         assertThat(matchers.size(), is(0));
 
     }
 
+    @Test
+    public void test_validateFlat_supportJsonPathExpressions() throws Exception {
+        ScenarioExecutionState scenarioExecutionState = new ScenarioExecutionState();
+        scenarioExecutionState.addStepState(createResolvedScenarioState());
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/16_test_validators_jsonpath_expressions_support.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(1);
+
+        String actualResult = "{\n" +
+                "  \"records\" : [ {\n" +
+                "    \"key\" : null,\n" +
+                "    \"value\" : {\n" +
+                "      \"test\" : \"1\"\n" +
+                "    },\n" +
+                "    \"headers\" : {\n" +
+                "      \"CORRELATION_ID\" : \"test\"\n" + // THIS value allow to match with the jsonpath expression
+                "    }\n" +
+                "  } ],\n" +
+                "  \"size\" : 1\n" +
+                "}";
+
+        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult, scenarioExecutionState.getResolvedScenarioState());
+        assertThat(matchers.size(), is(0));
+
+    }
+
+    private String createResolvedScenarioState() {
+        Map<String, String> parammap = new HashMap<>();
+
+        parammap.put("STEP.NAME", "produce_step");
+        parammap.put("STEP.REQUEST", "{\n" +
+                        "\"recordType\":\"JSON\"," +
+                        "\"records\":[{\"key\":null,\"headers\":{\"CORRELATION_ID\":\"test\"},\"value\":{\"test\":\"1\"}}]\n" +
+                "}");
+        parammap.put("STEP.RESPONSE", "{\n" +
+                "    \"id\" : 10101\n" +
+                "}");
+
+        StrSubstitutor sub = new StrSubstitutor(parammap);
+
+        return sub.replace((new StepExecutionState()).getRequestResponseState());
+    }
     @Test
     public void test_validateFlat_nonMatching() throws Exception {
 
@@ -74,7 +124,7 @@ public class ZeroCodeValidatorImplTest {
                 "                }\n" +
                 "            }";
 
-        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult);
+        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult, "resolvedScenarioState");
         assertThat(matchers.size(), is(2));
         assertThat(matchers.get(0).toString(), containsString("actual value 'Mrs X' did not match the expected value 'Mr Bean'"));
         assertThat(matchers.get(1).toString(), containsString("actual value '201' did not match the expected value '200'"));
diff --git a/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java b/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
index ccdd1cd6..82d1effd 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
@@ -76,7 +76,7 @@ public class SmartUtilsTest {
     @Test
     public void willReadAllfileNamesFrom_TestResource() throws Exception {
         List<String> allTestCaseFiles = SmartUtils.getAllEndPointFiles("unit_test_files/engine_unit_test_jsons");
-        assertThat(allTestCaseFiles.size(), is(18));
+        assertThat(allTestCaseFiles.size(), is(19));
         assertThat(allTestCaseFiles.get(0), is("unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json"));
     }
 
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/16_test_validators_jsonpath_expressions_support.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/16_test_validators_jsonpath_expressions_support.json
new file mode 100755
index 00000000..36731ca4
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/16_test_validators_jsonpath_expressions_support.json
@@ -0,0 +1,46 @@
+{
+  "scenarioName": "Validate jsonpath in validators",
+  "steps": [
+    {
+      "name": "produce_step",
+      "url": "kafka-topic:any-topic",
+      "operation": "produce",
+      "request": {
+        "recordType": "JSON",
+        "records": [
+          {
+            "key": null,
+            "headers": {
+              "CORRELATION_ID": "test"
+            },
+            "value": {
+              "test": "1"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "consume the response",
+      "url": "kafka-topic:test-topic",
+      "operation": "consume",
+      "request": {
+        "consumerLocalConfigs": {
+          "recordType": "JSON"
+        }
+      },
+      "validators": [
+        {
+          "field": "$.records[?(@.headers.CORRELATION_ID == '${$.produce_step.request.records[0].headers.CORRELATION_ID}')]",
+          "value": [
+            {
+              "value": {
+                "test": "1"
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/KafkaConsumeJsonTest.java b/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/KafkaConsumeJsonTest.java
index a452537e..a892d48a 100644
--- a/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/KafkaConsumeJsonTest.java
+++ b/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/KafkaConsumeJsonTest.java
@@ -14,4 +14,9 @@ public class KafkaConsumeJsonTest {
     @Scenario("kafka/consume/test_kafka_consume_json_msg.json")
     public void testKafkaConsume_json() throws Exception {
     }
+
+    @Test
+    @Scenario("kafka/consume/test_kafka_consume_support_of_jsonpath_in_validators.json")
+    public void testKafkaProduceConsume_support_of_jsonpath_expression_in_validators_field() throws Exception {
+    }
 }
diff --git a/kafka-testing/src/test/resources/kafka/consume/test_kafka_consume_support_of_jsonpath_in_validators.json b/kafka-testing/src/test/resources/kafka/consume/test_kafka_consume_support_of_jsonpath_in_validators.json
new file mode 100755
index 00000000..2f3c8748
--- /dev/null
+++ b/kafka-testing/src/test/resources/kafka/consume/test_kafka_consume_support_of_jsonpath_in_validators.json
@@ -0,0 +1,50 @@
+{
+  "scenarioName": "Produce a JSON message to a kafka topic",
+  "steps": [
+    {
+      "name": "load_kafka",
+      "url": "kafka-topic:support-of-jsonpath-in-validators",
+      "operation": "load",
+      "request": {
+        "records": [
+          {
+            "key": "${RANDOM.NUMBER}",
+            "headers": {
+              "CORRELATION_ID": "${RANDOM.UUID}"
+            },
+            "value": "{\"name\": \"Ludovic\"}"
+          }
+        ]
+      },
+      "assertions": {
+        "status": "Ok",
+        "recordMetadata": "$NOT.NULL"
+      }
+    },
+    {
+      "name": "support-of-jsonpath-in-validators",
+      "url": "kafka-topic:support-of-jsonpath-in-validators",
+      "operation": "unload",
+      "request": {
+        "consumerLocalConfigs": {
+          "recordType": "JSON",
+          "commitSync": true,
+          "showRecordsConsumed": true,
+          "maxNoOfRetryPollsOrTimeouts": 1
+        }
+      },
+      "validators": [
+        {
+          "field": "$.records[?(@.headers.CORRELATION_ID == '${$.load_kafka.request.records[0].headers.CORRELATION_ID}')]",
+          "value": [
+            {
+              "value": {
+                "name": "Ludovic"
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}