001package io.ebean.test;
002
003import io.ebean.DB;
004
005import static org.assertj.core.api.Assertions.assertThat;
006
007/**
008 * Helper for testing to assert that the JSON form of an entity
009 * or list of entities is as expected.
010 * <p>
011 * Using assertContains() we can match the JSON form of an entity
012 * against a subset of JSON content. Typically the subset of JSON
013 * excludes generated properties like id, when created and
014 * when modified properties.
015 * </p>
016 *
017 * <h3>Assert contains</h3>
018 * <pre>{@code
019 *
020 *    DbJson.of(order)
021 *      .assertContains("/order-partial.json");
022 *
023 * }</pre>
024 *
025 *
026 * <h3>Assert content matches</h3>
027 * <p>
028 * Using assertContentMatches() we are doing an exact content match.
029 * We typically need to replace generated property values. This assert
030 * will start to fail if the model changes like adding a property to
031 * the entity so we should use it less widely due to the maintenance
032 * burden we have with it.
033 * </p>
034 *
035 * <pre>{@code
036 *
037 *    DbJson.of(order)
038 *      .replace("id", "whenCreated", "whenModified)
039 *      .assertContentMatches("/order-full.json");
040 *
041 * }</pre>
042 */
043public class DbJson {
044
045  /**
046   * Create a PrettyJson object that has the JSON form of the entity bean or beans.
047   *
048   * <h3>Assert contains</h3>
049   * <pre>{@code
050   *
051   *    DbJson.of(order)
052   *      .assertContains("/order-partial.json");
053   *
054   * }</pre>
055   *
056   * <h3>Assert content matches</h3>
057   * <pre>{@code
058   *
059   *    DbJson.of(order)
060   *      .replace("id", "whenCreated", "whenModified)
061   *      .assertContentMatches("/order-full.json");
062   *
063   * }</pre>
064   */
065  public static PrettyJson of(Object bean) {
066    return new PrettyJson(DB.json().toJsonPretty(bean));
067  }
068
069  /**
070   * Read the content for the given resource path.
071   */
072  public static String readResource(String resourcePath) {
073    return IOUtils.readResource(resourcePath);
074  }
075
076  /**
077   * Contains the JSON of beans(s).
078   */
079  public static class PrettyJson {
080
081    private String placeHolder = "\"*\"";
082    private String rawJson;
083
084    PrettyJson(String rawJson) {
085      this.rawJson = rawJson;
086    }
087
088    /**
089     * Set the placeHolder to use when replacing property values.
090     */
091    public PrettyJson withPlaceholder(String placeHolder) {
092      this.placeHolder = placeHolder;
093      return this;
094    }
095
096    /**
097     * Replace the values of the given properties with a placeholder value.
098     * <p>
099     * Typically we do this on generated properties such as id and timestamp properties.
100     * </p>
101     */
102    public PrettyJson replace(String... propertyNames) {
103      for (String propertyName : propertyNames) {
104        String placeholder = "\"" + propertyName + "\": " + placeHolder;
105        rawJson = rawJson.replaceAll("\"" + propertyName + "\": (\\d+)", placeholder);
106        rawJson = rawJson.replaceAll("\"" + propertyName + "\": \"(.*?)\"", placeholder);
107      }
108      return this;
109    }
110
111    /**
112     * Return the JSON content.
113     */
114    public String asJson() {
115      return rawJson;
116    }
117
118    /**
119     * Assert the json exactly matches the content at the given resource path.
120     *
121     * <pre>{@code
122     *
123     *    DbJson.of(timedEntries)
124     *      .replace("id", "eventTime")
125     *      .assertContentMatches("/assertJson/full-1-timed.json");
126     *
127     * }</pre>
128     */
129    public void assertContentMatches(String resourcePath) {
130      assertThat(lineEnd(rawJson)).isEqualTo(lineEnd(readResource(resourcePath)));
131    }
132
133    /**
134     * Normalise line ending characters to just use new line.
135     */
136    private String lineEnd(String content) {
137      return content.replace("\r\n", "\n");
138    }
139
140    /**
141     * Assert the DB json contains the given json content.
142     * <p>
143     * With this "contains" check the DB Json can contain more content than what
144     * it is checked against. Typically the DB json can contain generated properties
145     * like id values, when created, when modified etc and we leave these out of the
146     * json content we are checking against.
147     * </p>
148     *
149     * @param json The subset json content that should be contained by the DB json.
150     */
151    public void assertContains(String json) {
152      Json.assertContains(rawJson, json);
153    }
154
155    /**
156     * Assert the DB Json contains the Json at the given resource path.
157     * <p>
158     * With this "contains" check the DB Json can contain more content than what
159     * it is checked against. Typically the DB json can contain generated properties
160     * like id values, when created, when modified etc and we leave these out of the
161     * json content we are checking against.
162     * </p>
163     *
164     * @param resourcePath The resource path of the JSON content we are checking against.
165     */
166    public void assertContainsResource(String resourcePath) {
167      assertContains(readResource(resourcePath));
168    }
169  }
170}