Package restx.tests.json

Source Code of restx.tests.json.JsonAssertions

package restx.tests.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
import restx.common.Mustaches;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static restx.common.MoreStrings.indent;

/**
* Date: 4/2/14
* Time: 21:34
*/
public class JsonAssertions {

    public static JsonAssertions assertThat(String json) {
        return new JsonAssertions(new StringJsonSource("actual", json));
    }
    public static JsonAssertions assertThat(File json, Charset cs) {
        return new JsonAssertions(new FileJsonSource(json, cs));
    }
    public static JsonAssertions assertThat(URL json, Charset cs) {
        return new JsonAssertions(new URLJsonSource(json, cs));
    }
    public static JsonAssertions assertThat(JsonSource json) {
        return new JsonAssertions(json);
    }

    private final JsonDiffer differ;
    private final JsonSource actual;
    private final ObjectMapper objectMapper = new ObjectMapper();

    private boolean allowingExtraUnexpectedFields;
    private int contentLengthHtmlReportThreshold = 600;

    private JsonAssertions(JsonSource actual) {
        this.actual = actual;
        differ = new JsonDiffer();
    }

    public JsonAssertions allowingExtraUnexpectedFields() {
        allowingExtraUnexpectedFields = true;
        differ.getLeftConfig().setIgnoreExtraFields(true);
        return this;
    }

    public JsonAssertions withJsonDiffComparator(JsonDiffComparator jsonDiffComparator) {
        differ.setJsonDiffComparator(jsonDiffComparator);
        return this;
    }

    public JsonAssertions withContentLengthHtmlReportThreshold(final int contentLengthHtmlReportThreshold) {
        this.contentLengthHtmlReportThreshold = contentLengthHtmlReportThreshold;
        return this;
    }


