Repository: authorjapps/zerocode
Pull request #138 (2018-10-14 08:14:17)

ISS-2 # External JSON payload -Green


Patch:
diff --git a/pom.xml b/pom.xml
index 28f61993..f663d048 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,7 @@
 
         <!-- this project properties -->
         <junit.version>4.12</junit.version>
-        <jackson-databind.version>2.8.2</jackson-databind.version>
+        <jackson-databind.version>2.9.7</jackson-databind.version>
         <guava.version>23.0</guava.version>
         <jsonassert.version>1.2.3</jsonassert.version>
         <classpath-explorer.version>1.0</classpath-explorer.version>
diff --git a/src/main/java/org/jsmart/zerocode/core/di/ApplicationMainModule.java b/src/main/java/org/jsmart/zerocode/core/di/ApplicationMainModule.java
index 83dc62d1..eef3fc51 100644
--- a/src/main/java/org/jsmart/zerocode/core/di/ApplicationMainModule.java
+++ b/src/main/java/org/jsmart/zerocode/core/di/ApplicationMainModule.java
@@ -7,6 +7,8 @@ import org.jsmart.zerocode.core.engine.executor.JavaExecutor;
 import org.jsmart.zerocode.core.engine.executor.JavaExecutorImpl;
 import org.jsmart.zerocode.core.engine.executor.JsonServiceExecutor;
 import org.jsmart.zerocode.core.engine.executor.JsonServiceExecutorImpl;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessor;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessorImpl;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeJsonTestProcesor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeJsonTestProcesorImpl;
 import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
