All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.groupbyinc.common.test.util.AssertUtils Maven / Gradle / Ivy

There is a newer version: 198
Show newest version
package com.groupbyinc.common.test.util;

import com.fasterxml.jackson.Mappers;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.thisptr.jackson.jq.JsonQuery;
import net.thisptr.jackson.jq.Scope;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.skyscreamer.jsonassert.JSONCompare;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult;
import org.skyscreamer.jsonassert.comparator.DefaultComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class AssertUtils {

  private static final transient Logger LOG = LoggerFactory.getLogger(AssertUtils.class);

  private static final int JSON_SPACING = 4;

  public static class LenientDoublesJsonComparator extends DefaultComparator {

    private static final double MARGIN = 0.1;

    private LenientDoublesJsonComparator() {
      super(JSONCompareMode.STRICT);
    }

    private double parseMantissa(Number value) {
      return Double.parseDouble(value.toString().split("E", 2)[0]);
    }

    private int parseExponent(Number value) {
      String[] split = value.toString().split("E", 2);
      return split.length < 2 ? 1 : Integer.parseInt(split[1]);
    }

    @Override
    public void compareValues(String prefix, Object expectedValue, Object actualValue, JSONCompareResult result) throws JSONException {
      if (expectedValue instanceof Number && actualValue instanceof Number) {
        Number expectedNumber = ((Number) expectedValue);
        Number actualNumber = ((Number) actualValue);

        if (parseExponent(expectedNumber) != parseExponent(actualNumber) || Math.abs(parseMantissa(expectedNumber) - parseMantissa(actualNumber)) > MARGIN) {
          result.fail(prefix, expectedValue, actualValue);
        }
      } else {
        super.compareValues(prefix, expectedValue, actualValue, result);
      }
    }
  }

  private AssertUtils() {
    // not publicly instantiable
  }

  public static > void assertListEqualsIgnoreOrder(List expected, List actual) {
    Collections.sort(expected);
    Collections.sort(actual);
    assertEquals(expected, actual);
  }

  public static void assertJsonLinesEquals(List expectedLines, List actualLines) {
    if (expectedLines.size() != actualLines.size()) {
      fail("Expected line count: " + expectedLines.size() + " Actual line count: " + actualLines.size());
    }
    Collections.sort(expectedLines);
    Collections.sort(actualLines);

    for (int i = 0; i < expectedLines.size(); i++) {
      assertJsonEquals(expectedLines.get(i), actualLines.get(i));
    }
  }

  public static void assertJsonEquals(String expectedJson, Object actual) {
    assertJsonEquals(".", expectedJson, actual);
  }

  public static void assertJsonEquals(String key, String expectedJson, Object actual) {
    assertJsonEqualsWithMsg(null, key, expectedJson, actual);
  }

  public static void assertJsonEqualsWithMsg(String msg, String key, String expectedJson, Object actual) {
    String actualJson = actual instanceof String ? (String) actual : Mappers.writeValueAsString(actual);
    try {
      if (StringUtils.isNotBlank(expectedJson) && StringUtils.isBlank(actualJson)) {
        fail("no actual json received to compare to");
      } else if (StringUtils.isBlank(expectedJson) && StringUtils.isNotBlank(actualJson)) {
        fail("no expected json given to compare to");
      }

      boolean isArrayCheck = StringUtils.startsWith(expectedJson, "[");
      boolean isValueCheck = !StringUtils.startsWith(expectedJson, "{");
      JsonQuery jq = JsonQuery.compile(key + (isArrayCheck ? "[]" : ""));
      ObjectMapper mapper = new ObjectMapper();
      JsonNode actualJsonNode = mapper.readTree(actualJson);
      List partials = jq.apply(Scope.newEmptyScope(), actualJsonNode).stream().map(p -> {
        try {
          return mapper.treeToValue(p, Object.class);
        } catch (JsonProcessingException e) {
          fail("unable to parse json" + e.getMessage());
          return null;
        }
      }).collect(Collectors.toList());

      if (CollectionUtils.isEmpty(partials)) {
        fail("provided jq query does not match any partial json");
      } else {
        if (isArrayCheck) {
          assertJsonArrayEquals(msg, expectedJson, mapper.writeValueAsString(partials));
        } else if (isValueCheck) {
          assertEquals(msg, expectedJson.replaceAll("\"", "'"), mapper.writeValueAsString(partials.get(0)).replaceAll("\"", "'"));
        } else {
          Map value = Mappers.getStrictReader(false).with(JsonParser.Feature.ALLOW_TRAILING_COMMA).forType(Map.class).readValue(expectedJson.getBytes());
          JSONObject expected = new JSONObject(value);
          JSONObject actualJsonObject = new JSONObject(mapper.writeValueAsString(partials.get(0)));
          JSONCompareResult result = JSONCompare.compareJSON(expected, actualJsonObject, new LenientDoublesJsonComparator());
          verifyJson(result, expected.toString(JSON_SPACING), actualJsonObject.toString(JSON_SPACING));
        }
      }
    } catch (Exception e) {
      LOG.warn("expected: {}\n  actual: {}\n{}", expectedJson, actualJson, e.getMessage());
      String errorMsg = "invalid expected json";
      if (StringUtils.isNotBlank(msg)) {
        errorMsg = ": " + msg;
      }
      fail(errorMsg + "\n expected: " + expectedJson);
    }
  }

  private static void assertJsonArrayEquals(String msg, String expectedJson, Object actual) {
    String actualJson = actual instanceof String ? (String) actual : Mappers.writeValueAsString(actual);
    try {
      List value = Mappers.getStrictReader(false).with(JsonParser.Feature.ALLOW_TRAILING_COMMA).forType(List.class).readValue(expectedJson.getBytes());
      JSONArray expected = new JSONArray(value);
      JSONArray actualArray = new JSONArray(actualJson);
      JSONCompareResult result = JSONCompare.compareJSON(expected, actualArray, new LenientDoublesJsonComparator());
      verifyJson(result, expected.toString(JSON_SPACING), actualArray.toString(JSON_SPACING));
    } catch (Exception e) {
      LOG.warn("expected: {}\n  actual: {}\n{}", expectedJson, actualJson, e.getMessage());
      String errorMsg = "invalid expected json";
      if (StringUtils.isNotBlank(msg)) {
        errorMsg = ": " + msg;
      }
      fail(errorMsg + "\n expected: " + expectedJson);
    }
  }

  private static void verifyJson(JSONCompareResult result, String expectedFormatted, String actualFormatted) {
    if (!result.passed()) {
      assertEquals(String.format("expected: %s\n\nactual: %s\n\nmsg: %s\n", expectedFormatted, actualFormatted, result.getMessage()), expectedFormatted, actualFormatted);
      assertTrue(result.passed());
    }
  }

  public static void assertJsonEqualsWithMsg(String msg, String expectedJson, Object actual) {
    assertJsonEqualsWithMsg(msg, ".", expectedJson, actual);
  }
}