    public JsonAssertions isSameJsonAs(String expected) {
        return isSameJsonAs(new StringJsonSource("expected", expected));
    }
    public JsonAssertions isSameJsonAs(File expected, Charset cs) {
        return isSameJsonAs(new FileJsonSource(expected, cs));
    }
    public JsonAssertions isSameJsonAs(URL expected, Charset cs) {
        return isSameJsonAs(new URLJsonSource(expected, cs));
    }
    public JsonAssertions isSameJsonAs(JsonSource expected) {
        JsonDiff diff = differ.compare(actual, expected);

        if (!diff.isSame()) {
            if (expected.content().length() < contentLengthHtmlReportThreshold
                    && actual.content().length() < contentLengthHtmlReportThreshold) {
                throw new AssertionError(fullTextReport(expected, diff).toString());
            } else {
                StringBuilder sb = new StringBuilder();

                sb.append("Expecting:\n")
                        .append(indent(limit(actual.content(), contentLengthHtmlReportThreshold), 2)).append("\n")
                        .append("\nto be same json as:\n")
                        .append(indent(limit(expected.content(), contentLengthHtmlReportThreshold), 2)).append("\n")
                        .append("\nbut following differences were found:\n\n");

                int i = 1;
                for (JsonDiff.Difference difference : diff.getDifferences()) {
                    appendDifferenceInfo(i, sb, difference);
                    i++;
                }

                try {
                    File htmlReport = File.createTempFile("json-diff", ".html");

                    List<Object> differences = new ArrayList<>();
                    ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
                    i = 1;
                    for (JsonDiff.Difference difference : diff.getDifferences()) {
                        StringBuilder diffSb = new StringBuilder();
                        appendDifferenceInfo(i, diffSb, difference);

                        String actualKeyOfInterest;
                        String expectedKeyOfInterest;
                        String actualContextPath;
                        String expectedContextPath;

                        // for added and removed keys, the interesting context is at captured path.
                        // for others it's more interesting to get up one level
                        if (difference instanceof JsonDiff.AddedKey) {
                            actualKeyOfInterest = "";
                            actualContextPath = difference.getLeftPath();
                            expectedKeyOfInterest = ((JsonDiff.AddedKey) difference).getKey();
                            expectedContextPath = difference.getRightPath();
                        } else if (difference instanceof JsonDiff.RemovedKey) {
                            actualKeyOfInterest = ((JsonDiff.RemovedKey) difference).getKey();
                            actualContextPath = difference.getLeftPath();
                            expectedKeyOfInterest = "";
                            expectedContextPath = difference.getRightPath();
                        } else {
                            actualKeyOfInterest = diff.getLastElementPath(difference.getLeftPath());
                            expectedKeyOfInterest = diff.getLastElementPath(difference.getRightPath());
                            actualContextPath = diff.getParentPath(difference.getLeftPath());
                            expectedContextPath = diff.getParentPath(difference.getRightPath());
                        }

                        differences.add(
                                ImmutableMap.builder()
                                        .put("number", String.valueOf(i))
                                        .put("difference", diffSb.toString().replace("\"", "\\\"").replace("\n", "\\n"))
                                        .put("actual-path", difference.getLeftPath())
                                        .put("actual-keyOfInterest", actualKeyOfInterest)
                                        .put("actual-context", objectWriter.writeValueAsString(
                                            toContext(diff.getLeftAt(actualContextPath),
                                                        actualKeyOfInterest))
                                                    .replace("\"", "\\\"").replace("\n", "\\n"))
                                        .put("expected-path", difference.getRightPath())
                                        .put("expected-keyOfInterest", expectedKeyOfInterest)
                                        .put("expected-context", objectWriter.writeValueAsString(
                                            toContext(diff.getRightAt(expectedContextPath),
                                                    expectedKeyOfInterest))
                                                    .replace("\"", "\\\"").replace("\n", "\\n"))
                                .build()
                                );
                         i++;
                    }

                    String r = Mustaches.execute(Mustaches.compile(JsonAssertions.class, "json-diff.html"),
                            ImmutableMap.of(
                                    "actual", actual.content(),
                                    "expected", expected.content(),
                                    "diff", diff,
                                    "differences", differences,
                                    "fix-expected", new JsonMerger().mergeToRight(diff)
                            ));

                    Files.write(r, htmlReport, Charsets.UTF_8);

                    sb.append("\n\nA detailed HTML REPORT has been generated in ")
                            .append(htmlReport.toURI().toURL()).append("\n");
                } catch (IOException e) {
                    sb.append("\n\nERROR occured when generating html report: " + e + "\n\n");
                }

                throw new AssertionError(sb.toString());
            }
        }
        return this;
    }

    @SuppressWarnings("unchecked")
    private Object toContext(Object o, String keyOfInterest) {
        if (o instanceof Map) {
            Map<String, Object> map = (Map) o;
            Map<String, Object> context = new LinkedHashMap<>();

            for (String key : map.keySet()) {
                Object v = map.get(key);
                if (key.equals(keyOfInterest)) {
                    context.put(key, v);
                } else {
                    if (v instanceof Map) {
                        context.put(key, "/object with " + ((Map) v).size() + " entries/");
                    } else if (v instanceof List) {
                        context.put(key, "/array with " + ((List) v).size() + " entries/");
                    } else {
                        context.put(key, v);
                    }
                }
            }
            return context;
        }

        return o;
    }

    private String limit(String s, int threshold) {
        if (s.length() <= threshold) {
            return s;
        }
        return s.substring(0, threshold) + "[...]\n[" + (s.length() - threshold) + " chars stripped]";
    }

