001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.client.assertion;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022
023import org.apache.juneau.commons.utils.*;
024import org.apache.juneau.rest.client.*;
025
026/**
027 * A fluent assertion class for HTTP response bodies.
028 *
029 * <p>
030 * Obtain instances via {@link ResponseAssertion#body()}.
031 *
032 * <h5 class='section'>See Also:</h5><ul>
033 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
034 * </ul>
035 *
036 * @since 9.2.1
037 */
038@SuppressWarnings({
039   "resource" // Eclipse resource analysis: response is borrowed; caller closes it
040})
041public final class ResponseBodyAssertion {
042
043   private final RestResponse response;
044   private final ResponseAssertion parent;
045
046   ResponseBodyAssertion(RestResponse response, ResponseAssertion parent) {
047      this.response = response;
048      this.parent = parent;
049   }
050
051   /**
052    * Asserts that the response body (as a UTF-8 string) equals the expected value.
053    *
054    * @param expected The expected body string. May be <jk>null</jk> to assert an empty/absent body.
055    * @return This object (for chaining).
056    * @throws AssertionError If the body does not match.
057    * @throws UncheckedIOException If reading the body throws {@link IOException}.
058    */
059   @SuppressWarnings({
060      "java:S1201" // 'equals(String)' is an intentional fluent-assertion DSL method (asserts body equality), not an override of Object.equals(Object); renaming would break this public API.
061   })
062   public ResponseBodyAssertion equals(String expected) {
063      var actual = readBodyAsString();
064      if (expected == null ? actual != null : neq(expected, actual))
065         throw new AssertionError("Expected body '" + expected + "' but got '" + actual + "'");
066      return this;
067   }
068
069   /**
070    * Asserts that the response body (as a UTF-8 string) contains the given substring.
071    *
072    * @param substring The substring to look for. Must not be <jk>null</jk>.
073    * @return This object (for chaining).
074    * @throws AssertionError If the body is absent or does not contain the substring.
075    * @throws UncheckedIOException If reading the body throws {@link IOException}.
076    */
077   public ResponseBodyAssertion contains(String substring) {
078      var actual = readBodyAsString();
079      if (actual == null || !actual.contains(substring))
080         throw new AssertionError("Body '" + actual + "' does not contain '" + substring + "'");
081      return this;
082   }
083
084   /**
085    * Asserts that the response body is empty (<jk>null</jk> or zero-length).
086    *
087    * @return This object (for chaining).
088    * @throws AssertionError If the body is non-empty.
089    * @throws UncheckedIOException If reading the body throws {@link IOException}.
090    */
091   public ResponseBodyAssertion isEmpty() {
092      var actual = readBodyAsString();
093      if (StringUtils.isNotEmpty(actual))
094         throw new AssertionError("Expected empty body but got '" + actual + "'");
095      return this;
096   }
097
098   /**
099    * Asserts that the response body is present and non-empty.
100    *
101    * @return This object (for chaining).
102    * @throws AssertionError If the body is <jk>null</jk> or empty.
103    * @throws UncheckedIOException If reading the body throws {@link IOException}.
104    */
105   public ResponseBodyAssertion isNotEmpty() {
106      var actual = readBodyAsString();
107      if (StringUtils.isEmpty(actual))
108         throw new AssertionError("Expected non-empty body but body was absent or empty");
109      return this;
110   }
111
112   /**
113    * Asserts that the response body starts with the given prefix.
114    *
115    * @param prefix The expected prefix. Must not be <jk>null</jk>.
116    * @return This object (for chaining).
117    * @throws AssertionError If the body is absent or does not start with the prefix.
118    * @throws UncheckedIOException If reading the body throws {@link IOException}.
119    */
120   public ResponseBodyAssertion startsWith(String prefix) {
121      var actual = readBodyAsString();
122      if (actual == null || !actual.startsWith(prefix))
123         throw new AssertionError("Body '" + actual + "' does not start with '" + prefix + "'");
124      return this;
125   }
126
127   /**
128    * Returns the parent {@link ResponseAssertion} to continue assertion chaining.
129    *
130    * @return The parent assertion. Never <jk>null</jk>.
131    */
132   public ResponseAssertion and() {
133      return parent;
134   }
135
136   private String readBodyAsString() {
137      try {
138         return response.getBodyAsString();
139      } catch (IOException e) {
140         throw new UncheckedIOException(e);
141      }
142   }
143}