diff --git a/core/pom.xml b/core/pom.xml
index f59e00aa5..c6a437e5a 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -192,6 +192,11 @@
com.google.protobuf
protobuf-java-util
+
+ com.networknt
+ json-schema-validator
+ 1.4.0
+
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/ScenarioSpec.java b/core/src/main/java/org/jsmart/zerocode/core/domain/ScenarioSpec.java
index d2425eb3c..93c11d565 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/domain/ScenarioSpec.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/ScenarioSpec.java
@@ -62,6 +62,9 @@ public Parameterized getParameterized() {
return parameterized;
}
+ public void addStep(Step step) {
+ this.steps.add(step);
+ }
@Override
public String toString() {
return "ScenarioSpec{" +
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/Schema.java b/core/src/main/java/org/jsmart/zerocode/core/domain/Schema.java
new file mode 100644
index 000000000..c08359741
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/Schema.java
@@ -0,0 +1,11 @@
+package org.jsmart.zerocode.core.domain;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable( value = Schemas.class )
+public @interface Schema {
+ String value();
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/SchemaStep.java b/core/src/main/java/org/jsmart/zerocode/core/domain/SchemaStep.java
new file mode 100644
index 000000000..1002e3598
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/SchemaStep.java
@@ -0,0 +1,48 @@
+package org.jsmart.zerocode.core.domain;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.github.tomakehurst.wiremock.common.Json;
+
+import java.util.List;
+
+public class SchemaStep extends Step{
+
+ private JsonNode response;
+ public SchemaStep(Integer loop, Retry retry, String name, String operation, String method, String url, JsonNode request, List validators, JsonNode sort, JsonNode assertions, JsonNode verify , JsonNode response , String verifyMode, boolean ignoreStep) {
+ super(loop, retry, name, operation, method, url, request, validators, sort, assertions, verify, verifyMode, ignoreStep);
+
+ this.response = response;
+ }
+
+ public JsonNode getResponse()
+ {
+ return response;
+ }
+
+ public void setResponse(JsonNode response) {
+ this.response = response;
+ }
+
+ @Override
+ public String toString() {
+ return "SchemaStep{" +
+ "loop=" + getLoop() +
+ ", retry='" + getRetry() + '\'' +
+ ", name='" + getName() + '\'' +
+ ", method='" + getMethod() + '\'' +
+ ", operation='" + getOperation() + '\'' +
+ ", url='" + getUrl() + '\'' +
+ ", request=" + getRequest() +
+ ", validators=" + getValidators() +
+ ", assertions=" + getAssertions() +
+ ", response=" + response +
+ ", verifyMode=" + getVerifyMode() +
+ ", verify=" + getVerify() +
+ ", id='" + getId() + '\'' +
+ ", stepFile=" + getStepFile() +
+ ", stepFiles=" + getStepFiles() +
+ ", parameterized=" + getParameterized() +
+ ", customLog=" + getCustomLog() +
+ '}';
+ }
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/Schemas.java b/core/src/main/java/org/jsmart/zerocode/core/domain/Schemas.java
new file mode 100644
index 000000000..9f427ef09
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/Schemas.java
@@ -0,0 +1,12 @@
+package org.jsmart.zerocode.core.domain;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Schemas {
+ public Schema[] value() default {};
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
index 814b7bd13..7bd108ec8 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/StepExecutionState.java
@@ -31,6 +31,16 @@ public void addRequest(String requestJson) {
}
+ public String getResponse()
+ {
+ return paramMap.get("STEP.RESPONSE");
+ }
+
+ public String getRequest()
+ {
+ return paramMap.get("STEP.REQUEST");
+ }
+
public void addResponse(String responseJson) {
paramMap.put("STEP.RESPONSE", responseJson);
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcher.java b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcher.java
new file mode 100644
index 000000000..c76262ed7
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcher.java
@@ -0,0 +1,10 @@
+package org.jsmart.zerocode.core.engine.preprocessor;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public interface ZeroCodeJsonSchemaMatcher {
+
+ public boolean ismatching(JsonNode JsonFile , JsonNode JsonSchema);
+
+
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcherImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcherImpl.java
new file mode 100644
index 000000000..e0898c07d
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonSchemaMatcherImpl.java
@@ -0,0 +1,23 @@
+package org.jsmart.zerocode.core.engine.preprocessor;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.ValidationMessage;
+import java.util.Set;
+
+
+public class ZeroCodeJsonSchemaMatcherImpl implements ZeroCodeJsonSchemaMatcher {
+
+
+ @Override
+ public boolean ismatching(JsonNode jsonFile, JsonNode schema) {
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
+ JsonSchema jsonSchema = factory.getSchema(schema);
+ Set errors = jsonSchema.validate(jsonFile);
+ return errors.isEmpty();
+ }
+}
+
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 ca5518366..cdd87cc99 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
@@ -1,6 +1,7 @@
package org.jsmart.zerocode.core.runner;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
import com.google.inject.Inject;
@@ -13,6 +14,7 @@
import org.jsmart.zerocode.core.domain.Parameterized;
import org.jsmart.zerocode.core.domain.ScenarioSpec;
+import org.jsmart.zerocode.core.domain.SchemaStep;
import org.jsmart.zerocode.core.domain.Step;
import org.jsmart.zerocode.core.domain.builders.ZeroCodeExecReportBuilder;
@@ -108,6 +110,8 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
private Boolean stepOutcomeGreen;
+ private ScenarioSpec schemaScenario;
+
@Override
public synchronized boolean runScenario(ScenarioSpec scenario, RunNotifier notifier, Description description) {
@@ -133,6 +137,8 @@ public synchronized boolean runScenario(ScenarioSpec scenario, RunNotifier notif
ScenarioSpec parameterizedScenario = parameterizedProcessor.resolveParameterized(scenario, scnCount);
+ schemaScenario = new ScenarioSpec(parameterizedScenario.getLoop() , parameterizedScenario.getIgnoreStepFailures() , parameterizedScenario.getScenarioName() , new ArrayList() , parameterizedScenario.getParameterized() , parameterizedScenario.getMeta());
+
resultReportBuilder = newInstance()
.loop(scnCount)
.scenarioName(parameterizedScenario.getScenarioName());
@@ -255,6 +261,7 @@ private Boolean executeRetry(RunNotifier notifier,
}
executionResult = executeApi(logPrefixRelationshipId, thisStep, resolvedRequestJson, scenarioExecutionState);
+
// logging response
final LocalDateTime responseTimeStamp = LocalDateTime.now();
correlLogger.aResponseBuilder()
@@ -315,6 +322,16 @@ private Boolean executeRetry(RunNotifier notifier,
continue;
}
+
+
+ JsonNode schemaRequest = objectMapper.readTree(stepExecutionState.getRequest()+"\n" );
+ JsonNode schemaResponse = objectMapper.readTree(executionResult);
+
+ SchemaStep schemaStep = new SchemaStep(thisStep.getLoop() , thisStep.getRetry() , thisStep.getName() , thisStep.getOperation() , thisStep.getMethod() , thisStep.getUrl() ,schemaRequest , thisStep.getValidators() , thisStep.getSort() , null , null , schemaResponse , thisStep.getVerifyMode() , thisStep.getIgnoreStep());
+
+ schemaScenario.addStep(schemaStep);
+
+
boolean ignoreStepFailures = scenario.getIgnoreStepFailures() == null ? false : scenario.getIgnoreStepFailures();
if (!failureResults.isEmpty()) {
stepOutcomeGreen = notificationHandler.handleAssertion(
@@ -602,4 +619,11 @@ else if (ofNullable(thisStep.getVerifyMode()).orElse("LENIENT").equals("STRICT")
return failureResults;
}
+ public ScenarioSpec getSchemaScenario() {
+ return schemaScenario;
+ }
+
+ public void setSchemaScenario(ScenarioSpec scenarioSpec) {
+ this.schemaScenario = scenarioSpec;
+ }
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
index 6c659e247..89d4ea8b6 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
@@ -1,13 +1,10 @@
package org.jsmart.zerocode.core.runner;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Modules;
-import java.lang.annotation.Annotation;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
import org.jsmart.zerocode.core.di.module.RuntimeHttpClientModule;
import org.jsmart.zerocode.core.di.module.RuntimeKafkaClientModule;
@@ -18,9 +15,11 @@
import org.jsmart.zerocode.core.domain.TargetEnv;
import org.jsmart.zerocode.core.domain.UseHttpClient;
import org.jsmart.zerocode.core.domain.UseKafkaClient;
+import org.jsmart.zerocode.core.domain.Schema;
import org.jsmart.zerocode.core.domain.builders.ZeroCodeExecReportBuilder;
import org.jsmart.zerocode.core.domain.builders.ZeroCodeIoWriteBuilder;
import org.jsmart.zerocode.core.engine.listener.TestUtilityListener;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeJsonSchemaMatcherImpl;
import org.jsmart.zerocode.core.httpclient.BasicHttpClient;
import org.jsmart.zerocode.core.httpclient.ssl.SslTrustHttpClient;
import org.jsmart.zerocode.core.kafka.client.BasicKafkaClient;
@@ -41,6 +40,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.lang.annotation.Annotation;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
import static java.lang.System.getProperty;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.CHARTS_AND_CSV;
import static org.jsmart.zerocode.core.constants.ZeroCodeReportConstants.ZEROCODE_JUNIT;
@@ -64,7 +69,7 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
private ZerocodeCorrelationshipLogger corrLogger;
protected boolean testRunCompleted;
protected boolean passed;
-
+ protected Schema schemaAnno;
private ZeroCodeMultiStepsScenarioRunner multiStepsRunner;
/**
@@ -118,11 +123,13 @@ protected void runChild(FrameworkMethod method, RunNotifier notifier) {
jsonTestCaseAnno = evalScenarioToJsonTestCase(method.getMethod().getAnnotation(Scenario.class));
}
+ schemaAnno = method.getMethod().getAnnotation(Schema.class);
+
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
- } else if (jsonTestCaseAnno != null) {
+ } else if (jsonTestCaseAnno != null ) {
runLeafJsonTest(notifier, description, jsonTestCaseAnno);
@@ -205,6 +212,7 @@ protected ZeroCodeReportGenerator getInjectedReportGenerator() {
return getMainModuleInjector().getInstance(ZeroCodeReportGenerator.class);
}
+
private void runLeafJsonTest(RunNotifier notifier, Description description, JsonTestCase jsonTestCaseAnno) {
if (jsonTestCaseAnno != null) {
currentTestCase = jsonTestCaseAnno.value();
@@ -221,7 +229,38 @@ private void runLeafJsonTest(RunNotifier notifier, Description description, Json
LOGGER.debug("### Found currentTestCase : -" + child);
passed = multiStepsRunner.runScenario(child, notifier, description);
+ //Schema validation
+ if( schemaAnno == null )
+ {
+ LOGGER.warn("### No Json Schema was added for validation");
+ }
+ else
+ {
+ LOGGER.debug("### Found Json Schema : - \n" + smartUtils.prettyPrintJson(smartUtils.readJsonAsString(schemaAnno.value())));
+ // Schema Validation is performed here...
+
+ ObjectMapper mapper = new ObjectMapper();
+
+ ZeroCodeJsonSchemaMatcherImpl zeroCodeJsonSchemaMatcher = new ZeroCodeJsonSchemaMatcherImpl();
+
+ JsonNode jsonFile = mapper.valueToTree(((ZeroCodeMultiStepsScenarioRunnerImpl)multiStepsRunner).getSchemaScenario());
+
+ String jsonScenarioFile = SmartUtils.readJsonAsString( schemaAnno.value() );
+
+ JsonNode schemaFile = mapper.readTree(jsonScenarioFile);
+
+ if( zeroCodeJsonSchemaMatcher.ismatching(jsonFile , schemaFile) )
+ {
+ LOGGER.debug("### Json Schema matches with the Json test file." );
+
+ }
+ else
+ {
+ LOGGER.error("### Json Schema does not matches with the Json test file.");
+ throw new Exception("Json Schema does not matches with the Json test file.");
+ }
+ }
} catch (Exception ioEx) {
ioEx.printStackTrace();
notifier.fireTestFailure(new Failure(description, ioEx));
@@ -240,6 +279,8 @@ private void runLeafJsonTest(RunNotifier notifier, Description description, Json
notifier.fireTestFinished(description);
}
+
+
private List getSmartChildrenList() {
List children = getChildren();
children.forEach(
@@ -368,6 +409,4 @@ public String value() {
return jsonTestCase.value() == null ? null : jsonTestCase;
}
-
-
}
diff --git a/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/helloworld/JustHelloWorldTest.java b/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/helloworld/JustHelloWorldTest.java
index 12046e8eb..fd8edbbaf 100644
--- a/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/helloworld/JustHelloWorldTest.java
+++ b/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/helloworld/JustHelloWorldTest.java
@@ -14,5 +14,4 @@ public class JustHelloWorldTest {
@Scenario("helloworld/hello_world_status_ok_assertions.json")
public void testGet() throws Exception {
}
-
-}
+}
\ No newline at end of file
diff --git a/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/hellowworldschema/JustHelloWorldSchemaTest.java b/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/hellowworldschema/JustHelloWorldSchemaTest.java
new file mode 100644
index 000000000..2a49e6975
--- /dev/null
+++ b/http-testing/src/test/java/org/jsmart/zerocode/testhelp/tests/hellowworldschema/JustHelloWorldSchemaTest.java
@@ -0,0 +1,20 @@
+package org.jsmart.zerocode.testhelp.tests.hellowworldschema;
+
+import org.jsmart.zerocode.core.domain.Scenario;
+import org.jsmart.zerocode.core.domain.Schema;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@TargetEnv("github_host.properties")
+@RunWith(ZeroCodeUnitRunner.class)
+public class JustHelloWorldSchemaTest {
+
+ @Test
+ @Scenario("helloworldschema/hello_world_status_ok_assertions.json")
+ @Schema("helloworldschema/hello_world_status_ok_assertions_schema.json")
+ public void testGet() throws Exception {
+ }
+
+}
\ No newline at end of file
diff --git a/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions.json b/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions.json
new file mode 100644
index 000000000..0873b3a3f
--- /dev/null
+++ b/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions.json
@@ -0,0 +1,20 @@
+{
+ "scenarioName": "GIVEN- the GitHub REST end point, WHEN- I invoke GET, THEN- I will receive the 200 status with body",
+ "steps": [
+ {
+ "name": "get_user_details",
+ "url": "/users/octocat",
+ "method": "GET",
+ "request": {
+ },
+ "verify": {
+ "status": 200,
+ "body": {
+ "login" : "octocat",
+ "id" : 583231,
+ "type" : "User"
+ }
+ }
+ }
+ ]
+}
diff --git a/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions_schema.json b/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions_schema.json
new file mode 100644
index 000000000..3e5bf9878
--- /dev/null
+++ b/http-testing/src/test/resources/helloworldschema/hello_world_status_ok_assertions_schema.json
@@ -0,0 +1,69 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "scenarioName": {
+ "type": "string"
+ },
+ "steps": {
+ "type": "array",
+ "items": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ },
+ "method": {
+ "type": "string"
+ },
+ "request": {
+ "type": "object"
+ },
+ "response": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "integer"
+ },
+ "body": {
+ "type": "object",
+ "properties": {
+ "login": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "login",
+ "id",
+ "type"
+ ]
+ }
+ },
+ "required": [
+ "status",
+ "body"
+ ]
+ }
+ },
+ "required": [
+ "response"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "scenarioName",
+ "steps"
+ ]
+}
\ No newline at end of file