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