From fa09b00780db9cccb0a8d462067c5d083e502de8 Mon Sep 17 00:00:00 2001 From: Victor Orlovsky Date: Mon, 23 Jul 2018 16:10:13 +0300 Subject: [PATCH 1/2] JsonUnit has been added --- README.md | 16 +- allure-jsonunit/build.gradle | 14 + .../AllureConfigurableJsonMatcher.java | 26 ++ .../allure/jsonunit/DiffAttachment.java | 36 ++ .../allure/jsonunit/JsonPatchListener.java | 133 ++++++++ .../allure/jsonunit/JsonPatchMatcher.java | 102 ++++++ .../src/main/resources/tpl/diff.ftl | 311 ++++++++++++++++++ .../jsonunit/JsonPatchListenerTest.java | 171 ++++++++++ .../allure/restassured/AllureRestAssured.java | 17 +- build.gradle | 3 + examples/jsonunit-patch-listener/build.gradle | 30 ++ .../jsonunit/JsonPatchListenerTest.java | 141 ++++++++ .../src/test/resources/allure.properties | 1 + .../src/test/resources/left.json | 97 ++++++ .../src/test/resources/right.json | 104 ++++++ settings.gradle | 4 +- 16 files changed, 1201 insertions(+), 5 deletions(-) create mode 100644 allure-jsonunit/build.gradle create mode 100644 allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/AllureConfigurableJsonMatcher.java create mode 100644 allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/DiffAttachment.java create mode 100644 allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java create mode 100644 allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java create mode 100644 allure-jsonunit/src/main/resources/tpl/diff.ftl create mode 100644 allure-jsonunit/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java create mode 100644 examples/jsonunit-patch-listener/build.gradle create mode 100644 examples/jsonunit-patch-listener/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java create mode 100644 examples/jsonunit-patch-listener/src/test/resources/allure.properties create mode 100644 examples/jsonunit-patch-listener/src/test/resources/left.json create mode 100644 examples/jsonunit-patch-listener/src/test/resources/right.json diff --git a/README.md b/README.md index 32301cf52..75640bf19 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,11 @@ Usage example: ``` .filter(new AllureRestAssured()) ``` -You can specify custom templateName: +You can specify custom templates, which should be placed in src/main/resources/tpl folder: ``` -.filter(new AllureRestAssured().withTemplate("/templates/custom_template.ftl")) +.filter(new AllureRestAssured() + .withRequestTemplate("custom-http-request.ftl") + .withResponseTemplate("custom-http-response.ftl")) ``` ## OkHttp @@ -136,3 +138,13 @@ Usage example: .addInterceptorLast(new AllureHttpClientResponse()); ``` +## JsonUnit +JsonPatchMatcher is extension of JsonUnit matcher, that generates pretty html attachment for differences based on [json diff patch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md). + +```xml + + io.qameta.allure + allure-jsonunit + $LATEST_VERSION + +``` \ No newline at end of file diff --git a/allure-jsonunit/build.gradle b/allure-jsonunit/build.gradle new file mode 100644 index 000000000..91f80ede8 --- /dev/null +++ b/allure-jsonunit/build.gradle @@ -0,0 +1,14 @@ +description = 'Allure JsonUnit' + +apply from: "${gradleScriptDir}/maven-publish.gradle" +apply from: "${gradleScriptDir}/bintray.gradle" +apply plugin: 'maven' + +dependencies { + compile project(':allure-attachments') + compile('net.javacrumbs.json-unit:json-unit:2.0.0.RC1') + compile('com.google.code.gson:gson') + + testCompile('junit:junit') + testCompile('org.hamcrest:hamcrest-library') +} diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/AllureConfigurableJsonMatcher.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/AllureConfigurableJsonMatcher.java new file mode 100644 index 000000000..75cbd4020 --- /dev/null +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/AllureConfigurableJsonMatcher.java @@ -0,0 +1,26 @@ +package io.qameta.allure.jsonunit; + +import net.javacrumbs.jsonunit.core.Option; +import net.javacrumbs.jsonunit.core.internal.Options; +import org.hamcrest.Matcher; + +import java.math.BigDecimal; + +/** + * @param the type of matcher + * @see net.javacrumbs.jsonunit.ConfigurableJsonMatcher + */ +public interface AllureConfigurableJsonMatcher extends Matcher { + + AllureConfigurableJsonMatcher withTolerance(BigDecimal tolerance); + + AllureConfigurableJsonMatcher withTolerance(double tolerance); + + AllureConfigurableJsonMatcher when(Option first, Option... next); + + AllureConfigurableJsonMatcher withOptions(Options options); + + AllureConfigurableJsonMatcher withMatcher(String matcherName, Matcher matcher); + + AllureConfigurableJsonMatcher whenIgnoringPaths(String... paths); +} diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/DiffAttachment.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/DiffAttachment.java new file mode 100644 index 000000000..5af031a61 --- /dev/null +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/DiffAttachment.java @@ -0,0 +1,36 @@ +package io.qameta.allure.jsonunit; + +import io.qameta.allure.attachment.AttachmentData; + +/** + * @author Victor Orlovsky + */ +public class DiffAttachment implements AttachmentData { + + private final String patch; + private final String actual; + private final String expected; + + public DiffAttachment(final String actual, final String expected, final String patch) { + this.actual = actual; + this.expected = expected; + this.patch = patch; + } + + public String getPatch() { + return patch; + } + + public String getActual() { + return actual; + } + + public String getExpected() { + return expected; + } + + @Override + public String getName() { + return "JSON difference"; + } +} diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java new file mode 100644 index 000000000..6575a239d --- /dev/null +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java @@ -0,0 +1,133 @@ +package io.qameta.allure.jsonunit; + +import com.google.gson.GsonBuilder; +import net.javacrumbs.jsonunit.core.listener.Difference; +import net.javacrumbs.jsonunit.core.listener.DifferenceContext; +import net.javacrumbs.jsonunit.core.listener.DifferenceListener; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JsonUnit listener, that keeps difference and + * return formatted json to represent deltas + * (i.e. the output of jsondiffpatch.diff). + */ +public class JsonPatchListener implements DifferenceListener { + + private static final String UNKNOWN_TYPE_ERROR = "Difference has unknown type"; + private final List differences = new ArrayList<>(); + + private DifferenceContext context; + + @Override + public void diff(final Difference difference, final DifferenceContext differenceContext) { + this.context = differenceContext; + differences.add(difference); + } + + public List getDifferences() { + return differences; + } + + public DifferenceContext getContext() { + return context; + } + + @SuppressWarnings("ReturnCount") + private String getPath(final Difference difference) { + switch (difference.getType()) { + case DIFFERENT: + return difference.getActualPath(); + + case MISSING: + return difference.getExpectedPath(); + + case EXTRA: + return difference.getActualPath(); + + default: + throw new IllegalArgumentException(UNKNOWN_TYPE_ERROR); + } + } + + @SuppressWarnings("ReturnCount") + private List getPatch(final Difference difference) { + final List result = new ArrayList<>(); + + switch (difference.getType()) { + case DIFFERENT: + result.add(difference.getExpected()); + result.add(difference.getActual()); + return result; + + case MISSING: + result.add(difference.getExpected()); + result.add(0); + result.add(0); + return result; + + case EXTRA: + result.add(difference.getActual()); + return result; + + default: + throw new IllegalArgumentException(UNKNOWN_TYPE_ERROR); + } + } + + @SuppressWarnings({"all", "unchecked"}) + public String getJsonPatch() { + final Map jsonDiffPatch = new HashMap<>(); + // take care of corner case when two jsons absolutelly different + if (getDifferences().size() == 1) { + final Difference difference = getDifferences().get(0); + final String field = getPath(difference); + if (field.isEmpty()) { + return new GsonBuilder().create().toJson(getPatch(difference)); + } + } + getDifferences().forEach(difference -> { + + final String field = getPath(difference); + Map currentMap = jsonDiffPatch; + + final String fieldWithDots = StringUtils.replace(field, "[", "."); + final int len = fieldWithDots.length(); + int left = 0; + int right = 0; + while (left < len) { + right = fieldWithDots.indexOf('.', left); + if (right == -1) { + right = len; + } + String fieldName = fieldWithDots.substring(left, right); + fieldName = StringUtils.remove(fieldName, "]"); + + if (right != len) { + if (!fieldName.isEmpty()) { + if (!currentMap.containsKey(fieldName)) { + currentMap.put(fieldName, new HashMap<>()); + } + currentMap = (Map) currentMap.get(fieldName); + } + + if (field.charAt(right) == '[') { + if (!currentMap.containsKey(fieldName)) { + currentMap.put("_t", "a"); + } + } + } else { + final List actualExpectedValue = getPatch(difference); + currentMap.put(fieldName, actualExpectedValue); + } + left = right + 1; + } + }); + + return new GsonBuilder().create().toJson(jsonDiffPatch); + } +} diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java new file mode 100644 index 000000000..775e24d7e --- /dev/null +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java @@ -0,0 +1,102 @@ +package io.qameta.allure.jsonunit; + +import com.google.gson.GsonBuilder; +import io.qameta.allure.Step; +import io.qameta.allure.attachment.DefaultAttachmentProcessor; +import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; +import net.javacrumbs.jsonunit.core.Configuration; +import net.javacrumbs.jsonunit.core.Option; +import net.javacrumbs.jsonunit.core.internal.Diff; +import net.javacrumbs.jsonunit.core.internal.Options; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.math.BigDecimal; + +/** + * JsonPatchMatcher is extension of JsonUnit matcher, + * that generates pretty html attachment for differences. + * @param the type + */ +public final class JsonPatchMatcher implements AllureConfigurableJsonMatcher { + private static final String EMPTY_PATH = ""; + private static final String FULL_JSON = "fullJson"; + private Configuration configuration = Configuration.empty(); + private final Object expected; + private String differences; + + private JsonPatchMatcher(final Object expected) { + this.expected = expected; + } + + @Step("Has no difference") + public static AllureConfigurableJsonMatcher jsonEquals(final Object expected) { + return new JsonPatchMatcher(expected); + } + + public AllureConfigurableJsonMatcher withTolerance(final BigDecimal tolerance) { + configuration = configuration.withTolerance(tolerance); + return this; + } + + public AllureConfigurableJsonMatcher withTolerance(final double tolerance) { + configuration = configuration.withTolerance(tolerance); + return this; + } + + public AllureConfigurableJsonMatcher when(final Option first, final Option... next) { + configuration = configuration.when(first, next); + return this; + } + + public AllureConfigurableJsonMatcher withOptions(final Options options) { + configuration = configuration.withOptions(options); + return this; + } + + public AllureConfigurableJsonMatcher withMatcher(final String matcherName, final Matcher matcher) { + configuration = configuration.withMatcher(matcherName, matcher); + return this; + } + + public AllureConfigurableJsonMatcher whenIgnoringPaths(final String... paths) { + configuration = configuration.whenIgnoringPaths(paths); + return this; + } + + private void render(final JsonPatchListener listener) { + final Object actual = new GsonBuilder().create().toJson(listener.getContext().getActualSource()); + final Object expected = new GsonBuilder().create().toJson(listener.getContext().getExpectedSource()); + final String patch = listener.getJsonPatch(); + final DiffAttachment attachment = new DiffAttachment(actual.toString(), expected.toString(), patch); + new DefaultAttachmentProcessor().addAttachment(attachment, + new FreemarkerAttachmentRenderer("diff.ftl")); + } + + @Override + public boolean matches(final Object actual) { + final JsonPatchListener listener = new JsonPatchListener(); + final Diff diff = Diff.create(expected, actual, FULL_JSON, EMPTY_PATH, + configuration.withDifferenceListener(listener)); + if (!diff.similar()) { + differences = diff.differences(); + render(listener); + } + return diff.similar(); + } + + public void describeTo(final Description description) { + description.appendText("has no difference"); + } + + @Override + public void describeMismatch(final Object item, final Description description) { + description.appendText(differences); + } + + @SuppressWarnings("deprecation") + @Override + public void _dont_implement_Matcher___instead_extend_BaseMatcher_() { + //do nothing + } +} diff --git a/allure-jsonunit/src/main/resources/tpl/diff.ftl b/allure-jsonunit/src/main/resources/tpl/diff.ftl new file mode 100644 index 000000000..64adfb513 --- /dev/null +++ b/allure-jsonunit/src/main/resources/tpl/diff.ftl @@ -0,0 +1,311 @@ + + + + + + + +
+ + + \ No newline at end of file diff --git a/allure-jsonunit/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java b/allure-jsonunit/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java new file mode 100644 index 000000000..3c61d629b --- /dev/null +++ b/allure-jsonunit/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java @@ -0,0 +1,171 @@ +package io.qameta.allure.jsonunit; + +import com.google.gson.GsonBuilder; +import net.javacrumbs.jsonunit.core.Configuration; +import net.javacrumbs.jsonunit.core.Option; +import net.javacrumbs.jsonunit.core.internal.Diff; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +public class JsonPatchListenerTest { + + private final JsonPatchListener listener = new JsonPatchListener(); + + @Test + public void shouldSeeEmptyDiffNodes() { + Diff diff = Diff.create("{}", "{}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getDifferences(), hasSize(0)); + } + + @Test + public void shouldSeeRemovedNode() { + Diff diff = Diff.create("{\"test\": \"1\"}", "{}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[\"1\",0,0]}")); + } + + @Test + public void shouldSeeAddedNode() { + Diff diff = Diff.create("{}", "{\"test\": \"1\"}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[\"1\"]}")); + } + + @Test + public void shouldSeeEmptyForCheckAnyNode() { + Diff diff = Diff.create("{\"test\": \"${json-unit.ignore}\"}", "{\"test\":\"1\"}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{}")); + } + + @Test + public void shouldSeeEmptyForCheckAnyBooleanNode() { + Diff diff = Diff.create("{\"test\": \"${json-unit.any-boolean}\"}", "{\"test\": true}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{}")); + } + + @Test + public void shouldSeeEmptyForCheckAnyNumberNode() { + Diff diff = Diff.create("{\"test\": \"${json-unit.any-number}\"}", "{\"test\": 11}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{}")); + + } + + @Test + public void shouldSeeEmptyForCheckAnyStringNode() { + Diff diff = Diff.create("{\"test\": \"${json-unit.any-string}\"}", "{\"test\": \"1\"}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{}")); + } + + + @Test + public void shouldSeeChangedStringNode() { + Diff diff = Diff.create("{\"test\": \"1\"}", "{\"test\": \"2\"}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[\"1\",\"2\"]}")); + } + + @Test + public void shouldSeeChangedNumberNode() { + Diff diff = Diff.create("{\"test\": 1}", "{\"test\": 2 }", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[1,2]}")); + + } + + @Test + public void shouldSeeChangedBooleanNode() { + Diff diff = Diff.create("{\"test\": true}", "{\"test\": false}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[true,false]}")); + } + + @Test + public void shouldSeeChangedStructureNode() { + Diff diff = Diff.create("{\"test\": \"1\"}", "{\"test\": false}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":[\"1\",false]}")); + } + + @Test + public void shouldSeeChangedArrayNode() { + Diff diff = Diff.create("[1, 1]", "[1, 2]", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"1\":[1,2],\"_t\":\"a\"}")); + } + + @Test + public void shouldSeeRemovedArrayNode() { + Diff diff = Diff.create("[1, 2]", "[1]", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"1\":[2,0,0],\"_t\":\"a\"}")); + } + + @Test + public void shouldSeeAddedArrayNode() { + Diff diff = Diff.create("[1]", "[1, 2]", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"1\":[2],\"_t\":\"a\"}")); + } + + @Test + public void shouldSeeObjectDiffNodes() { + Diff diff = Diff.create("{\"test\": { \"test1\": \"1\"}}", "{\"test\": { \"test1\": \"2\"} }", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{\"test\":{\"test1\":[\"1\",\"2\"]}}")); + } + + @Test + public void shouldSeeNullNode() { + Diff diff = Diff.create(null, null, "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("{}")); + } + + @Test + public void shouldWorkWhenIgnoringArrayOrder() { + Diff diff = Diff.create("{\"test\": [[1,2],[2,3]]}", "{\"test\":[[4,2],[1,2]]}", "", "", commonConfig().when(Option.IGNORING_ARRAY_ORDER)); + diff.similar(); + assertThat(listener.getJsonPatch(), + equalTo("{\"test\":{\"0\":{\"0\":[3,4],\"_t\":\"a\"},\"_t\":\"a\"}}")); + } + + @Test + public void shouldSeeActualSource() { + Diff diff = Diff.create("{\"test\": \"1\"}", "{}", "", "", commonConfig()); + diff.similar(); + assertThat(new GsonBuilder().create().toJson(listener.getContext().getActualSource()), equalTo("{}")); + } + + @Test + public void shouldSeeExpectedSource() { + Diff diff = Diff.create("{\"test\": \"1\"}", "{}", "", "", commonConfig()); + diff.similar(); + assertThat(new GsonBuilder().create().toJson(listener.getContext().getExpectedSource()), equalTo("{\"test\":\"1\"}")); + } + + @Test + public void shouldSeeNodeChangeToArray() { + Diff diff = Diff.create("{\"test\": \"1\"}", "[[1,2],[2,3],[1,1]]", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("[{\"test\":\"1\"},[[1,2],[2,3],[1,1]]]")); + } + + @Test + public void shouldArrayChangeToNode() { + Diff diff = Diff.create("[[1,2],[2,3],[1,1]]", "{\"test\": \"1\"}", "", "", commonConfig()); + diff.similar(); + assertThat(listener.getJsonPatch(), equalTo("[[[1,2],[2,3],[1,1]],{\"test\":\"1\"}]")); + } + + private Configuration commonConfig() { + return Configuration.empty().withDifferenceListener(listener); + } +} diff --git a/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java b/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java index 9b30a684e..c36329fe2 100644 --- a/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java +++ b/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java @@ -24,6 +24,9 @@ */ public class AllureRestAssured implements OrderedFilter { + private String requestTemplatePath = "http-request.ftl"; + private String responseTemplatePath = "http-response.ftl"; + @Override public Response filter(final FilterableRequestSpecification requestSpec, final FilterableResponseSpecification responseSpec, @@ -44,7 +47,7 @@ public Response filter(final FilterableRequestSpecification requestSpec, new DefaultAttachmentProcessor().addAttachment( requestAttachment, - new FreemarkerAttachmentRenderer("http-request.ftl") + new FreemarkerAttachmentRenderer(requestTemplatePath) ); final Response response = filterContext.next(requestSpec, responseSpec); @@ -56,7 +59,7 @@ public Response filter(final FilterableRequestSpecification requestSpec, new DefaultAttachmentProcessor().addAttachment( responseAttachment, - new FreemarkerAttachmentRenderer("http-response.ftl") + new FreemarkerAttachmentRenderer(responseTemplatePath) ); return response; @@ -68,6 +71,16 @@ private static Map toMapConverter(final Iterable dependencies { dependency 'com.codeborne:selenide:4.12.2' dependency 'com.github.tomakehurst:wiremock:2.18.0' + dependency 'com.google.code.gson:gson:2.8.5' dependency 'com.google.inject:guice:4.2.0' dependency 'com.google.testing.compile:compile-testing:0.15' dependency 'com.squareup.okhttp3:okhttp:3.10.0' @@ -83,6 +84,7 @@ subprojects { project -> dependency 'org.assertj:assertj-core:3.10.0' dependency 'org.codehaus.groovy:groovy-all:2.5.0' dependency 'org.freemarker:freemarker:2.3.28' + dependency 'org.hamcrest:hamcrest-library:1.3' dependency 'org.jbehave:jbehave-core:4.3.4' dependency 'org.jooq:joor-java-8:0.9.9' dependency 'org.junit.jupiter:junit-jupiter-api:5.2.0' @@ -98,6 +100,7 @@ subprojects { project -> dependency 'org.springframework:spring-test:4.3.18.RELEASE' dependency 'org.springframework:spring-webmvc:4.3.18.RELEASE' dependency 'org.testng:testng:6.14.3' + dependency 'net.javacrumbs.json-unit:json-unit:2.0.0.RC1' } } diff --git a/examples/jsonunit-patch-listener/build.gradle b/examples/jsonunit-patch-listener/build.gradle new file mode 100644 index 000000000..06531789d --- /dev/null +++ b/examples/jsonunit-patch-listener/build.gradle @@ -0,0 +1,30 @@ +description = 'Allure JsonUnit patch listener' + +configurations { + agent +} + +dependencies { + agent('org.aspectj:aspectjweaver') + + testCompile project(':allure-jsonunit') + testCompile project(':allure-junit-platform') + testCompile('com.github.tomakehurst:wiremock') + testCompile('org.junit.jupiter:junit-jupiter-api') + testCompile('org.slf4j:slf4j-simple') + testCompile 'org.hamcrest:hamcrest-library' + testRuntime('org.junit.jupiter:junit-jupiter-engine') +} + +test { + systemProperty 'org.slf4j.simpleLogger.defaultLogLevel', 'debug' + systemProperty 'allure.model.indentOutput', true + + useJUnitPlatform { + includeEngines 'junit-jupiter' + } + + doFirst { + jvmArgs "-javaagent:${configurations.agent.singleFile}" + } +} diff --git a/examples/jsonunit-patch-listener/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java b/examples/jsonunit-patch-listener/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java new file mode 100644 index 000000000..900987e9c --- /dev/null +++ b/examples/jsonunit-patch-listener/src/test/java/io/qameta/allure/jsonunit/JsonPatchListenerTest.java @@ -0,0 +1,141 @@ +package io.qameta.allure.jsonunit; + +import com.google.common.base.Charsets; +import org.apache.tika.io.IOUtils; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.qameta.allure.jsonunit.JsonPatchMatcher.jsonEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JsonPatchListenerTest { + + @Test + void shouldSeeEmptyDiffNodes() { + assertThat("{}", jsonEquals("{}")); + } + + @Test + void shouldSeeAddedNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": \"1\"}", jsonEquals("{}"))); + + } + + @Test + void shouldSeeRemovedNode() { + assertThrows(AssertionError.class, () -> assertThat("{}", jsonEquals("{\"test\": \"1\"}"))); + } + + @Test + void shouldSeeEmptyForCheckAnyNode() { + assertThat("{\"test\":\"1\"}", jsonEquals("{\"test\": \"${json-unit.ignore}\"}")); + } + + @Test + void shouldSeeEmptyForCheckAnyBooleanNode() { + assertThat("{\"test\": true}", jsonEquals("{\"test\": \"${json-unit.any-boolean}\"}")); + } + + @Test + void shouldSeeEmptyForCheckAnyNumberNode() { + assertThat("{\"test\": 11}", jsonEquals("{\"test\": \"${json-unit.any-number}\"}")); + } + + @Test + void shouldSeeEmptyForCheckAnyStringNode() { + assertThat("{\"test\": \"1\"}", jsonEquals("{\"test\": \"${json-unit.any-string}\"}")); + } + + + @Test + void shouldSeeChangedStringNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": \"2\"}", + jsonEquals("{\"test\": \"1\"}"))); + } + + @Test + void shouldSeeChangedNumberNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": 2 }", + jsonEquals("{\"test\": 1}"))); + + } + + @Test + void shouldSeeChangedBooleanNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": false}", + jsonEquals("{\"test\": true}"))); + } + + @Test + void shouldSeeChangedStructureNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": false}", + jsonEquals("{\"test\": \"1\"}"))); + } + + @Test + void shouldSeeChangedArrayNode() { + assertThrows(AssertionError.class, () -> assertThat("[1, 2]", jsonEquals("[1, 1]"))); + } + + @Test + void shouldSeeRemovedArrayNode() { + assertThrows(AssertionError.class, () -> assertThat("[1]", jsonEquals("[1, 2]"))); + } + + @Test + void shouldSeeAddedArrayNode() { + assertThrows(AssertionError.class, () -> assertThat("[1, 2]", jsonEquals("[1]"))); + } + + @Test + void shouldSeeObjectDiffNodes() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\": { \"test1\": \"2\"} }", + jsonEquals("{\"test\": { \"test1\": \"1\"}}"))); + } + + @Test + void shouldSeeNullNode() { + assertThat(null, jsonEquals(null)); + } + + @Test + void shouldWorkWhenIgnoringArrayOrder() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\":[[4,2],[1,2]]}", + jsonEquals("{\"test\": [[1,2],[2,3]]}"))); + } + + @Test + void shouldSeeActualSource() { + assertThrows(AssertionError.class, () -> assertThat("{}", jsonEquals("{\"test\": \"1\"}"))); + } + + @Test + void shouldSeeExpectedSource() { + assertThrows(AssertionError.class, () -> assertThat("{}", jsonEquals("{\"test\": \"1\"}"))); + } + + @Test + void shouldSeeArrayChangeToNode() { + assertThrows(AssertionError.class, () -> assertThat("{\"test\":\"1\"}", + jsonEquals("[[1,2],[2,3],[1,1]]"))); + } + + @Test + void shouldSeeChangeNodeToArray() { + assertThrows(AssertionError.class, () -> assertThat("[[1,2],[2,3],[1,1]]", + jsonEquals("{\"test\":\"1\"}"))); + } + + @Test + void shouldSeeDifference() throws IOException { + assertThrows(AssertionError.class, () -> assertThat(getResourceAsString("left.json"), jsonEquals(getResourceAsString("right.json")))); + } + + private String getResourceAsString(String path) throws IOException { + return IOUtils.toString(JsonPatchListenerTest.class.getClassLoader().getResourceAsStream(path), + Charsets.UTF_8.name()); + } + +} \ No newline at end of file diff --git a/examples/jsonunit-patch-listener/src/test/resources/allure.properties b/examples/jsonunit-patch-listener/src/test/resources/allure.properties new file mode 100644 index 000000000..cb77d0a3e --- /dev/null +++ b/examples/jsonunit-patch-listener/src/test/resources/allure.properties @@ -0,0 +1 @@ +allure.results.directory=build/allure-results \ No newline at end of file diff --git a/examples/jsonunit-patch-listener/src/test/resources/left.json b/examples/jsonunit-patch-listener/src/test/resources/left.json new file mode 100644 index 000000000..4b3f21a4f --- /dev/null +++ b/examples/jsonunit-patch-listener/src/test/resources/left.json @@ -0,0 +1,97 @@ +{ + "name": "South America", + "summary": "South America (Spanish: América del Sur, Sudamérica or \nSuramérica; Portuguese: América do Sul; Quechua and Aymara: \nUrin Awya Yala; Guarani: Ñembyamérika; Dutch: Zuid-Amerika; \nFrench: Amérique du Sud) is a continent situated in the \nWestern Hemisphere, mostly in the Southern Hemisphere, with \na relatively small portion in the Northern Hemisphere. \nThe continent is also considered a subcontinent of the \nAmericas.[2][3] It is bordered on the west by the Pacific \nOcean and on the north and east by the Atlantic Ocean; \nNorth America and the Caribbean Sea lie to the northwest. \nIt includes twelve countries: Argentina, Bolivia, Brasil, \nChile, Colombia, Ecuador, Guyana, Paraguay, Peru, Suriname, \nUruguay, and Venezuela. The South American nations that \nborder the Caribbean Sea—including Colombia, Venezuela, \nGuyana, Suriname, as well as French Guiana, which is an \noverseas region of France—are a.k.a. Caribbean South \nAmerica. South America has an area of 17,840,000 square \nkilometers (6,890,000 sq mi). Its population as of 2005 \nhas been estimated at more than 371,090,000. South America \nranks fourth in area (after Asia, Africa, and North America) \nand fifth in population (after Asia, Africa, Europe, and \nNorth America). The word America was coined in 1507 by \ncartographers Martin Waldseemüller and Matthias Ringmann, \nafter Amerigo Vespucci, who was the first European to \nsuggest that the lands newly discovered by Europeans were \nnot India, but a New World unknown to Europeans.", + "timezone": [ + -4, + -2 + ], + "demographics": { + "population": 385744896, + "largestCities": [ + "São Paulo", + "Buenos Aires", + "Rio de Janeiro", + "Lima", + "Bogotá" + ] + }, + "languages": [ + "spanish", + "portuguese", + "inglés", + "dutch", + "french", + "quechua", + "guaraní", + "aimara", + "mapudungun" + ], + "countries": [ + { + "name": "Argentina", + "capital": "Rawson", + "independence": "1816-07-08T20:00:00.000Z", + "unasur": true + }, + { + "name": "Bolivia", + "capital": "La Paz", + "independence": "1825-08-05T21:00:00.000Z", + "unasur": true + }, + { + "name": "Peru", + "capital": "Lima", + "independence": "1821-07-27T21:00:00.000Z", + "unasur": true + }, + { + "name": "Brazil", + "capital": "Brasilia", + "independence": "1822-09-06T21:00:00.000Z", + "unasur": true + }, + { + "name": "Chile", + "capital": "Santiago", + "independence": "1818-02-11T21:00:00.000Z", + "unasur": true + }, + { + "name": "Ecuador", + "capital": "Quito", + "independence": "1809-08-09T21:00:00.000Z", + "unasur": true + }, + { + "name": "Guyana", + "capital": "Georgetown", + "independence": "1966-05-25T21:00:00.000Z", + "unasur": true + }, + { + "name": "Paraguay", + "capital": "Asunción", + "independence": "1811-05-13T21:00:00.000Z", + "unasur": true + }, + { + "name": "Suriname", + "capital": "Paramaribo", + "independence": "1975-11-24T21:00:00.000Z", + "unasur": true + }, + { + "name": "Antártida", + "unasur": false + }, + { + "name": "Colombia", + "capital": "Bogotá", + "independence": "1810-07-19T21:00:00.000Z", + "unasur": true, + "population": 42888594 + } + ], + "spanishName": "Sudamérica" +} \ No newline at end of file diff --git a/examples/jsonunit-patch-listener/src/test/resources/right.json b/examples/jsonunit-patch-listener/src/test/resources/right.json new file mode 100644 index 000000000..df1a0b8ce --- /dev/null +++ b/examples/jsonunit-patch-listener/src/test/resources/right.json @@ -0,0 +1,104 @@ +{ + "name": "South America", + "summary": "South America (Spanish: América del Sur, Sudamérica or \nSuramérica; Portuguese: América do Sul; Quechua and Aymara: \nUrin Awya Yala; Guarani: Ñembyamérika; Dutch: Zuid-Amerika; \nFrench: Amérique du Sud) is a continent situated in the \nWestern Hemisphere, mostly in the Southern Hemisphere, with \na relatively small portion in the Northern Hemisphere. \nThe continent is also considered a subcontinent of the \nAmericas.[2][3] It is bordered on the west by the Pacific \nOcean and on the north and east by the Atlantic Ocean; \nNorth America and the Caribbean Sea lie to the northwest. \nIt includes twelve countries: Argentina, Bolivia, Brazil, \nChile, Colombia, Ecuador, Guyana, Paraguay, Peru, Suriname, \nUruguay, and Venezuela. The South American nations that \nborder the Caribbean Sea—including Colombia, Venezuela, \nGuyana, Suriname, as well as French Guiana, which is an \noverseas region of France—are also known as Caribbean South \nAmerica. South America has an area of 17,840,000 square \nkilometers (6,890,000 sq mi). Its population as of 2005 \nhas been estimated at more than 371,090,000. South America \nranks fourth in area (after Asia, Africa, and North America) \nand fifth in population (after Asia, Africa, Europe, and \nNorth America). The word America was coined in 1507 by \ncartographers Martin Waldseemüller and Matthias Ringmann, \nafter Amerigo Vespucci, who was the first European to \nsuggest that the lands newly discovered by Europeans were \nnot India, but a New World unknown to Europeans.", + "surface": 17840000, + "timezone": [ + -4, + -2 + ], + "demographics": { + "population": 385742554, + "largestCities": [ + "São Paulo", + "Buenos Aires", + "Rio de Janeiro", + "Lima", + "Bogotá" + ] + }, + "languages": [ + "spanish", + "portuguese", + "english", + "dutch", + "french", + "quechua", + "guaraní", + "aimara", + "mapudungun" + ], + "countries": [ + { + "name": "Argentina", + "capital": "Buenos Aires", + "independence": "1816-07-08T20:00:00.000Z", + "unasur": true + }, + { + "name": "Bolivia", + "capital": "La Paz", + "independence": "1825-08-05T21:00:00.000Z", + "unasur": true + }, + { + "name": "Brazil", + "capital": "Brasilia", + "independence": "1822-09-06T21:00:00.000Z", + "unasur": true + }, + { + "name": "Chile", + "capital": "Santiago", + "independence": "1818-02-11T21:00:00.000Z", + "unasur": true + }, + { + "name": "Colombia", + "capital": "Bogotá", + "independence": "1810-07-19T21:00:00.000Z", + "unasur": true + }, + { + "name": "Ecuador", + "capital": "Quito", + "independence": "1809-08-09T21:00:00.000Z", + "unasur": true + }, + { + "name": "Guyana", + "capital": "Georgetown", + "independence": "1966-05-25T21:00:00.000Z", + "unasur": true + }, + { + "name": "Paraguay", + "capital": "Asunción", + "independence": "1811-05-13T21:00:00.000Z", + "unasur": true + }, + { + "name": "Peru", + "capital": "Lima", + "independence": "1821-07-27T21:00:00.000Z", + "unasur": true + }, + { + "name": "Suriname", + "capital": "Paramaribo", + "independence": "1975-11-24T21:00:00.000Z", + "unasur": true + }, + { + "name": "Uruguay", + "capital": "Montevideo", + "independence": "1825-08-24T21:00:00.000Z", + "unasur": true + }, + { + "name": "Venezuela", + "capital": "Caracas", + "independence": "1811-07-04T21:00:00.000Z", + "unasur": true + } + ] +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index af663d6c3..29ce6d9a3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,6 +23,7 @@ include 'allure-spock' include 'allure-assertj' include 'allure-jbehave' include 'allure-selenide' +include 'allure-jsonunit' def examples = [ 'testng', @@ -32,7 +33,8 @@ def examples = [ 'http-client-attachment', 'rest-assured', 'okhttp3', - 'spock' + 'spock', + 'jsonunit-patch-listener' ] examples.forEach({ From d96ad8f899f970b6bb1ee43708a9c558d9f69a3f Mon Sep 17 00:00:00 2001 From: Victor Orlovsky Date: Sun, 12 Aug 2018 15:59:04 +0300 Subject: [PATCH 2/2] Use jackson instead of gson, include minified version directly, remove unnecessary css styles --- allure-jsonunit/build.gradle | 1 - .../allure/jsonunit/JsonPatchListener.java | 17 +- .../allure/jsonunit/JsonPatchMatcher.java | 20 ++- .../src/main/resources/tpl/diff.ftl | 150 +----------------- .../jsonunit/JsonPatchListenerTest.java | 11 +- build.gradle | 1 - 6 files changed, 37 insertions(+), 163 deletions(-) diff --git a/allure-jsonunit/build.gradle b/allure-jsonunit/build.gradle index 91f80ede8..383fd829f 100644 --- a/allure-jsonunit/build.gradle +++ b/allure-jsonunit/build.gradle @@ -7,7 +7,6 @@ apply plugin: 'maven' dependencies { compile project(':allure-attachments') compile('net.javacrumbs.json-unit:json-unit:2.0.0.RC1') - compile('com.google.code.gson:gson') testCompile('junit:junit') testCompile('org.hamcrest:hamcrest-library') diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java index 6575a239d..77dd38dfa 100644 --- a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchListener.java @@ -1,6 +1,7 @@ package io.qameta.allure.jsonunit; -import com.google.gson.GsonBuilder; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import net.javacrumbs.jsonunit.core.listener.Difference; import net.javacrumbs.jsonunit.core.listener.DifferenceContext; import net.javacrumbs.jsonunit.core.listener.DifferenceListener; @@ -87,7 +88,12 @@ public String getJsonPatch() { final Difference difference = getDifferences().get(0); final String field = getPath(difference); if (field.isEmpty()) { - return new GsonBuilder().create().toJson(getPatch(difference)); + final ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.writeValueAsString(getPatch(difference)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Could not process patch json", e); + } } } getDifferences().forEach(difference -> { @@ -128,6 +134,11 @@ public String getJsonPatch() { } }); - return new GsonBuilder().create().toJson(jsonDiffPatch); + final ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.writeValueAsString(jsonDiffPatch); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Could not process patch json", e); + } } } diff --git a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java index 775e24d7e..122b5504b 100644 --- a/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java +++ b/allure-jsonunit/src/main/java/io/qameta/allure/jsonunit/JsonPatchMatcher.java @@ -1,7 +1,7 @@ package io.qameta.allure.jsonunit; -import com.google.gson.GsonBuilder; -import io.qameta.allure.Step; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import io.qameta.allure.attachment.DefaultAttachmentProcessor; import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; import net.javacrumbs.jsonunit.core.Configuration; @@ -29,7 +29,6 @@ private JsonPatchMatcher(final Object expected) { this.expected = expected; } - @Step("Has no difference") public static AllureConfigurableJsonMatcher jsonEquals(final Object expected) { return new JsonPatchMatcher(expected); } @@ -65,12 +64,17 @@ public AllureConfigurableJsonMatcher whenIgnoringPaths(final String... paths) } private void render(final JsonPatchListener listener) { - final Object actual = new GsonBuilder().create().toJson(listener.getContext().getActualSource()); - final Object expected = new GsonBuilder().create().toJson(listener.getContext().getExpectedSource()); + final ObjectMapper mapper = new ObjectMapper(); final String patch = listener.getJsonPatch(); - final DiffAttachment attachment = new DiffAttachment(actual.toString(), expected.toString(), patch); - new DefaultAttachmentProcessor().addAttachment(attachment, - new FreemarkerAttachmentRenderer("diff.ftl")); + try { + final String actual = mapper.writeValueAsString(listener.getContext().getActualSource()); + final String expected = mapper.writeValueAsString(listener.getContext().getExpectedSource()); + final DiffAttachment attachment = new DiffAttachment(actual, expected, patch); + new DefaultAttachmentProcessor().addAttachment(attachment, + new FreemarkerAttachmentRenderer("diff.ftl")); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Could not process actual/expected json", e); + } } @Override diff --git a/allure-jsonunit/src/main/resources/tpl/diff.ftl b/allure-jsonunit/src/main/resources/tpl/diff.ftl index 64adfb513..478c29f5f 100644 --- a/allure-jsonunit/src/main/resources/tpl/diff.ftl +++ b/allure-jsonunit/src/main/resources/tpl/diff.ftl @@ -1,153 +1,13 @@ - +<#--https://cdn.jsdelivr.net/npm/jsondiffpatch/dist/jsondiffpatch.umd.min.js--> + +<#--https://github.com/benjamine/jsondiffpatch/blob/master/docs/formatters-styles/html.css-->