@@ -44,6 +46,7 @@ public class ApplicationMainModule extends AbstractModule {
         bind(JavaExecutor.class).to(JavaExecutorImpl.class);
         bind(ZeroCodeJsonTestProcesor.class).to(ZeroCodeJsonTestProcesorImpl.class);
         bind(ZeroCodeReportGenerator.class).to(ZeroCodeReportGeneratorImpl.class);
+        bind(ZeroCodeExternalFileProcessor.class).to(ZeroCodeExternalFileProcessorImpl.class);
         //bind(ExecutorServiceRunner.class).in(Singleton.class);
         //bind(SmartUtils.class);
 
diff --git a/src/main/java/org/jsmart/zerocode/core/domain/Step.java b/src/main/java/org/jsmart/zerocode/core/domain/Step.java
index a715b851..a34ef98a 100644
--- a/src/main/java/org/jsmart/zerocode/core/domain/Step.java
+++ b/src/main/java/org/jsmart/zerocode/core/domain/Step.java
@@ -16,8 +16,8 @@ public class Step {
     private final String name;
     private final String operation;
     private final String url;
-    private final JsonNode request;
-    private final JsonNode assertions;
+    private JsonNode request;
+    private JsonNode assertions;
 
 
     public Integer getLoop() {
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ScenarioExecutionState.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ScenarioExecutionState.java
index c23c4505..df435faf 100644
--- a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ScenarioExecutionState.java
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ScenarioExecutionState.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.jsmart.zerocode.core.engine.preprocessor;
 
 import org.apache.commons.lang.text.StrSubstitutor;
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
index e8730b68..384c7e1c 100644
--- a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.jsmart.zerocode.core.engine.preprocessor;
 
 import org.apache.commons.lang.text.StrSubstitutor;
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessor.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessor.java
new file mode 100644
index 00000000..7071f3f7
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jsmart.zerocode.core.engine.preprocessor;
+
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.engine.assertion.AssertionReport;
+import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
+
+import java.util.List;
+
+public interface ZeroCodeExternalFileProcessor {
+
+    Step resolveExtJsonFile(Step thisStep);
+
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImpl.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImpl.java
new file mode 100644
index 00000000..28c60d4e
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.Inject;
+import com.google.inject.Singleton;
+import org.jsmart.zerocode.core.domain.Step;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeJsonTestProcesorImpl.JSON_PAYLOAD_FILE;
+import static org.jsmart.zerocode.core.utils.SmartUtils.getAllTokens;
+import static org.jsmart.zerocode.core.utils.SmartUtils.readJsonAsString;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * <h3>External File Resolver</h3>
+ * <p>
+ * Processes the Step definition and resolves any reference to the external file.
+ * <p>
+ * Given a Json Java map, it digs deep into the fields and finds the references to the external content
+ * in the classpath and replaces the value of this key with the content from the file.
+ *
+ * Issue-2 - Feature requested by @UmeshIghe(Github)
+ * Github Link - https://github.com/authorjapps/zerocode/issues/2
+ *
+ * Suggestion and Recommendation:
+ * Try to keep the test cases as independent possible. Do not create too much nested dependencies which adds
+ * unnecessary complexity to the project. The complexity could be in terms project maintenance or interpreting the
+ * test case itself. We should use the IDE features which now a days makes a lot easier in terms of dealing with
+ * JSON contents to keep the testing life cycle simple.
+ *
+ * @author nirmal.fleet@google.com (Nirmal Chandra)
+ * @author ghorpade.a.g@gmail.com (Amit Ghorpade)
+ */
+@Singleton
+public class ZeroCodeExternalFileProcessorImpl implements ZeroCodeExternalFileProcessor {
+    private static final Logger LOGGER = getLogger(ZeroCodeExternalFileProcessorImpl.class);
+
+    private final ObjectMapper objectMapper;
+
+    @Inject
+    public ZeroCodeExternalFileProcessorImpl(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+    /**
+     * Resolves the external file content for a place holder against a key e.g. in the payload or assertions.
+     *
+     * First the logic checks if dig-deep needed to avoid unwanted recursions. If not needed, the step definition is
+     * returned intact. Otherwise calls the dig deep method to perform the operation.
+     *
+     * @param thisStep A step, defining the API call and validation
+     * @return The effective step definition
+     */
+    @Override
+    public Step resolveExtJsonFile(Step thisStep) {
+
+        try {
+
+            if (!checkDigNeeded(thisStep)) {
+                return thisStep;
+            }
+
+            JsonNode stepNode = objectMapper.convertValue(thisStep, JsonNode.class);
+
+            Map<String, Object> stepMap = objectMapper.readValue(stepNode.toString(), new TypeReference<Map<String, Object>>() {
+            });
+
+            digReplaceContent(stepMap);
+
+            JsonNode jsonStepNode = objectMapper.valueToTree(stepMap);
+
+            return objectMapper.treeToValue(jsonStepNode, Step.class);
+
+        } catch (Exception exx) {
+
+            LOGGER.error("External file reading exception - {}", exx.getMessage());
+
+            throw new RuntimeException("External file reading exception. Details - " + exx);
+
+        }
+
+    }
+
+    /**
+     * Digs deep into the nested map and looks for external file reference,if found, replaces the place holder with
+     * the file content. This is handy when the engineers wants to drive the common contents from a central place.
+     *
+     * @param map A map representing the key-value pairs, can be nested
+     */
+    void digReplaceContent(Map<String, Object> map) {
+
+        map.entrySet().stream().forEach(entry -> {
+
+            Object value = entry.getValue();
+
+            if (value instanceof Map) {
+                digReplaceContent((Map<String, Object>) value);
+
+            } else {
+                LOGGER.debug("Leaf node found = {}, checking for any external json file...", value);
+                if (value != null && value.toString().contains(JSON_PAYLOAD_FILE)) {
+                    LOGGER.info("Found external JSON file place holder = {}. Replacing with content", value);
+                    String valueString = value.toString();
+                    String token = getJsonFilePhToken(valueString);
+                    if (token != null && token.startsWith(JSON_PAYLOAD_FILE)) {
+                        String resourceJsonFile = token.substring(JSON_PAYLOAD_FILE.length());
+                        try {
+                            Object jsonFileContent = objectMapper.readTree(readJsonAsString(resourceJsonFile));
+                            entry.setValue(jsonFileContent);
+                        } catch (Exception exx) {
+                            LOGGER.error("External file reference exception - {}", exx.getMessage());
+                            throw new RuntimeException(exx);
+                        }
+                    }
+                    // ----------------------------------------------------
+                    // Extension- for XML file type in case a ticket raised
+                    // ----------------------------------------------------
+                    /*
+                    else if (token != null && token.startsWith(XML_FILE)) {
+
+                    }
+                    */
+
+                    // ---------------------------------------------------
+                    // Extension- for Other types in case a ticket raised
+                    // ---------------------------------------------------
+                    /*
+                    else if (token != null && token.startsWith(OTHER_FILE)) {
+
+                    }
+                    */
+
+                }
+            }
+        });
+    }
+
+    private String getJsonFilePhToken(String valueString) {
+        if (valueString != null) {
+            List<String> allTokens = getAllTokens(valueString);
+            if (allTokens != null && !allTokens.isEmpty()) {
+                return allTokens.get(0);
+            }
+        }
+        return null;
+    }
+
+    boolean checkDigNeeded(Step thisStep) throws JsonProcessingException {
+        String stepJson = objectMapper.writeValueAsString(thisStep);
+        List<String> allTokens = getAllTokens(stepJson);
+
+        return allTokens.toString().contains(JSON_PAYLOAD_FILE);
+    }
+
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
index 9cdbbd83..4ce2cf19 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
-
 import org.apache.commons.lang.StringUtils;
 import org.jsmart.zerocode.core.domain.ScenarioSpec;
 import org.jsmart.zerocode.core.domain.Step;
@@ -15,17 +14,16 @@ import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
 import org.jsmart.zerocode.core.engine.executor.JsonServiceExecutor;
 import org.jsmart.zerocode.core.engine.preprocessor.ScenarioExecutionState;
 import org.jsmart.zerocode.core.engine.preprocessor.StepExecutionState;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeJsonTestProcesor;
 import org.jsmart.zerocode.core.logbuilder.LogCorrelationshipPrinter;
 import org.jsmart.zerocode.core.utils.ServiceType;
 import org.junit.runner.Description;
 import org.junit.runner.notification.RunNotifier;
+import org.slf4j.Logger;
 
 import java.time.LocalDateTime;
 import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
 
 import static org.apache.commons.lang.StringUtils.isEmpty;
@@ -37,14 +35,13 @@ import static org.slf4j.LoggerFactory.getLogger;
 @Singleton
 public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsScenarioRunner {
 
-    private static final org.slf4j.Logger LOGGER = getLogger(ZeroCodeMultiStepsScenarioRunnerImpl.class);
+    private static final Logger LOGGER = getLogger(ZeroCodeMultiStepsScenarioRunnerImpl.class);
 
-    //guice -starts
     @Inject
-    private ObjectMapper objectMapper;
+    private ZeroCodeJsonTestProcesor zeroCodeJsonTestProcesor;
 
     @Inject
-    private ZeroCodeJsonTestProcesor zeroCodeJsonTestProcesor;
+    private ZeroCodeExternalFileProcessor extFileProcessor;
 
     @Inject
     private JsonServiceExecutor serviceExecutor;
@@ -60,7 +57,6 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
     @Inject
     @Named("web.application.endpoint.context")
     private String applicationContext;
-    //guice -ends
 
     private LogCorrelationshipPrinter logCorrelationshipPrinter;
 
@@ -93,8 +89,6 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                     .scenarioName(scenario.getScenarioName());
 
             for (Step thisStep : scenario.getSteps()) {
-                // Another way to get the String
-                // String requestJson = objectMapper.valueToTree(thisStep.getRequest()).toString();
 
                 final int stepLoopTimes = thisStep.getLoop() == null ? 1 : thisStep.getLoop();
                 for (int i = 0; i < stepLoopTimes; i++) {
@@ -102,6 +96,8 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                     logCorrelationshipPrinter = LogCorrelationshipPrinter.newInstance(LOGGER);
                     logCorrelationshipPrinter.stepLoop(i);
 
+                    thisStep = extFileProcessor.resolveExtJsonFile(thisStep);
+
                     final String requestJsonAsString = thisStep.getRequest().toString();
 
                     StepExecutionState stepExecutionState = new StepExecutionState();
@@ -123,59 +119,59 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                     String executionResult = "-response not decided-";
 
                     final String logPrefixRelationshipId = logCorrelationshipPrinter.createRelationshipId();
-    
+
                     try {
                         String serviceName = thisStep.getUrl();
                         String operationName = thisStep.getOperation();
-        
+
                         // Resolve the URL patterns if any
                         serviceName = zeroCodeJsonTestProcesor.resolveStringJson(
-                                        serviceName,
-                                        scenarioExecutionState.getResolvedScenarioState()
-                                                                                );
-        
+                                serviceName,
+                                scenarioExecutionState.getResolvedScenarioState()
+                        );
+
                         final LocalDateTime requestTimeStamp = LocalDateTime.now();
                         switch (serviceType(serviceName, operationName)) {
                             case REST_CALL:
                                 serviceName = getFullyQualifiedRestUrl(serviceName);
                                 logCorrelationshipPrinter.aRequestBuilder()
-                                                .stepLoop(i)
-                                                .relationshipId(logPrefixRelationshipId)
-                                                .requestTimeStamp(requestTimeStamp)
-                                                .step(thisStepName)
-                                                .url(serviceName)
-                                                .method(operationName)
-                                                .request(prettyPrintJson(resolvedRequestJson));
-                
+                                        .stepLoop(i)
+                                        .relationshipId(logPrefixRelationshipId)
+                                        .requestTimeStamp(requestTimeStamp)
+                                        .step(thisStepName)
+                                        .url(serviceName)
+                                        .method(operationName)
+                                        .request(prettyPrintJson(resolvedRequestJson));
+
                                 executionResult = serviceExecutor.executeRESTService(serviceName, operationName, resolvedRequestJson);
                                 break;
-            
+
                             case JAVA_CALL:
                                 logCorrelationshipPrinter.aRequestBuilder()
-                                                .stepLoop(i)
-                                                .relationshipId(logPrefixRelationshipId)
-                                                .requestTimeStamp(requestTimeStamp)
-                                                .step(thisStepName)
-                                                .url(serviceName)
-                                                .method(operationName)
-                                                .request(prettyPrintJson(resolvedRequestJson));
-                
+                                        .stepLoop(i)
+                                        .relationshipId(logPrefixRelationshipId)
+                                        .requestTimeStamp(requestTimeStamp)
+                                        .step(thisStepName)
+                                        .url(serviceName)
+                                        .method(operationName)
+                                        .request(prettyPrintJson(resolvedRequestJson));
+
                                 executionResult = serviceExecutor.executeJavaService(serviceName, operationName, resolvedRequestJson);
                                 break;
-            
+
                             case NONE:
                                 logCorrelationshipPrinter.aRequestBuilder()
-                                                .stepLoop(i)
-                                                .relationshipId(logPrefixRelationshipId)
-                                                .requestTimeStamp(requestTimeStamp)
-                                                .step(thisStepName)
-                                                .url(serviceName)
-                                                .method(operationName)
-                                                .request(prettyPrintJson(resolvedRequestJson));
-                
+                                        .stepLoop(i)
+                                        .relationshipId(logPrefixRelationshipId)
+                                        .requestTimeStamp(requestTimeStamp)
+                                        .step(thisStepName)
+                                        .url(serviceName)
+                                        .method(operationName)
+                                        .request(prettyPrintJson(resolvedRequestJson));
+
                                 executionResult = prettyPrintJson(resolvedRequestJson);
                                 break;
-            
+
                             default:
                                 throw new RuntimeException("Opps! Service Undecided. If it is intentional, then leave it blank for same response as request");
                         }
@@ -190,10 +186,9 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                         stepExecutionState.addResponse(executionResult);
                         scenarioExecutionState.addStepState(stepExecutionState.getResolvedStep());
 
-                        /** ******************************
-                         * Handle assertion section -START
-                         * *******************************
-                         */
+                        // ---------------------------------
+                        // Handle assertion section -START
+                        // ---------------------------------
                         String resolvedAssertionJson = zeroCodeJsonTestProcesor.resolveStringJson(
                                 thisStep.getAssertions().toString(),
                                 scenarioExecutionState.getResolvedScenarioState()
@@ -232,11 +227,9 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                                 thisStepName,
                                 failureResults,
                                 notificationHandler::handleAssertionPassed);
-
-                        /** ******************************
-                         * Handle assertion section   -END
-                         * *******************************
-                         */
+                        // ---------------------------------
+                        // Handle assertion section -END
+                        // ---------------------------------
 
                         logCorrelationshipPrinter.result(stepOutcome);
 
@@ -280,7 +273,7 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                         /*
                          * FAILED and Exception reports are generated here
                          */
-                        if(!stepOutcome) {
+                        if (!stepOutcome) {
                             reportBuilder.result(reportResultBuilder.build());
                             reportBuilder.printToFile(scenario.getScenarioName() + logCorrelationshipPrinter.getCorrelationId() + ".json");
                         }
@@ -320,22 +313,22 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
 
         return true;
     }
-    
+
     public void overridePort(int port) {
         this.port = port + "";
     }
-    
+
     public void overrideHost(String host) {
         this.host = host;
     }
-    
+
     public void overrideApplicationContext(String applicationContext) {
         this.applicationContext = applicationContext;
     }
-    
+
     private ServiceType serviceType(String serviceName, String methodName) {
         ServiceType serviceType;
-        
+
         if (StringUtils.isEmpty(serviceName) || isEmpty(methodName)) {
             serviceType = ServiceType.NONE;
         } else if (serviceName != null && serviceName.contains("/")) {
@@ -343,7 +336,7 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
         } else {
             serviceType = ServiceType.JAVA_CALL;
         }
-        
+
         return serviceType;
     }
 


Test patch:
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesor.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesor.java
index 3d4b93d7..5e046766 100644
--- a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesor.java
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesor.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.jsmart.zerocode.core.engine.preprocessor;
 
 import org.jsmart.zerocode.core.engine.assertion.AssertionReport;
diff --git a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImpl.java b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImpl.java
index 27417cf4..68ea9891 100644
--- a/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImpl.java
+++ b/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImpl.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 jApps Ltd and
+ * Copyright 2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.jsmart.zerocode.core.engine.preprocessor;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -38,6 +54,8 @@ public class ZeroCodeJsonTestProcesorImpl implements ZeroCodeJsonTestProcesor {
     /*
      * General place holders
      */
+    public static final String JSON_PAYLOAD_FILE = "JSON.FILE:";
+
     private static final String PREFIX_ASU = "ASU";
     private static final String RANDOM_NUMBER = "RANDOM.NUMBER";
     private static final String RANDOM_STRING_PREFIX = "RANDOM.STRING:";
@@ -85,7 +103,7 @@ public class ZeroCodeJsonTestProcesorImpl implements ZeroCodeJsonTestProcesor {
 
     @Override
     public String resolveStringJson(String requestJsonOrAnyString, String scenarioStateJson) {
-        Map<String, String> parammap = new HashMap<>();
+        Map<String, Object> parammap = new HashMap<>();
 
         final List<String> allTokens = getAllTokens(requestJsonOrAnyString);
         allTokens.forEach(runTimeToken -> {
diff --git a/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImplTest.java b/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImplTest.java
new file mode 100644
index 00000000..1e40d087
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeExternalFileProcessorImplTest.java
@@ -0,0 +1,100 @@
+package org.jsmart.zerocode.core.engine.preprocessor;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jsmart.zerocode.core.di.ObjectMapperProvider;
+import org.jsmart.zerocode.core.domain.Step;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static com.jayway.jsonpath.JsonPath.read;
+import static org.hamcrest.core.Is.is;
+import static org.jsmart.zerocode.core.utils.SmartUtils.readJsonAsString;
+import static org.junit.Assert.assertThat;
+
+public class ZeroCodeExternalFileProcessorImplTest {
+    private final ObjectMapper objectMapper = new ObjectMapperProvider().get();
+    private ZeroCodeExternalFileProcessorImpl externalFileProcessor = new ZeroCodeExternalFileProcessorImpl(objectMapper);
+
+    @Test(expected = RuntimeException.class)
+    public void test_wrongFileException() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_test_wrong_file_ref.json");
+        Map<String, Object> map = objectMapper.readValue(jsonAsString, new TypeReference<Map<String, Object>>() {});
+
+        externalFileProcessor.digReplaceContent(map);
+    }
+
+    @Test
+    public void test_deepHashMapTraverse() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_test_file.json");
+        Map<String, Object> map = objectMapper.readValue(jsonAsString, new TypeReference<Map<String, Object>>() {});
+
+        externalFileProcessor.digReplaceContent(map);
+        String resultJson = objectMapper.writeValueAsString(map);
+
+        assertThat(read(resultJson, "$.request.body.id"), is("Emp-No-${RANDOM.NUMBER}"));
+        assertThat(read(resultJson, "$.request.headers.secret"), is("passwwrd"));
+        assertThat(read(resultJson, "$.assertions.body.age"), is(16));
+    }
+
+    @Test
+    public void test_addressArray() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_test_address_array.json");
+        Map<String, Object> map = objectMapper.readValue(jsonAsString, new TypeReference<Map<String, Object>>() {});
+
+        externalFileProcessor.digReplaceContent(map);
+        String resultJson = objectMapper.writeValueAsString(map);
+
+        assertThat(read(resultJson, "$.request.body.addresses[0].type"), is("corp-office"));
+        assertThat(read(resultJson, "$.request.body.addresses[1].type"), is("hr-office"));
+    }
+
+    @Test
+    public void test_NoExtJsonFile() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_no_ext_json_test_file.json");
+        Map<String, Object> map = objectMapper.readValue(jsonAsString, new TypeReference<Map<String, Object>>() {});
+
+        externalFileProcessor.digReplaceContent(map);
+        String resultJson = objectMapper.writeValueAsString(map);
+
+        assertThat(read(resultJson, "$.request.body.name"), is("Emma"));
+        assertThat(read(resultJson, "$.assertions.body.id"), is(100));
+    }
+
+    @Test
+    public void test_NoExtFileCheckDigNeeded() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_no_ext_json_test_file.json");
+        Step step = objectMapper.readValue(jsonAsString, Step.class);
+        assertThat(externalFileProcessor.checkDigNeeded(step), is(false));
+
+        jsonAsString = readJsonAsString("filebody_unit_test/json_step_text_node_ext_json_file_test.json");
+        step = objectMapper.readValue(jsonAsString, Step.class);
+        assertThat(externalFileProcessor.checkDigNeeded(step), is(true));
+    }
+
+    @Test
+    public void test_textNode() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_text_request.json");
+        Step step = objectMapper.readValue(jsonAsString, Step.class);
+
+        externalFileProcessor.resolveExtJsonFile(step);
+        String resultJsonStep = objectMapper.writeValueAsString(step);
+
+        assertThat(read(resultJsonStep, "$.request"), is("I am a simple text"));
+    }
+
+    @Test
+    public void test_textNodeExtFile() throws IOException {
+        String jsonAsString = readJsonAsString("filebody_unit_test/json_step_text_node_ext_json_file_test.json");
+        Step step = objectMapper.readValue(jsonAsString, Step.class);
+
+        Step effectiveStep = externalFileProcessor.resolveExtJsonFile(step);
+        String resultJsonStep = objectMapper.writeValueAsString(effectiveStep);
+
+        assertThat(read(resultJsonStep, "$.request.body.id"), is("Emp-No-${RANDOM.NUMBER}"));
+        assertThat(read(resultJsonStep, "$.request.headers.api_key"), is("hello key"));
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/filebody/ReuseFileBodyTest.java b/src/test/java/org/jsmart/zerocode/filebody/ReuseFileBodyTest.java
new file mode 100644
index 00000000..52434e00
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/filebody/ReuseFileBodyTest.java
@@ -0,0 +1,30 @@
+package org.jsmart.zerocode.filebody;
+
+import org.jsmart.zerocode.core.domain.JsonTestCase;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
+import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@TargetEnv("hello_world_host.properties")
+@RunWith(TestOnlyZeroCodeUnitRunner.class)
+public class ReuseFileBodyTest {
+
+    @Test
+    @JsonTestCase("filebody/hello_world_all_integrated_apis.json")
+    public void testHelloWorld_AllApi() throws Exception {
+    }
+
+    @Test
+    @JsonTestCase("filebody/hello_world_file_body.json")
+    public void test_fileBody() throws Exception {
+    }
+
+    @Test
+    @JsonTestCase("filebody/hello_world_file_request_n_response.json")
+    public void test_fileBodyAndAssertions() throws Exception {
+    }
+
+}
diff --git a/src/test/resources/filebody/bodyonly/request_body.json b/src/test/resources/filebody/bodyonly/request_body.json
new file mode 100644
index 00000000..c055aecc
--- /dev/null
+++ b/src/test/resources/filebody/bodyonly/request_body.json
@@ -0,0 +1,4 @@
+{
+    "name": "Shower-${RANDOM.NUMBER}",
+    "availability": true
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody/bodyonly/response_body.json b/src/test/resources/filebody/bodyonly/response_body.json
new file mode 100644
index 00000000..fd14fe56
--- /dev/null
+++ b/src/test/resources/filebody/bodyonly/response_body.json
@@ -0,0 +1,4 @@
+{
+    "name": "$NOT.NULL",
+    "availability": true
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody/hello_world_all_integrated_apis.json b/src/test/resources/filebody/hello_world_all_integrated_apis.json
new file mode 100644
index 00000000..314146cf
--- /dev/null
+++ b/src/test/resources/filebody/hello_world_all_integrated_apis.json
@@ -0,0 +1,34 @@
+{
+    "scenarioName": "POST via file body - usual way",
+    "steps": [
+        {
+            "name": "create_emp",
+            "url": "/home/bathroom",
+            "operation": "POST",
+            "request": {
+                "body": {
+                    "name": "Shower",
+                    "availability": true
+                }
+            },
+            "assertions": {
+                "status": 201
+            }
+        },
+        {
+            "name": "get_user_details",
+            "url": "/home/bathroom/${$.create_emp.response.body.id}",
+            "operation": "GET",
+            "request": {
+            },
+            "assertions": {
+                "status": 200,
+                "body": {
+                    "id": 1,
+                    "name": "Shower",
+                    "availability": true
+                }
+            }
+        }
+    ]
+}
diff --git a/src/test/resources/filebody/hello_world_file_body.json b/src/test/resources/filebody/hello_world_file_body.json
new file mode 100644
index 00000000..0fc5fc7a
--- /dev/null
+++ b/src/test/resources/filebody/hello_world_file_body.json
@@ -0,0 +1,31 @@
+{
+    "scenarioName": "POST via file body - reuse payload",
+    "steps": [
+        {
+            "name": "create_emp",
+            "url": "/home/bathroom",
+            "operation": "POST",
+            "request": {
+                "body" : "${JSON.FILE:filebody/bodyonly/request_body.json}"
+            },
+            "assertions": {
+                "status": 201
+            }
+        },
+        {
+            "name": "get_user_details",
+            "url": "/home/bathroom/${$.create_emp.response.body.id}",
+            "operation": "GET",
+            "request": {
+            },
+            "assertions": {
+                "status": 200,
+                "body": {
+                    "id" : 1,
+                    "name" : "Shower",
+                    "availability" : true
+                }
+            }
+        }
+    ]
+}
diff --git a/src/test/resources/filebody/hello_world_file_request_n_response.json b/src/test/resources/filebody/hello_world_file_request_n_response.json
new file mode 100644
index 00000000..b337950c
--- /dev/null
+++ b/src/test/resources/filebody/hello_world_file_request_n_response.json
@@ -0,0 +1,27 @@
+{
+    "scenarioName": "POST via file body - reuse payload",
+    "steps": [
+        {
+            "name": "create_emp",
+            "url": "/home/bathroom",
+            "operation": "POST",
+            "request": {
+                "body" : "${JSON.FILE:filebody/bodyonly/request_body.json}"
+            },
+            "assertions": {
+                "status": 201
+            }
+        },
+        {
+            "name": "get_user_details",
+            "url": "/home/bathroom/${$.create_emp.response.body.id}",
+            "operation": "GET",
+            "request": {
+            },
+            "assertions": {
+                "status": 200,
+                "body" : "${JSON.FILE:filebody/bodyonly/response_body.json}"
+            }
+        }
+    ]
+}
diff --git a/src/test/resources/filebody_unit_test/common/comm_addresses.json b/src/test/resources/filebody_unit_test/common/comm_addresses.json
new file mode 100644
index 00000000..42205f79
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/common/comm_addresses.json
@@ -0,0 +1,10 @@
+[
+    {
+        "type":"corp-office",
+        "city":"Texas"
+    },
+    {
+        "type":"hr-office",
+        "city":"Newark"
+    }
+]
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/common/common_content.json b/src/test/resources/filebody_unit_test/common/common_content.json
new file mode 100644
index 00000000..acaacd41
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/common/common_content.json
@@ -0,0 +1,5 @@
+{
+    "id": "Emp-No-${RANDOM.NUMBER}",
+    "age": 16,
+    "secret": "passwwrd"
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/common/common_content_request.json b/src/test/resources/filebody_unit_test/common/common_content_request.json
new file mode 100644
index 00000000..f300e213
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/common/common_content_request.json
@@ -0,0 +1,10 @@
+{
+    "headers":{
+        "api_key":"hello key"
+    },
+    "body": {
+        "id": "Emp-No-${RANDOM.NUMBER}",
+        "age": 16,
+        "secret": "passwwrd"
+    }
+}
diff --git a/src/test/resources/filebody_unit_test/json_step_no_ext_json_test_file.json b/src/test/resources/filebody_unit_test/json_step_no_ext_json_test_file.json
new file mode 100644
index 00000000..efab9030
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_no_ext_json_test_file.json
@@ -0,0 +1,16 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": {
+        "body": {
+            "name": "Emma"
+        }
+    },
+    "assertions": {
+        "status": 201,
+        "body": {
+            "id": 100
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/json_step_test_address_array.json b/src/test/resources/filebody_unit_test/json_step_test_address_array.json
new file mode 100644
index 00000000..1169709f
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_test_address_array.json
@@ -0,0 +1,16 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": {
+        "headers": "${JSON.FILE:filebody_unit_test/common/common_content.json}",
+        "body": {
+            "name": "Mr Gates",
+            "addresses": "${JSON.FILE:filebody_unit_test/common/comm_addresses.json}"
+        }
+    },
+    "assertions": {
+        "status": 201,
+        "body": "${JSON.FILE:filebody_unit_test/common/common_content.json}"
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/json_step_test_file.json b/src/test/resources/filebody_unit_test/json_step_test_file.json
new file mode 100644
index 00000000..5cf878ea
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_test_file.json
@@ -0,0 +1,13 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": {
+        "headers": "${JSON.FILE:filebody_unit_test/common/common_content.json}",
+        "body": "${JSON.FILE:filebody_unit_test/common/common_content.json}"
+    },
+    "assertions": {
+        "status": 201,
+        "body": "${JSON.FILE:filebody_unit_test/common/common_content.json}"
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/json_step_test_wrong_file_ref.json b/src/test/resources/filebody_unit_test/json_step_test_wrong_file_ref.json
new file mode 100644
index 00000000..5454dfab
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_test_wrong_file_ref.json
@@ -0,0 +1,13 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": {
+        "headers": "${JSON.FILE:filebody_unit_test/common/common_content_WRONG.json}",
+        "body": "${JSON.FILE:filebody_unit_test/common/common_content.json}"
+    },
+    "assertions": {
+        "status": 201,
+        "body": "${JSON.FILE:filebody_unit_test/common/common_content.json}"
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/json_step_text_node_ext_json_file_test.json b/src/test/resources/filebody_unit_test/json_step_text_node_ext_json_file_test.json
new file mode 100644
index 00000000..6dff544d
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_text_node_ext_json_file_test.json
@@ -0,0 +1,12 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": "${JSON.FILE:filebody_unit_test/common/common_content_request.json}",
+    "assertions": {
+        "status": 201,
+        "body": {
+            "id": 100
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/filebody_unit_test/json_step_text_request.json b/src/test/resources/filebody_unit_test/json_step_text_request.json
new file mode 100644
index 00000000..509d4cdc
--- /dev/null
+++ b/src/test/resources/filebody_unit_test/json_step_text_request.json
@@ -0,0 +1,12 @@
+{
+    "name": "create_emp",
+    "url": "/api/test/v1/employess",
+    "operation": "POST",
+    "request": "I am a simple text",
+    "assertions": {
+        "status": 201,
+        "body": {
+            "id": 100
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/hello_world_host.properties b/src/test/resources/hello_world_host.properties
new file mode 100644
index 00000000..da73caca
--- /dev/null
+++ b/src/test/resources/hello_world_host.properties
@@ -0,0 +1,6 @@
+# Web Server host and port
+restful.application.endpoint.host=http://localhost
+restful.application.endpoint.port=9998
+
+# Web Service context; Leave it blank in case you do not have a common context
+restful.application.endpoint.context=
diff --git a/src/test/resources/simulators/test_purpose_end_points.json b/src/test/resources/simulators/test_purpose_end_points.json
index 51af93e6..ea2cba5e 100644
--- a/src/test/resources/simulators/test_purpose_end_points.json
+++ b/src/test/resources/simulators/test_purpose_end_points.json
@@ -1,5 +1,5 @@
 {
-    "name": "Bathroom-Simulator",
+    "name": "Mock endpoints Simulator - API Stubs",
     "apis": [
         {
             "name": "Get Bathroom by Id",
@@ -18,6 +18,7 @@
             "name": "POST Bathroom - Create",
             "operation": "POST",
             "url": "/home/bathroom",
+            "ignoreBody": true,
             "response": {
                 "status": 201,
                 "body": {