Repository: authorjapps/zerocode
Pull request #559 (2023-02-26 20:50:59)

Issue 555: Sort message list or array for deterministic assertions
# <Feature Title>Sort message list or array for deterministic assertions

[Link issue](https://github.com/authorjapps/zerocode/issues/555)

PR Branch
**https://github.com/ihorsas/zerocode/tree/issue-555_sorting**

## Motivation and Context

## Checklist:

* [x] Unit tests added

* [x] 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
  * [ ] Not applicable. This was only a refactor change, no functional or behaviour changes were introduced

* [ ] Http test added to `http-testing` module(if applicable) ?
  * [ ] 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/di/main/ApplicationMainModule.java b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
index b561cf2c..316143ec 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
@@ -21,6 +21,8 @@ import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcesso
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessorImpl;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessorImpl;
+import org.jsmart.zerocode.core.engine.sorter.ZeroCodeSorter;
+import org.jsmart.zerocode.core.engine.sorter.ZeroCodeSorterImpl;
 import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidator;
 import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidatorImpl;
 import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
@@ -65,6 +67,7 @@ public class ApplicationMainModule extends AbstractModule {
         bind(ZeroCodeReportGenerator.class).to(ZeroCodeReportGeneratorImpl.class);
         bind(ZeroCodeExternalFileProcessor.class).to(ZeroCodeExternalFileProcessorImpl.class);
         bind(ZeroCodeParameterizedProcessor.class).to(ZeroCodeParameterizedProcessorImpl.class);
+        bind(ZeroCodeSorter.class).to(ZeroCodeSorterImpl.class);
 
         // ------------------------------------------------
         // Bind properties for localhost, CI, DIT, SIT etc
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java b/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
index ef8644aa..02d2e1d6 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
@@ -22,6 +22,7 @@ public class Step {
     private final String url;
     private final JsonNode request;
     private final List<Validator> validators;
+    private final JsonNode sort;
     private final JsonNode assertions;
     private final String verifyMode;
     private final JsonNode verify;
@@ -67,6 +68,10 @@ public class Step {
         return validators;
     }
 
+    public JsonNode getSort() {
+        return sort;
+    }
+
     public JsonNode getAssertions() {
         return assertions;
     }
@@ -138,6 +143,7 @@ public class Step {
             @JsonProperty("url") String url,
             @JsonProperty("request") JsonNode request,
             @JsonProperty("validators") List<Validator> validators,
+            @JsonProperty("sort") JsonNode sort,
             @JsonProperty("assertions") JsonNode assertions,
             @JsonProperty("verify") JsonNode verify,
             @JsonProperty("verifyMode") String verifyMode,
@@ -151,6 +157,7 @@ public class Step {
         this.method = method != null? method : operation;
         this.request = request;
         this.url = url;
+        this.sort = sort;
         this.assertions = assertions.isNull() ? verify : assertions;
         this.verify = verify;
         this.ignoreStep = ignoreStep;
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/SortOrder.java b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/SortOrder.java
new file mode 100644
index 00000000..75e09e72
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/SortOrder.java
@@ -0,0 +1,17 @@
+package org.jsmart.zerocode.core.engine.sorter;
+
+public enum SortOrder {
+    NATURAL("natural"), REVERSE("reverse");
+
+    String value;
+
+    SortOrder(String order) {
+        this.value = order;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorter.java b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorter.java
new file mode 100644
index 00000000..375285ab
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorter.java
@@ -0,0 +1,11 @@
+package org.jsmart.zerocode.core.engine.sorter;
+
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
+
+import java.util.List;
+
+public interface ZeroCodeSorter {
+
+    String sortArrayAndReplaceInResponse(Step thisStep, String results, String resolvedScenarioState);
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImpl.java
new file mode 100644
index 00000000..ea6eba51
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImpl.java
@@ -0,0 +1,112 @@
+package org.jsmart.zerocode.core.engine.sorter;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.jayway.jsonpath.JsonPath;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessor;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class ZeroCodeSorterImpl implements ZeroCodeSorter {
+
+    private static final Logger LOGGER = getLogger(ZeroCodeSorterImpl.class);
+    private final ObjectMapper mapper;
+    private final ZeroCodeAssertionsProcessor zeroCodeAssertionsProcessor;
+
+    @Inject
+    public ZeroCodeSorterImpl(ZeroCodeAssertionsProcessor zeroCodeAssertionsProcessor,
+                              ObjectMapper mapper) {
+        this.zeroCodeAssertionsProcessor = zeroCodeAssertionsProcessor;
+        this.mapper = mapper;
+    }
+
+    @Override
+    public String sortArrayAndReplaceInResponse(Step thisStep, String results, String resolvedScenarioState) {
+        String key, order, path;
+        // reading key and order fields
+        try {
+            JsonNode sort = thisStep.getSort();
+            Map<String, String> fieldMap = convertToMap(sort.toString());
+            key = fieldMap.get("key");
+            order = fieldMap.getOrDefault("order", "natural");
+            path = fieldMap.get("path");
+        } catch (Exception e) {
+            LOGGER.error("Unable to read values in sort field");
+            throw new RuntimeException("Unable to read values in sort field", e);
+        }
+        if (Objects.isNull(path)) {
+            LOGGER.error("Path is null in sort section hence can't sort the response");
+            throw new RuntimeException("Path was not specified in sort");
+        }
+        //
+        String transformedPath = zeroCodeAssertionsProcessor.resolveStringJson(path,
+                resolvedScenarioState);
+        Object result = getArrayToSort(transformedPath, results);
+
+        if (result instanceof JSONArray) {
+            JSONArray arrayToSort = (JSONArray) result;
+
+            // sorting passed array
+            JSONArray sortedArray = sortArray(arrayToSort, key, order);
+            return replaceArrayWithSorted(results, transformedPath, sortedArray);
+        } else {
+            throw new RuntimeException("Can't sort not an array");
+        }
+    }
+
+    private JSONArray sortArray(JSONArray arrayToSort, String key, String order) {
+        JSONArray sortedJsonArray = new JSONArray();
+
+        List<Map<String, ?>> jsonValues = new ArrayList<>();
+        for (Object o : arrayToSort) {
+            jsonValues.add((Map<String, ?>) o);
+        }
+
+        jsonValues.sort((a, b) -> {
+            Comparable valA;
+            Comparable valB;
+
+            try {
+                valA = (Comparable) a.get(key);
+                valB = (Comparable) b.get(key);
+            } catch (Exception e) {
+                LOGGER.error("Objects can't be compared" + e);
+                throw new RuntimeException("Objects can't be compared", e.getCause());
+            }
+            return order.equalsIgnoreCase(SortOrder.NATURAL.getValue()) ? valA.compareTo(valB)
+                    : -valA.compareTo(valB);
+        });
+        sortedJsonArray.addAll(jsonValues);
+        return sortedJsonArray;
+    }
+
+    private Map<String, String> convertToMap(String value) {
+        try {
+            return mapper.readValue(value, new TypeReference<Map<String, String>>() {
+            });
+        } catch (Exception ex) {
+            LOGGER.error("Field Type conversion exception. \nDetails:" + ex);
+            throw new RuntimeException(ex);
+        }
+    }
+
+    public Object getArrayToSort(String path, String results) {
+        return JsonPath.read(results, path);
+    }
+
+    public String replaceArrayWithSorted(String results, String path, Object sortedArray) {
+        return JsonPath.parse(results).set(path, sortedArray).jsonString();
+    }
+
+}
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 36f7e51f..5e94b277 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
@@ -6,10 +6,13 @@ import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 import com.univocity.parsers.csv.CsvParser;
+
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.BiConsumer;
+
 import org.jsmart.zerocode.core.domain.ScenarioSpec;
 import org.jsmart.zerocode.core.domain.Step;
 import org.jsmart.zerocode.core.domain.builders.ZeroCodeExecReportBuilder;
@@ -21,6 +24,7 @@ import org.jsmart.zerocode.core.engine.preprocessor.StepExecutionState;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessor;
+import org.jsmart.zerocode.core.engine.sorter.ZeroCodeSorter;
 import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidator;
 import org.jsmart.zerocode.core.logbuilder.ZerocodeCorrelationshipLogger;
 import org.jsmart.zerocode.core.utils.ApiTypeUtils;
@@ -56,6 +60,9 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
     @Inject
     private ZeroCodeParameterizedProcessor parameterizedProcessor;
 
+    @Inject
+    private ZeroCodeSorter sorter;
+
     @Inject
     private ApiServiceExecutor apiExecutor;
 
@@ -236,6 +243,16 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                 stepExecutionState.addResponse(executionResult);
                 scenarioExecutionState.addStepState(stepExecutionState.getResolvedStep());
 
+                // ---------------------------------
+                // Handle sort section
+                // ---------------------------------
+                if (!thisStep.getSort().isNull()) {
+                    executionResult = sorter.sortArrayAndReplaceInResponse(thisStep, executionResult, scenarioExecutionState.getResolvedScenarioState());
+                    correlLogger.customLog("Updated response: " + executionResult);
+                    stepExecutionState.addResponse(executionResult);
+                    scenarioExecutionState.addStepState(stepExecutionState.getResolvedStep());
+                }
+
                 // ---------------------------------
                 // Handle assertion section -START
                 // ---------------------------------


Test patch:
diff --git a/core/src/test/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImplTest.java
new file mode 100644
index 00000000..3ca55720
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/core/engine/sorter/ZeroCodeSorterImplTest.java
@@ -0,0 +1,130 @@
+package org.jsmart.zerocode.core.engine.sorter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+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.preprocessor.ScenarioExecutionState;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessorImpl;
+import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+public class ZeroCodeSorterImplTest {
+
+    Injector injector;
+    SmartUtils smartUtils;
+    ObjectMapper mapper;
+
+    ZeroCodeAssertionsProcessorImpl jsonPreProcessor;
+    ZeroCodeSorterImpl sorter;
+
+
+    @Before
+    public void setUpStuff() throws Exception {
+        String serverEnvFileName = "config_hosts_test.properties";
+        injector = Guice.createInjector(new ApplicationMainModule(serverEnvFileName));
+        smartUtils = injector.getInstance(SmartUtils.class);
+        mapper = new ObjectMapperProvider().get();
+        jsonPreProcessor =
+                new ZeroCodeAssertionsProcessorImpl(smartUtils.getMapper(), serverEnvFileName);
+
+        sorter = new ZeroCodeSorterImpl(jsonPreProcessor, mapper);
+    }
+
+
+    @Test
+    public void testSortResponseWithStringField() throws Exception {
+        ScenarioExecutionState scenarioExecutionState = new ScenarioExecutionState();
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(0);
+
+        String response = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"name\" : \"Ihor\"}," +
+                "       {\"name\" : \"Andrew\"}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String sortedResponse = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"name\" : \"Andrew\"}," +
+                "       {\"name\" : \"Ihor\"}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String result = sorter.sortArrayAndReplaceInResponse(step, response, scenarioExecutionState.getResolvedScenarioState());
+        JSONAssert.assertEquals(sortedResponse, result, false);
+    }
+
+    @Test
+    public void testSortResponseWithIntegerValueAndReverseOrder() throws Exception {
+        ScenarioExecutionState scenarioExecutionState = new ScenarioExecutionState();
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(1);
+
+        String response = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"id\" : 1}," +
+                "       {\"id\" : 2}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String sortedResponse = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"id\" : 2}," +
+                "       {\"id\" : 1}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String result = sorter.sortArrayAndReplaceInResponse(step, response, scenarioExecutionState.getResolvedScenarioState());
+        JSONAssert.assertEquals(sortedResponse, result, false);
+    }
+
+    @Test
+    public void testSortResponseWithDefaultOrder() throws Exception {
+        ScenarioExecutionState scenarioExecutionState = new ScenarioExecutionState();
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(2);
+
+        String response = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"id\" : 2}," +
+                "       {\"id\" : 1}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String sortedResponse = "{\n" +
+                "  \"body\" : {\n" +
+                "    \"persons\" : [" +
+                "       {\"id\" : 1}," +
+                "       {\"id\" : 2}" +
+                "   ]\n" +
+                "  }\n" +
+                "}\n";
+
+        String result = sorter.sortArrayAndReplaceInResponse(step, response, scenarioExecutionState.getResolvedScenarioState());
+        JSONAssert.assertEquals(sortedResponse, result, false);
+    }
+
+}
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 82d1effd..7b501212 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(19));
+        assertThat(allTestCaseFiles.size(), is(20));
         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/java/org/jsmart/zerocode/integrationtests/SorterTest.java b/core/src/test/java/org/jsmart/zerocode/integrationtests/SorterTest.java
new file mode 100644
index 00000000..1c5f3796
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/integrationtests/SorterTest.java
@@ -0,0 +1,31 @@
+package org.jsmart.zerocode.integrationtests;
+
+import org.jsmart.zerocode.core.domain.HostProperties;
+import org.jsmart.zerocode.core.domain.Scenario;
+import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@HostProperties(host = "http://localhost", port = 9998, context = "")
+@RunWith(TestOnlyZeroCodeUnitRunner.class)
+public class SorterTest {
+
+    /**
+     * Mock end points are in test/resources: simulators/test_purpose_end_points.json.
+     *
+     * @RunWith(TestOnlyZeroCodeUnitRunner.class) : starts these mocks first before running the tests
+     * <p>
+     * Path:
+     * src/test/resources/simulators/test_purpose_end_points.json
+     */
+
+    @Test
+    @Scenario("integration_test_files/helloworld/get_api_integration_sorted_response_STRICT_test.json")
+    public void testValidateSortedResponse() throws Exception {
+
+    }
+
+}
+
+
+
diff --git a/core/src/test/resources/integration_test_files/helloworld/get_api_integration_sorted_response_STRICT_test.json b/core/src/test/resources/integration_test_files/helloworld/get_api_integration_sorted_response_STRICT_test.json
new file mode 100644
index 00000000..9a078104
--- /dev/null
+++ b/core/src/test/resources/integration_test_files/helloworld/get_api_integration_sorted_response_STRICT_test.json
@@ -0,0 +1,99 @@
+{
+    "scenarioName": "As simple GET API - Strict validation for sorted response",
+    "steps": [
+        {
+            "name": "Sort by String field test",
+            "url": "/api/v1/search/persons",
+            "method": "GET",
+            "request": {
+                "queryParams": {
+                    "country": "UK"
+                }
+            },
+            "sort": {
+                "key": "name",
+                "order": "natural",
+                "path": "$.body.persons"
+            },
+            "verifyMode":"STRICT",
+            "verify": {
+                "status": 200,
+                "body": {
+                    "persons": [
+                        {
+                            "id": 2,
+                            "name": "Andrew"
+                        },
+                        {
+                            "id": 1,
+                            "name": "Ihor"
+                        }
+                    ]
+                }
+            }
+        },
+        {
+            "name": "Sort by id field with reverse order test",
+            "url": "/api/v1/search/persons",
+            "method": "GET",
+            "request": {
+                "queryParams": {
+                    "country": "UK"
+                }
+            },
+            "sort": {
+                "key": "id",
+                "order": "reverse",
+                "path": "$.body.persons"
+            },
+            "verifyMode":"STRICT",
+            "verify": {
+                "status": 200,
+                "body": {
+                    "persons": [
+                        {
+                            "id": 2,
+                            "name": "Andrew"
+                        },
+                        {
+                            "id": 1,
+                            "name": "Ihor"
+                        }
+                    ]
+                }
+            }
+        },
+        {
+            "name": "Sort already sorted array test",
+            "url": "/api/v1/search/persons",
+            "method": "GET",
+            "request": {
+                "queryParams": {
+                    "country": "UK"
+                }
+            },
+            "sort": {
+                "key": "id",
+                "order": "natural",
+                "path": "$.body.persons"
+            },
+            "verifyMode":"STRICT",
+            "verify": {
+                "status": 200,
+                "body": {
+                    "persons": [
+                        {
+                            "id": 1,
+                            "name": "Ihor"
+                        },
+                        {
+                            "id": 2,
+                            "name": "Andrew"
+                        }
+                    ]
+                }
+            }
+        }
+    ]
+}
+
diff --git a/core/src/test/resources/simulators/test_purpose_end_points.json b/core/src/test/resources/simulators/test_purpose_end_points.json
index 5ce0ae97..0dba9e58 100644
--- a/core/src/test/resources/simulators/test_purpose_end_points.json
+++ b/core/src/test/resources/simulators/test_purpose_end_points.json
@@ -122,6 +122,26 @@
                     ]
                 }
             }
+        },
+        {
+            "name": "request with query params",
+            "operation": "GET",
+            "url": "/api/v1/search/persons?country=UK",
+            "response": {
+                "status": 200,
+                "body": {
+                    "persons": [
+                        {
+                            "id": 1,
+                            "name": "Ihor"
+                        },
+                        {
+                            "id": 2,
+                            "name": "Andrew"
+                        }
+                    ]
+                }
+            }
         }
     ]
 }
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json
new file mode 100644
index 00000000..156115cf
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/17_scenario_with_sort.json
@@ -0,0 +1,70 @@
+{
+    "scenarioName": "Sort array test",
+    "steps": [
+        {
+            "name": "StepNameWithStringField",
+            "url": "/persons",
+            "operation": "GET",
+            "request": {
+                "body": {
+                    "persons": [
+                        {
+                            "name": "Ihor"
+                        },
+                        {
+                            "name": "Andrew"
+                        }
+                    ]
+                }
+            },
+            "sort": {
+                "key": "name",
+                "order": "natural",
+                "path": "$.body.persons"
+            }
+        },
+        {
+            "name": "StepNameWithIntegerFieldAndReverseOrder",
+            "url": "/persons",
+            "operation": "GET",
+            "request": {
+                "body": {
+                    "persons": [
+                        {
+                            "id": 1
+                        },
+                        {
+                            "id": 2
+                        }
+                    ]
+                }
+            },
+            "sort": {
+                "key": "id",
+                "order": "reverse",
+                "path": "$.body.persons"
+            }
+        },
+        {
+            "name": "StepNameWithDefaultOrder",
+            "url": "/persons",
+            "operation": "GET",
+            "request": {
+                "body": {
+                    "persons": [
+                        {
+                            "id": 2
+                        },
+                        {
+                            "id": 1
+                        }
+                    ]
+                }
+            },
+            "sort": {
+                "key": "id",
+                "path": "$.body.persons"
+            }
+        }
+    ]
+}
diff --git a/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/sorting/KafkaSortingTest.java b/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/sorting/KafkaSortingTest.java
new file mode 100644
index 00000000..52c487e6
--- /dev/null
+++ b/kafka-testing/src/test/java/org/jsmart/zerocode/integration/tests/kafka/consume/sorting/KafkaSortingTest.java
@@ -0,0 +1,18 @@
+package org.jsmart.zerocode.integration.tests.kafka.consume.sorting;
+
+import org.jsmart.zerocode.core.domain.Scenario;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@TargetEnv("kafka_servers/kafka_test_server.properties")
+@RunWith(ZeroCodeUnitRunner.class)
+public class KafkaSortingTest {
+
+    @Test
+    @Scenario("kafka/consume/sorting/test_kafka_sort_records_by_json_path.json")
+    public void testConsumeSort_byJsonPath(){
+    }
+
+}
diff --git a/kafka-testing/src/test/resources/kafka/consume/sorting/test_kafka_sort_records_by_json_path.json b/kafka-testing/src/test/resources/kafka/consume/sorting/test_kafka_sort_records_by_json_path.json
new file mode 100755
index 00000000..1f5ac6f1
--- /dev/null
+++ b/kafka-testing/src/test/resources/kafka/consume/sorting/test_kafka_sort_records_by_json_path.json
@@ -0,0 +1,85 @@
+{
+    "scenarioName": "Produce - 2 records and consume them and sort by JSON Path",
+    "steps": [
+        {
+            "name": "load_kafka",
+            "url": "kafka-topic:demo-sorting-topicx",
+            "operation": "PRODUCE",
+            "request": {
+                "records": [
+                    {
+                        "key": "101",
+                        "value": "Hello World 1"
+                    },
+                    {
+                        "key": "102",
+                        "value": "Hello World 2"
+                    }
+                ]
+            },
+            "assertions": {
+                "status": "Ok"
+            }
+        },
+        {
+            "name": "natural sort",
+            "url": "kafka-topic:demo-sorting-topicx",
+            "operation": "CONSUME",
+            "request": {
+                "consumerLocalConfigs": {
+                    "showRecordsConsumed": true,
+                    "maxNoOfRetryPollsOrTimeouts": 3,
+                    "commitSync": false
+                }
+            },
+            "sort": {
+                "key": "value",
+                "order": "natural",
+                "path": "$.records"
+            },
+            "assertions": {
+                "size": 2,
+                "records": [
+                    {
+                        "key": "101",
+                        "value": "Hello World 1"
+                    },
+                    {
+                        "key": "102",
+                        "value": "Hello World 2"
+                    }
+                ]
+            }
+        },
+        {
+            "name": "reverse sort",
+            "url": "kafka-topic:demo-sorting-topicx",
+            "operation": "CONSUME",
+            "request": {
+                "consumerLocalConfigs": {
+                    "showRecordsConsumed": true,
+                    "maxNoOfRetryPollsOrTimeouts": 3,
+                    "commitSync": false
+                }
+            },
+            "sort": {
+                "key": "key",
+                "order": "reverse",
+                "path": "$.records"
+            },
+            "assertions": {
+                "size": 2,
+                "records": [
+                    {
+                        "key": "102",
+                        "value": "Hello World 2"
+                    },
+                    {
+                        "key": "101",
+                        "value": "Hello World 1"
+                    }
+                ]
+            }
+        }
+    ]
+}