001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.assertions; 014 015import static org.apache.juneau.internal.StringUtils.*; 016 017import java.util.*; 018import java.util.function.*; 019import java.util.regex.*; 020import java.util.stream.*; 021 022import org.apache.juneau.internal.*; 023 024/** 025 * Used for fluent assertion calls against strings. 026 * 027 * <h5 class='section'>Example:</h5> 028 * <p class='bcode w800'> 029 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 030 * <jv>client</jv> 031 * .get(<jsf>URL</jsf>) 032 * .run() 033 * .assertBody().is(<js>"OK"</js>); 034 * </p> 035 * 036 * @param <R> The return type. 037 */ 038@FluentSetters(returns="FluentStringAssertion<R>") 039public class FluentStringAssertion<R> extends FluentObjectAssertion<R> { 040 041 private String text; 042 private boolean javaStrings; 043 044 /** 045 * Constructor. 046 * 047 * @param text The text being tested. 048 * @param returns The object to return after the test. 049 */ 050 public FluentStringAssertion(String text, R returns) { 051 this(null, text, returns); 052 } 053 054 /** 055 * Constructor. 056 * 057 * @param creator The assertion that created this assertion. 058 * @param text The text being tested. 059 * @param returns The object to return after the test. 060 */ 061 public FluentStringAssertion(Assertion creator, String text, R returns) { 062 super(creator, text, returns); 063 this.text = text; 064 } 065 066 /** 067 * When enabled, text in the message is converted to valid Java strings. 068 * 069 * <p class='bcode w800'> 070 * value.replaceAll(<js>"\\\\"</js>, <js>"\\\\\\\\"</js>).replaceAll(<js>"\n"</js>, <js>"\\\\n"</js>).replaceAll(<js>"\t"</js>, <js>"\\\\t"</js>); 071 * </p> 072 * 073 * @return This object (for method chaining). 074 */ 075 @FluentSetter 076 public FluentStringAssertion<R> javaStrings() { 077 this.javaStrings = true; 078 return this; 079 } 080 081 /** 082 * Performs the specified regular expression replacement on the underlying string. 083 * 084 * @param regex The regular expression to which this string is to be matched. 085 * @param replacement The string to be substituted for each match. 086 * @return This object (for method chaining). 087 */ 088 public FluentStringAssertion<R> replaceAll(String regex, String replacement) { 089 assertNotNull("regex", regex); 090 assertNotNull("replacement", replacement); 091 return apply(x -> x == null ? null : text.replaceAll(regex, replacement)); 092 } 093 094 /** 095 * Performs the specified substring replacement on the underlying string. 096 * 097 * @param target The sequence of char values to be replaced. 098 * @param replacement The replacement sequence of char values. 099 * @return This object (for method chaining). 100 */ 101 public FluentStringAssertion<R> replace(String target, String replacement) { 102 assertNotNull("target", target); 103 assertNotNull("replacement", replacement); 104 return apply(x -> x == null ? null : text.replace(target, replacement)); 105 } 106 107 /** 108 * URL-decodes the text in this assertion. 109 * 110 * @return The response object (for method chaining). 111 */ 112 public FluentStringAssertion<R> urlDecode() { 113 return apply(x->StringUtils.urlDecode(x)); 114 } 115 116 /** 117 * Sorts the contents of the text by lines. 118 * 119 * @return The response object (for method chaining). 120 */ 121 public FluentStringAssertion<R> sort() { 122 return apply(x->x == null ? null : Arrays.asList(x.trim().split("[\r\n]+")).stream().sorted().collect(Collectors.joining("\n"))); 123 } 124 125 /** 126 * Converts the text to lowercase. 127 * 128 * @return The response object (for method chaining). 129 */ 130 public FluentStringAssertion<R> lc() { 131 return apply(x->x == null ? null : x.toLowerCase()); 132 } 133 134 /** 135 * Converts the text to uppercase. 136 * 137 * @return The response object (for method chaining). 138 */ 139 public FluentStringAssertion<R> uc() { 140 return apply(x->x == null ? null : x.toUpperCase()); 141 } 142 143 /** 144 * Applies an abitrary function against the text in this assertion. 145 * 146 * @param f The function to apply. 147 * @return The response object (for method chaining). 148 */ 149 public FluentStringAssertion<R> apply(Function<String,String> f) { 150 return new FluentStringAssertion<>(this, f.apply(text), returns()); 151 } 152 153 /** 154 * Asserts that the text equals the specified value. 155 * 156 * <p> 157 * Similar to {@link #is(String)} except error message states diff position. 158 * 159 * <h5 class='section'>Example:</h5> 160 * <p class='bcode w800'> 161 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 162 * client 163 * .get(<jsf>URL</jsf>) 164 * .run() 165 * .assertBody().isEquals(<js>"OK"</js>); 166 * </p> 167 * 168 * @param value 169 * The value to check against. 170 * <br>If multiple values are specified, they are concatenated with newlines. 171 * @return The response object (for method chaining). 172 * @throws AssertionError If assertion failed. 173 */ 174 public R isEqual(String value) throws AssertionError { 175 if (! StringUtils.isEquals(value, text)) 176 throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPosition(value, text), fix(value), fix(text)); 177 return returns(); 178 } 179 180 /** 181 * Asserts that the lines of text equals the specified value. 182 * 183 * <h5 class='section'>Example:</h5> 184 * <p class='bcode w800'> 185 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 186 * client 187 * .get(<jsf>URL</jsf>) 188 * .run() 189 * .assertBody().isEqualLines( 190 * <js>"Line 1"</js>, 191 * <js>"Line 2"</js>, 192 * <js>"Line 3"</js> 193 * ); 194 * </p> 195 * 196 * @param lines 197 * The value to check against. 198 * <br>If multiple values are specified, they are concatenated with newlines. 199 * @return The response object (for method chaining). 200 * @throws AssertionError If assertion failed. 201 */ 202 public R isEqualLines(String...lines) throws AssertionError { 203 assertNotNull("lines", lines); 204 String v = join(lines, '\n'); 205 if (! StringUtils.isEquals(v, text)) 206 throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPosition(v, text), fix(v), fix(text)); 207 return returns(); 208 } 209 210 /** 211 * Asserts that the text equals the specified value after splitting both by newlines and sorting the rows. 212 * 213 * <h5 class='section'>Example:</h5> 214 * <p class='bcode w800'> 215 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 216 * client 217 * .get(<jsf>URL</jsf>) 218 * .run() 219 * .assertBody().isEqualSortedLines( 220 * <js>"Line 1"</js>, 221 * <js>"Line 2"</js>, 222 * <js>"Line 3"</js> 223 * ); 224 * </p> 225 * 226 * @param lines 227 * The value to check against. 228 * <br>If multiple values are specified, they are concatenated with newlines. 229 * @return The response object (for method chaining). 230 * @throws AssertionError If assertion failed. 231 */ 232 public R isEqualSortedLines(String...lines) { 233 assertNotNull("lines", lines); 234 exists(); 235 236 // Must work for windows too. 237 String[] e = StringUtils.join(lines, '\n').trim().split("[\r\n]+"), a = this.text.trim().split("[\r\n]+"); 238 239 if (e.length != a.length) 240 throw error("Expected text had different numbers of lines.\n\tExpected=[{0}]\n\tActual=[{1}]", e.length, a.length); 241 242 Arrays.sort(e); 243 Arrays.sort(a); 244 245 for (int i = 0; i < e.length; i++) 246 if (! e[i].equals(a[i])) 247 throw error("Expected text had different values at line {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", i+1, e[i], a[i]); 248 249 return returns(); 250 } 251 252 /** 253 * Asserts that the text equals the specified value. 254 * 255 * <p> 256 * Similar to {@link #isEqual(String)} except error message doesn't state diff position. 257 * 258 * <h5 class='section'>Example:</h5> 259 * <p class='bcode w800'> 260 * <jc>// Validates the response body of an HTTP call is the text "OK".</jc> 261 * client 262 * .get(<jsf>URL</jsf>) 263 * .run() 264 * .assertBody().is(<js>"OK"</js>); 265 * </p> 266 * 267 * @param value 268 * The value to check against. 269 * <br>If multiple values are specified, they are concatenated with newlines. 270 * @return The response object (for method chaining). 271 * @throws AssertionError If assertion failed. 272 */ 273 public R is(String value) throws AssertionError { 274 if (! StringUtils.isEquals(value, text)) 275 throw error("Unexpected value.\n\tExpected=[{0}]\n\tActual=[{1}]", fix(value), fix(text)); 276 return isEqual(value); 277 } 278 279 /** 280 * Asserts that the text equals the specified value ignoring case. 281 * 282 * @param value The value to check against. 283 * @return The response object (for method chaining). 284 * @throws AssertionError If assertion failed. 285 */ 286 public R isEqualIc(String value) throws AssertionError { 287 if (! StringUtils.isEqualsIc(value, text)) 288 throw error("Text differed at position {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", diffPositionIc(value, text), fix(value), fix(text)); 289 return returns(); 290 } 291 292 /** 293 * Asserts that the text equals the specified value. 294 * 295 * @param value The value to check against. 296 * @return The response object (for method chaining). 297 * @throws AssertionError If assertion failed. 298 */ 299 public R doesNotEqual(String value) throws AssertionError { 300 if (StringUtils.isEquals(value, text)) 301 throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text)); 302 return returns(); 303 } 304 305 /** 306 * Asserts that the text equals the specified value. 307 * 308 * <p> 309 * Equivalent to {@link #doesNotEqual(String)}. 310 * 311 * @param value The value to check against. 312 * @return The response object (for method chaining). 313 * @throws AssertionError If assertion failed. 314 */ 315 public R isNot(String value) throws AssertionError { 316 return doesNotEqual(value); 317 } 318 319 /** 320 * Asserts that the text does not equal the specified value ignoring case. 321 * 322 * @param value The value to check against. 323 * @return The response object (for method chaining). 324 * @throws AssertionError If assertion failed. 325 */ 326 public R doesNotEqualIc(String value) throws AssertionError { 327 if (StringUtils.isEqualsIc(value, text)) 328 throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text)); 329 return returns(); 330 } 331 332 /** 333 * Asserts that the text contains all of the specified substrings. 334 * 335 * @param values The values to check against. 336 * @return The response object (for method chaining). 337 * @throws AssertionError If assertion failed. 338 */ 339 public R contains(String...values) throws AssertionError { 340 assertNotNull("values", values); 341 for (String substring : values) 342 if (substring != null && ! StringUtils.contains(text, substring)) 343 throw error("Text did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text)); 344 return returns(); 345 } 346 347 /** 348 * Asserts that the text doesn't contain any of the specified substrings. 349 * 350 * @param values The values to check against. 351 * @return The response object (for method chaining). 352 * @throws AssertionError If assertion failed. 353 */ 354 public R doesNotContain(String...values) throws AssertionError { 355 assertNotNull("values", values); 356 for (String substring : values) 357 if (substring != null && StringUtils.contains(text, substring)) 358 throw error("Text contained unexpected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text)); 359 return returns(); 360 } 361 362 /** 363 * Asserts that the text is not empty. 364 * 365 * @return The response object (for method chaining). 366 * @throws AssertionError If assertion failed. 367 */ 368 public R isEmpty() throws AssertionError { 369 if (text != null && ! text.isEmpty()) 370 throw error("Text was not empty.\n\tText=[{0}]", fix(text)); 371 return returns(); 372 } 373 374 /** 375 * Asserts that the text is not null or empty. 376 * 377 * @return The response object (for method chaining). 378 * @throws AssertionError If assertion failed. 379 */ 380 public R isNotEmpty() throws AssertionError { 381 if (text == null) 382 throw error("Text was null."); 383 if (text.isEmpty()) 384 throw error("Text was empty."); 385 return returns(); 386 } 387 388 /** 389 * Asserts that the text matches the specified regular expression. 390 * 391 * @param regex The pattern to test for. 392 * @return The response object (for method chaining). 393 * @throws AssertionError If assertion failed. 394 */ 395 public R matches(String regex) throws AssertionError { 396 return matches(regex, 0); 397 } 398 399 /** 400 * Asserts that the text matches the specified pattern containing <js>"*"</js> meta characters. 401 * 402 * <p> 403 * The <js>"*"</js> meta character can be used to represent zero or more characters.. 404 * 405 * @param searchPattern The search pattern. 406 * @return The response object (for method chaining). 407 * @throws AssertionError If assertion failed. 408 */ 409 public R matchesSimple(String searchPattern) throws AssertionError { 410 assertNotNull("searchPattern", searchPattern); 411 return matches(getMatchPattern(searchPattern)); 412 } 413 414 /** 415 * Asserts that the text doesn't match the specified regular expression. 416 * 417 * @param regex The pattern to test for. 418 * @return The response object (for method chaining). 419 * @throws AssertionError If assertion failed. 420 */ 421 public R doesNotMatch(String regex) throws AssertionError { 422 assertNotNull("regex", regex); 423 return doesNotMatch(regex, 0); 424 } 425 426 /** 427 * Asserts that the text matches the specified regular expression. 428 * 429 * @param regex The pattern to test for. 430 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 431 * @return The response object (for method chaining). 432 * @throws AssertionError If assertion failed. 433 */ 434 public R matches(String regex, int flags) throws AssertionError { 435 assertNotNull("regex", regex); 436 exists(); 437 Pattern p = Pattern.compile(regex, flags); 438 if (! p.matcher(text).matches()) 439 throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(regex), fix(text)); 440 return returns(); 441 } 442 443 /** 444 * Asserts that the text doesn't match the specified regular expression. 445 * 446 * @param regex The pattern to test for. 447 * @param flags Pattern match flags. See {@link Pattern#compile(String, int)}. 448 * @return The response object (for method chaining). 449 * @throws AssertionError If assertion failed. 450 */ 451 public R doesNotMatch(String regex, int flags) throws AssertionError { 452 assertNotNull("regex", regex); 453 return doesNotMatch(Pattern.compile(regex, flags)); 454 } 455 456 /** 457 * Asserts that the text matches the specified regular expression pattern. 458 * 459 * @param pattern The pattern to test for. 460 * @return The response object (for method chaining). 461 * @throws AssertionError If assertion failed. 462 */ 463 public R matches(Pattern pattern) throws AssertionError { 464 assertNotNull("pattern", pattern); 465 exists(); 466 if (! pattern.matcher(text).matches()) 467 throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text)); 468 return returns(); 469 } 470 471 /** 472 * Asserts that the text doesn't match the specified regular expression pattern. 473 * 474 * @param pattern The pattern to test for. 475 * @return The response object (for method chaining). 476 * @throws AssertionError If assertion failed. 477 */ 478 public R doesNotMatch(Pattern pattern) throws AssertionError { 479 assertNotNull("pattern", pattern); 480 if (text != null && pattern.matcher(text).matches()) 481 throw error("Text matched unexpected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text)); 482 return returns(); 483 } 484 485 /** 486 * Asserts that the text starts with the specified string. 487 * 488 * @param string The string to test for. 489 * @return The response object (for method chaining). 490 * @throws AssertionError If assertion failed. 491 */ 492 public R startsWith(String string) { 493 exists(); 494 assertNotNull("string", string); 495 if (! text.startsWith(string)) 496 throw error("Text did not start with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text)); 497 return returns(); 498 } 499 500 /** 501 * Asserts that the text ends with the specified string. 502 * 503 * @param string The string to test for. 504 * @return The response object (for method chaining). 505 * @throws AssertionError If assertion failed. 506 */ 507 public R endsWith(String string) { 508 exists(); 509 assertNotNull("string", string); 510 if (! text.endsWith(string)) 511 throw error("Text did not end with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text)); 512 return returns(); 513 } 514 515 //------------------------------------------------------------------------------------------------------------------ 516 // Utility methods 517 //------------------------------------------------------------------------------------------------------------------ 518 519 private String fix(String text) { 520 if (javaStrings) 521 text = text.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t"); 522 return text; 523 } 524 525 // <FluentSetters> 526 527 @Override /* GENERATED - Assertion */ 528 public FluentStringAssertion<R> msg(String msg, Object...args) { 529 super.msg(msg, args); 530 return this; 531 } 532 533 @Override /* GENERATED - Assertion */ 534 public FluentStringAssertion<R> stderr() { 535 super.stderr(); 536 return this; 537 } 538 539 @Override /* GENERATED - Assertion */ 540 public FluentStringAssertion<R> stdout() { 541 super.stdout(); 542 return this; 543 } 544 545 // </FluentSetters> 546}