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 org.apache.juneau.rest.client.*;
020
021/**
022 * A fluent assertion class for validating {@link RestResponse} instances.
023 *
024 * <p>
025 * Provides assertion methods for HTTP status code, response headers, and body content.
026 * All assertion methods return {@code this} for chaining, and throw {@link AssertionError}
027 * on failure.
028 *
029 * <p>
030 * Obtain instances via {@link RestResponse#assertThat()}.
031 *
032 * <p class='bjava'>
033 *    <jk>try</jk> (RestResponse <jv>resp</jv> = client.get(<js>"/api/users"</js>).run()) {
034 *       <jv>resp</jv>.assertThat()
035 *          .statusCode(200)
036 *          .header(<js>"Content-Type"</js>).contains(<js>"application/json"</js>)
037 *          .body().contains(<js>"alice"</js>);
038 *    }
039 * </p>
040 *
041 * <h5 class='section'>See Also:</h5><ul>
042 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
043 * </ul>
044 *
045 * @since 9.2.1
046 */
047@SuppressWarnings({
048   "resource" // Eclipse resource analysis: response is borrowed for assertions; caller closes it
049})
050public final class ResponseAssertion {
051
052   private final RestResponse response;
053
054   /**
055    * Constructor.
056    *
057    * @param response The response to assert on. Must not be <jk>null</jk>.
058    */
059   public ResponseAssertion(RestResponse response) {
060      this.response = response;
061   }
062
063   /**
064    * Asserts that the response status code equals the expected value.
065    *
066    * @param expected The expected status code.
067    * @return This object (for chaining).
068    * @throws AssertionError If the status code does not match.
069    */
070   public ResponseAssertion statusCode(int expected) {
071      var actual = response.getStatusCode();
072      if (actual != expected)
073         throw new AssertionError("Expected status code " + expected + " but got " + actual);
074      return this;
075   }
076
077   /**
078    * Asserts that the response status code is in the 2xx (success) range.
079    *
080    * @return This object (for chaining).
081    * @throws AssertionError If the status code is not in 200–299.
082    */
083   public ResponseAssertion isOk() {
084      var sc = response.getStatusCode();
085      if (sc < 200 || sc > 299)
086         throw new AssertionError("Expected 2xx status but got " + sc);
087      return this;
088   }
089
090   /**
091    * Asserts that the response status code is in the 4xx (client error) range.
092    *
093    * @return This object (for chaining).
094    * @throws AssertionError If the status code is not in 400–499.
095    */
096   public ResponseAssertion isClientError() {
097      var sc = response.getStatusCode();
098      if (sc < 400 || sc > 499)
099         throw new AssertionError("Expected 4xx status but got " + sc);
100      return this;
101   }
102
103   /**
104    * Asserts that the response status code is in the 5xx (server error) range.
105    *
106    * @return This object (for chaining).
107    * @throws AssertionError If the status code is not in 500–599.
108    */
109   public ResponseAssertion isServerError() {
110      var sc = response.getStatusCode();
111      if (sc < 500 || sc > 599)
112         throw new AssertionError("Expected 5xx status but got " + sc);
113      return this;
114   }
115
116   /**
117    * Returns a fluent header assertion for the named response header.
118    *
119    * @param name The header name (case-insensitive). Must not be <jk>null</jk>.
120    * @return A new header assertion. Never <jk>null</jk>.
121    */
122   public ResponseHeaderAssertion header(String name) {
123      return new ResponseHeaderAssertion(name, response, this);
124   }
125
126   /**
127    * Returns a fluent body assertion.
128    *
129    * @return A new body assertion. Never <jk>null</jk>.
130    */
131   public ResponseBodyAssertion body() {
132      return new ResponseBodyAssertion(response, this);
133   }
134
135   /**
136    * Returns the underlying response (for further access after assertions).
137    *
138    * @return The response. Never <jk>null</jk>.
139    */
140   public RestResponse and() {
141      return response;
142   }
143}