Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-jsonunit</artifactId>
<version>$LATEST_VERSION</version>
</dependency>
```
13 changes: 13 additions & 0 deletions allure-jsonunit/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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')

testCompile('junit:junit')
testCompile('org.hamcrest:hamcrest-library')
}
Original file line number Diff line number Diff line change
@@ -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 <T> the type of matcher
* @see net.javacrumbs.jsonunit.ConfigurableJsonMatcher
*/
public interface AllureConfigurableJsonMatcher<T> extends Matcher<T> {

AllureConfigurableJsonMatcher<T> withTolerance(BigDecimal tolerance);

AllureConfigurableJsonMatcher<T> withTolerance(double tolerance);

AllureConfigurableJsonMatcher<T> when(Option first, Option... next);

AllureConfigurableJsonMatcher<T> withOptions(Options options);

AllureConfigurableJsonMatcher<T> withMatcher(String matcherName, Matcher<?> matcher);

AllureConfigurableJsonMatcher<T> whenIgnoringPaths(String... paths);
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package io.qameta.allure.jsonunit;

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;
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<Difference> differences = new ArrayList<>();

private DifferenceContext context;

@Override
public void diff(final Difference difference, final DifferenceContext differenceContext) {
this.context = differenceContext;
differences.add(difference);
}

public List<Difference> 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<Object> getPatch(final Difference difference) {
final List<Object> 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<String, Object> 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()) {
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 -> {

final String field = getPath(difference);
Map<String, Object> 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<String, Object>) 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;
}
});

final ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(jsonDiffPatch);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not process patch json", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.qameta.allure.jsonunit;

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;
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 <T> the type
*/
public final class JsonPatchMatcher<T> implements AllureConfigurableJsonMatcher<T> {
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;
}

public static <T> AllureConfigurableJsonMatcher<T> jsonEquals(final Object expected) {
return new JsonPatchMatcher<T>(expected);
}

public AllureConfigurableJsonMatcher<T> withTolerance(final BigDecimal tolerance) {
configuration = configuration.withTolerance(tolerance);
return this;
}

public AllureConfigurableJsonMatcher<T> withTolerance(final double tolerance) {
configuration = configuration.withTolerance(tolerance);
return this;
}

public AllureConfigurableJsonMatcher<T> when(final Option first, final Option... next) {
configuration = configuration.when(first, next);
return this;
}

public AllureConfigurableJsonMatcher<T> withOptions(final Options options) {
configuration = configuration.withOptions(options);
return this;
}

public AllureConfigurableJsonMatcher<T> withMatcher(final String matcherName, final Matcher matcher) {
configuration = configuration.withMatcher(matcherName, matcher);
return this;
}

public AllureConfigurableJsonMatcher<T> whenIgnoringPaths(final String... paths) {
configuration = configuration.whenIgnoringPaths(paths);
return this;
}

private void render(final JsonPatchListener listener) {
final ObjectMapper mapper = new ObjectMapper();
final String patch = listener.getJsonPatch();
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
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
}
}
171 changes: 171 additions & 0 deletions allure-jsonunit/src/main/resources/tpl/diff.ftl

Large diffs are not rendered by default.

Loading