    protected StringBuilder fullTextReport(JsonSource expected, JsonDiff diff) {
        StringBuilder sb = new StringBuilder();

        sb.append("Expecting:\n")
                .append(indent(actual.content(), 2)).append("\n")
                .append("to be same json as:\n")
                .append(indent(expected.content(), 2)).append("\n")
                .append("but following differences were found:\n");

        Multimap<JsonObjectLocation, JsonDiff.Difference> differencesPerLocation = LinkedListMultimap.create();
        for (JsonDiff.Difference difference : diff.getDifferences()) {
            differencesPerLocation.put(difference.getLeftContext(), difference);
        }

        int i = 1;
        for (JsonObjectLocation context : differencesPerLocation.keySet()) {
            sb.append(String.format("- within [L%dC%d]-[L%dC%d]:\n",
                    context.getFrom().getLineNr(), context.getFrom().getColumnNr(),
                    context.getTo().getLineNr(), context.getTo().getColumnNr()))
                    .append(indent(context.getJson(), 2))
                    .append("\n");
            for (JsonDiff.Difference difference : differencesPerLocation.get(context)) {
                appendDifferenceInfo(i, sb, difference);
                i++;
            }
        }

        // merging can be done with simple copy paste when not allowing extra fields,
        // but with extra fields allowed copying the actual content when it's the expectation which is not
        // up to date leads to adding previously ignored fields to the expectation.
        // therefore we dump a merged expect in this case to ease test maintenance.
        if (allowingExtraUnexpectedFields) {
            sb.append("\n\nif the expectation is not up to date, here is a merged" +
                    " expect that you can use to fix your test:\n");
            sb.append(indent(new JsonMerger().mergeToRight(diff), 2)).append("\n\n");
        }
        return sb;
    }

    private void appendDifferenceInfo(int i, StringBuilder sb, JsonDiff.Difference difference) {
        if (difference instanceof JsonDiff.AddedKey) {
            JsonDiff.AddedKey addedKey = (JsonDiff.AddedKey) difference;
            sb.append(String.format("%02d) ", i))
                    .append("missing key '").append(addedKey.getKey()).append("'")
                    .append(" at path '").append(addedKey.getLeftPath()).append("'")
                    .append(" expected value:\n")
                    .append(indent(asJson(addedKey.getValue()), 6))
                    .append("\n");;
        }
        if (difference instanceof JsonDiff.RemovedKey) {
            JsonDiff.RemovedKey removedKey = (JsonDiff.RemovedKey) difference;

            sb.append(String.format("%02d) ", i))
                    .append("extra key '").append(removedKey.getKey()).append("'")
                    .append(" at path '").append(removedKey.getLeftPath()).append("'")
                    .append(" with value:\n")
                    .append(indent(asJson(removedKey.getValue()), 6))
                    .append("\n");;
        }
        if (difference instanceof JsonDiff.ValueDiff) {
            JsonDiff.ValueDiff valueDiff = (JsonDiff.ValueDiff) difference;

            sb.append(String.format("%02d) ", i))
                    .append("expected value ").append(asJson(valueDiff.getRightValue()))
                    .append(" but was ").append(asJson(valueDiff.getLeftValue()))
                    .append(" at path '").append(valueDiff.getLeftPath()).append("'\n");
        }
        if (difference instanceof JsonDiff.ArrayInsertedValue) {
            JsonDiff.ArrayInsertedValue arrayInsertedValue = (JsonDiff.ArrayInsertedValue) difference;

            sb.append(String.format("%02d) ", i))
                    .append("missing element(s) in array at position ").append(arrayInsertedValue.getLeftPosition())
                    .append(" at path '").append(arrayInsertedValue.getLeftPath()).append("'")
                    .append(" expected value(s):\n")
                    .append(indent(asJson(arrayInsertedValue.getValues()), 6))
                    .append("\n");
        }
        if (difference instanceof JsonDiff.ArrayDeletedValue) {
            JsonDiff.ArrayDeletedValue arrayDeletedValue = (JsonDiff.ArrayDeletedValue) difference;

            sb.append(String.format("%02d) ", i))
                    .append("extra element(s) in array at position ").append(arrayDeletedValue.getLeftPosition())
                    .append(" at path '").append(arrayDeletedValue.getLeftPath()).append("'")
                    .append(" with value(s):\n")
                    .append(indent(asJson(arrayDeletedValue.getValues()), 6))
                    .append("\n");
        }
    }

    private String asJson(Object o) {
        if (o == null) {
            return "null";
        }

        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(o);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return o.toString();
        }
    }
}
TOP

Related Classes of restx.tests.json.JsonAssertions

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.