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.classic.assertion;
018
019import java.io.*;
020import java.lang.reflect.*;
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.function.*;
025import org.apache.juneau.http.classic.response.*;
026import org.apache.juneau.rest.client.classic.*;
027import org.apache.juneau.bct.assertions.*;
028
029/**
030 * Used for fluent assertion calls against {@link ResponseContent} objects.
031 *
032 * <h5 class='topic'>Test Methods</h5>
033 * <p>
034 * <ul class='javatree'>
035 *    <li class='jc'>{@link FluentResponseBodyAssertion}
036 *    <ul class='javatreec'>
037 *       <li class='jm'>{@link FluentResponseBodyAssertion#is(String) is(String)}
038 *       <li class='jm'>{@link FluentResponseBodyAssertion#isContains(String...) isContains(String...)}
039 *       <li class='jm'>{@link FluentResponseBodyAssertion#isNotContains(String...) isNotContains(String...)}
040 *       <li class='jm'>{@link FluentResponseBodyAssertion#isEmpty() isEmpty()}
041 *       <li class='jm'>{@link FluentResponseBodyAssertion#isNotEmpty() isNotEmpty()}
042 *    </ul>
043 *    <li class='jc'>{@link FluentObjectAssertion}
044 *    <ul class='javatreec'>
045 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
046 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
047 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
048 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
049 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
052 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
053 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
054 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
057 *    </ul>
058 * </ul>
059 *
060 * <h5 class='topic'>Transform Methods</h5>
061 * <p>
062 * <ul class='javatree'>
063 *    <li class='jc'>{@link FluentResponseBodyAssertion}
064 *    <ul class='javatreec'>
065 *       <li class='jm'>{@link FluentResponseBodyAssertion#asBytes() asBytes()}
066 *       <li class='jm'>{@link FluentResponseBodyAssertion#as(Class) as(Class)}
067 *       <li class='jm'>{@link FluentResponseBodyAssertion#as(Type,Type...) as(Type,Type...)}
068 *    </ul>
069 *    <li class='jc'>{@link FluentObjectAssertion}
070 *    <ul class='javatreec'>
071 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
072 *       <li class='jm'>{@link FluentObjectAssertion#asString(ThrowingFunction) asString(ThrowingFunction)}
073 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(UnaryOperator) asTransformed(UnaryOperator)}
074 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
075 * </ul>
076 * </ul>
077 *
078 * <h5 class='topic'>Configuration Methods</h5>
079 * <p>
080 * <ul class='javatree'>
081 *    <li class='jc'>{@link Assertion}
082 *    <ul class='javatreec'>
083 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
084 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
085 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
086 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
087 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
088 *    </ul>
089 * </ul>
090 *
091 * <h5 class='section'>See Also:</h5><ul>
092 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a>
093 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a>
094 * </ul>
095 *
096 * @param <R> The return type.
097 */
098@SuppressWarnings({
099   "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_exceptionDuringCall)
100   "java:S1192" // Duplicated literals read more clearly inline than as constants.
101})
102public class FluentResponseBodyAssertion<R> extends FluentObjectAssertion<ResponseContent,R> {
103
104   /**
105    * Chained constructor.
106    *
107    * <p>
108    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
109    *
110    * @param creator
111    *    The assertion that created this assertion.
112    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
113    * @param value
114    *    The object being tested.
115    *    <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value).
116    * @param returns
117    *    The object to return after a test method is called.
118    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
119    * used on the same assertion.
120    */
121   public FluentResponseBodyAssertion(Assertion creator, ResponseContent value, R returns) {
122      super(creator, value, returns);
123      setThrowable(BadRequest.class);
124   }
125
126   /**
127    * Constructor.
128    *
129    * @param value
130    *    The object being tested.
131    *    <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value).
132    * @param returns
133    *    The object to return after a test method is called.
134    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
135    * used on the same assertion.
136    */
137   public FluentResponseBodyAssertion(ResponseContent value, R returns) {
138      this(null, value, returns);
139   }
140
141   /**
142    * Converts the body to a type using {@link ResponseContent#as(Class)} and then returns the value as an object assertion.
143    *
144    * <h5 class='section'>Examples:</h5>
145    * <p class='bjava'>
146    *    <jc>// Validates the response body as a list of strings and validates the length.</jc>
147    *    <jv>client</jv>
148    *       .get(<js>"/myBean"</js>)
149    *       .run()
150    *       .assertContent().as(List.<jk>class</jk>, String.<jk>class</jk>).is(<jv>x</jv> -&gt; <jv>x</jv>.size() &gt; 0);
151    * </p>
152    *
153    * @param <T> The object type to create.
154    * @param type The object type to create.
155    * @return A new fluent assertion object.
156    */
157   public <T> FluentAnyAssertion<T,R> as(Class<T> type) {
158      return new FluentAnyAssertion<>(valueAsType(type), returns());
159   }
160
161   /**
162    * Converts the body to a type using {@link ResponseContent#as(Type,Type...)} and then returns the value as an object assertion.
163    *
164    * <h5 class='section'>Examples:</h5>
165    * <p class='bjava'>
166    *    <jc>// Validates the response body as a list of strings and validates the length.</jc>
167    *    <jv>client</jv>
168    *       .get(<js>"/myBean"</js>)
169    *       .run()
170    *       .assertContent().as(List.<jk>class</jk>, String.<jk>class</jk>).is(<jv>x</jv> -&gt; <jv>x</jv>.size() &gt; 0);
171    * </p>
172    *
173    * <p>
174    * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}.
175    *
176    * @param type The object type to create.
177    * @param args Optional type arguments.
178    * @return A new fluent assertion object.
179    */
180   public FluentAnyAssertion<Object,R> as(Type type, Type...args) {
181      return new FluentAnyAssertion<>(valueAsType(type, args), returns());
182   }
183
184   /**
185    * Provides the ability to perform fluent-style assertions on the bytes of the response body.
186    *
187    * <h5 class='section'>Examples:</h5>
188    * <p class='bjava'>
189    *    <jc>// Validates the response body equals the text "foo".</jc>
190    *    <jv>client</jv>
191    *       .get(<jsf>URI</jsf>)
192    *       .run()
193    *       .assertContent().asBytes().asHex().is(<js>"666F6F"</js>);
194    * </p>
195    *
196    * <h5 class='section'>Notes:</h5><ul>
197    *    <li class='note'>
198    *       If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
199    *  <li class='note'>
200    *    When using this method, the body is automatically cached by calling the {@link ResponseContent#cache()}.
201    *    <li class='note'>
202    *       The input stream is automatically closed after this call.
203    * </ul>
204    *
205    * @return A new fluent assertion object.
206    */
207   public FluentByteArrayAssertion<R> asBytes() {
208      return new FluentByteArrayAssertion<>(valueAsBytes(), returns());
209   }
210
211   /**
212    * Provides the ability to perform fluent-style assertions on this response body.
213    *
214    * <h5 class='section'>Examples:</h5>
215    * <p class='bjava'>
216    *    <jc>// Validates the response body equals the text "OK".</jc>
217    *    <jv>client</jv>
218    *       .get(<jsf>URI</jsf>)
219    *       .run()
220    *       .assertContent().is(<js>"OK"</js>);
221    *
222    *    <jc>// Validates the response body contains the text "OK".</jc>
223    *    <jv>client</jv>
224    *       .get(<jsf>URI</jsf>)
225    *       .run()
226    *       .assertContent().isContains(<js>"OK"</js>);
227    *
228    *    <jc>// Validates the response body passes a predicate test.</jc>
229    *    <jv>client</jv>
230    *       .get(<jsf>URI</jsf>)
231    *       .run()
232    *       .assertContent().is(<jv>x</jv> -&gt; <jv>x</jv>.contains(<js>"OK"</js>));
233    *
234    *    <jc>// Validates the response body matches a regular expression.</jc>
235    *    <jv>client</jv>
236    *       .get(<jsf>URI</jsf>)
237    *       .run()
238    *       .assertContent().isPattern(<js>".*OK.*"</js>);
239    *
240    *    <jc>// Validates the response body matches a regular expression using regex flags.</jc>
241    *    <jv>client</jv>
242    *       .get(<jsf>URI</jsf>)
243    *       .run()
244    *       .assertContent().isPattern(<js>".*OK.*"</js>, <jsf>MULTILINE</jsf> &amp; <jsf>CASE_INSENSITIVE</jsf>);
245    *
246    *    <jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc>
247    *    Pattern <jv>pattern</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>);
248    *    <jv>client</jv>
249    *       .get(<jsf>URI</jsf>)
250    *       .run()
251    *       .assertContent().isPattern(<jv>pattern</jv>);
252    * </p>
253    *
254    * <p>
255    * The assertion test returns the original response object allowing you to chain multiple requests like so:
256    * <p class='bjava'>
257    *    <jc>// Validates the response body matches a regular expression.</jc>
258    *    MyBean <jv>bean</jv> = <jv>client</jv>
259    *       .get(<jsf>URI</jsf>)
260    *       .run()
261    *       .assertContent().isPattern(<js>".*OK.*"</js>);
262    *       .assertContent().isNotPattern(<js>".*ERROR.*"</js>)
263    *       .getContent().as(MyBean.<jk>class</jk>);
264    * </p>
265    *
266    * <h5 class='section'>Notes:</h5><ul>
267    *    <li class='note'>
268    *       If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
269    *  <li class='note'>
270    *    When using this method, the body is automatically cached by calling the {@link ResponseContent#cache()}.
271    *    <li class='note'>
272    *       The input stream is automatically closed after this call.
273    * </ul>
274    *
275    * @return A new fluent assertion object.
276    */
277   @Override
278   public FluentStringAssertion<R> asString() {
279      return new FluentStringAssertion<>(valueAsString(), returns());
280   }
281
282   /**
283    * Asserts that the body contains the specified value.
284    *
285    * @param value The value to check against.
286    * @return This object.
287    * @throws AssertionError If assertion failed.
288    */
289   public R is(String value) throws AssertionError {
290      return asString().is(value);
291   }
292
293   /**
294    * Asserts that the text contains all of the specified substrings.
295    *
296    * @param values The values to check against.
297    * @return This object.
298    * @throws AssertionError If assertion failed.
299    */
300   public R isContains(String...values) throws AssertionError {
301      return asString().isContains(values);
302   }
303
304   /**
305    * Asserts that the body is empty.
306    *
307    * @return This object.
308    * @throws AssertionError If assertion failed.
309    */
310   public R isEmpty() { return asString().isEmpty(); }
311
312   /**
313    * Asserts that the body doesn't contain any of the specified substrings.
314    *
315    * @param values The values to check against.
316    * @return This object.
317    * @throws AssertionError If assertion failed.
318    */
319   public R isNotContains(String...values) throws AssertionError {
320      return asString().isNotContains(values);
321   }
322
323   /**
324    * Asserts that the body is not empty.
325    *
326    * @return This object.
327    * @throws AssertionError If assertion failed.
328    */
329   public R isNotEmpty() { return asString().isNotEmpty(); }
330
331   @Override /* Overridden from Assertion */
332   public FluentResponseBodyAssertion<R> setMsg(String msg, Object...args) {
333      super.setMsg(msg, args);
334      return this;
335   }
336
337   @Override /* Overridden from Assertion */
338   public FluentResponseBodyAssertion<R> setOut(PrintStream value) {
339      super.setOut(value);
340      return this;
341   }
342
343   @Override /* Overridden from Assertion */
344   public FluentResponseBodyAssertion<R> setSilent() {
345      super.setSilent();
346      return this;
347   }
348
349   @Override /* Overridden from Assertion */
350   public FluentResponseBodyAssertion<R> setStdOut() {
351      super.setStdOut();
352      return this;
353   }
354
355   @Override /* Overridden from Assertion */
356   public FluentResponseBodyAssertion<R> setThrowable(Class<? extends RuntimeException> value) {
357      super.setThrowable(value);
358      return this;
359   }
360
361   private byte[] valueAsBytes() throws AssertionError {
362      try {
363         return value().cache().asBytes();
364      } catch (RestCallException e) {
365         throw error(e, "Exception occurred during call.");
366      }
367   }
368
369   private <T> T valueAsType(Type type, Type...args) throws AssertionError {
370      try {
371         return value().cache().as(type, args);
372      } catch (RestCallException e) {
373         throw error(e, "Exception occurred during call.");
374      }
375   }
376
377   @Override
378   protected String valueAsString() throws AssertionError {
379      try {
380         return value().cache().asString();
381      } catch (RestCallException e) {
382         throw error(e, "Exception occurred during call.");
383      }
384   }
385}