Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Issue #126] Json Schema Validation #682

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
5 changes: 5 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public Parameterized getParameterized() {
return parameterized;
}

public void addStep(Step step) {
this.steps.add(step);
}
@Override
public String toString() {
return "ScenarioSpec{" +
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/java/org/jsmart/zerocode/core/domain/Schema.java
Original file line number Diff line number Diff line change
@@ -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();
}
48 changes: 48 additions & 0 deletions core/src/main/java/org/jsmart/zerocode/core/domain/SchemaStep.java
Original file line number Diff line number Diff line change
@@ -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<Validator> 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() +
'}';
}
}
12 changes: 12 additions & 0 deletions core/src/main/java/org/jsmart/zerocode/core/domain/Schemas.java
Original file line number Diff line number Diff line change
@@ -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 {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);


}
Original file line number Diff line number Diff line change
@@ -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<ValidationMessage> errors = jsonSchema.validate(jsonFile);
return errors.isEmpty();
}
}

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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) {

Expand All @@ -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<Step>() , parameterizedScenario.getParameterized() , parameterizedScenario.getMeta());

resultReportBuilder = newInstance()
.loop(scnCount)
.scenarioName(parameterizedScenario.getScenarioName());
Expand Down Expand Up @@ -255,6 +261,7 @@ private Boolean executeRetry(RunNotifier notifier,
}
executionResult = executeApi(logPrefixRelationshipId, thisStep, resolvedRequestJson, scenarioExecutionState);


// logging response
final LocalDateTime responseTimeStamp = LocalDateTime.now();
correlLogger.aResponseBuilder()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -64,7 +69,7 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
private ZerocodeCorrelationshipLogger corrLogger;
protected boolean testRunCompleted;
protected boolean passed;

protected Schema schemaAnno;
private ZeroCodeMultiStepsScenarioRunner multiStepsRunner;

/**
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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();
Expand All @@ -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));
Expand All @@ -240,6 +279,8 @@ private void runLeafJsonTest(RunNotifier notifier, Description description, Json
notifier.fireTestFinished(description);
}



private List<String> getSmartChildrenList() {
List<FrameworkMethod> children = getChildren();
children.forEach(
Expand Down Expand Up @@ -368,6 +409,4 @@ public String value() {

return jsonTestCase.value() == null ? null : jsonTestCase;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@ public class JustHelloWorldTest {
@Scenario("helloworld/hello_world_status_ok_assertions.json")
public void testGet() throws Exception {
}

}
}
Original file line number Diff line number Diff line change
@@ -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 {
}

}
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
]
}
Loading