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