Coverage Report - com.jcabi.http.response.RestResponse
 
Classes in this File Line Coverage Branch Coverage Complexity
RestResponse
83%
47/56
46%
14/30
1.643
RestResponse$StatusMatch
100%
4/4
100%
2/2
1.643
 
 1  
 /**
 2  
  * Copyright (c) 2011-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.http.response;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.http.Request;
 34  
 import com.jcabi.http.Response;
 35  
 import com.jcabi.log.Logger;
 36  
 import java.net.HttpCookie;
 37  
 import java.net.URI;
 38  
 import java.util.Collections;
 39  
 import java.util.Iterator;
 40  
 import java.util.List;
 41  
 import java.util.Map;
 42  
 import javax.ws.rs.core.Cookie;
 43  
 import javax.ws.rs.core.HttpHeaders;
 44  
 import lombok.EqualsAndHashCode;
 45  
 import org.hamcrest.CustomMatcher;
 46  
 import org.hamcrest.Matcher;
 47  
 import org.hamcrest.MatcherAssert;
 48  
 import org.hamcrest.Matchers;
 49  
 
 50  
 /**
 51  
  * REST response.
 52  
  *
 53  
  * <p>This response decorator is able to make basic assertions on
 54  
  * HTTP response and manipulate with it afterwords, for example:
 55  
  *
 56  
  * <pre> String name = new JdkRequest("http://my.example.com")
 57  
  *   .fetch()
 58  
  *   .as(RestResponse.class)
 59  
  *   .assertStatus(200)
 60  
  *   .assertBody(Matchers.containsString("hello, world!"))
 61  
  *   .assertHeader("Content-Type", Matchers.hasItem("text/plain"))
 62  
  *   .jump(URI.create("/users"))
 63  
  *   .fetch();</pre>
 64  
  *
 65  
  * <p>Method {@link #jump(URI)} creates a new instance of class
 66  
  * {@link Request} with all cookies transferred from the current one.
 67  
  *
 68  
  * <p>The class is immutable and thread-safe.
 69  
  *
 70  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 71  
  * @version $Id: 8b3634c366e075b7fd59c89cbc3839a1cbc26fb7 $
 72  
  * @since 0.8
 73  
  */
 74  54
 @Immutable
 75  0
 @EqualsAndHashCode(callSuper = true)
 76  
 @SuppressWarnings("PMD.TooManyMethods")
 77  
 public final class RestResponse extends AbstractResponse {
 78  
 
 79  
     /**
 80  
      * Public ctor.
 81  
      * @param resp Response
 82  
      */
 83  
     public RestResponse(final Response resp) {
 84  131
         super(resp);
 85  131
     }
 86  
 
 87  
     /**
 88  
      * Assert using custom matcher.
 89  
      * @param matcher The matcher to use
 90  
      * @return The same object
 91  
      */
 92  
     public RestResponse assertThat(final Matcher<Response> matcher) {
 93  0
         MatcherAssert.assertThat(
 94  
             String.format("HTTP response is not valid: %s", this),
 95  
             this,
 96  
             matcher
 97  
         );
 98  0
         return this;
 99  
     }
 100  
 
 101  
     /**
 102  
      * Verifies HTTP response status code against the provided absolute value,
 103  
      * and throws {@link AssertionError} in case of mismatch.
 104  
      * @param status Expected status code
 105  
      * @return The same object
 106  
      */
 107  
     public RestResponse assertStatus(final int status) {
 108  114
         final String message = String.format(
 109  
             "HTTP response with status %d", status
 110  
         );
 111  114
         MatcherAssert.assertThat(
 112  
             String.format(
 113  
                 "HTTP response status is not equal to %d:%n%s",
 114  
                 status, this
 115  
             ),
 116  
             this,
 117  
             new RestResponse.StatusMatch(message, status)
 118  
         );
 119  113
         return this;
 120  
     }
 121  
 
 122  
     /**
 123  
      * Verifies HTTP response status code against the provided matcher,
 124  
      * and throws {@link AssertionError} in case of mismatch.
 125  
      * @param matcher Matcher to validate status code
 126  
      * @return This object
 127  
      */
 128  
     public RestResponse assertStatus(final Matcher<Integer> matcher) {
 129  2
         MatcherAssert.assertThat(
 130  
             String.format(
 131  
                 "HTTP response status is not the one expected:%n%s",
 132  
                 this
 133  
             ),
 134  
             this.status(), matcher
 135  
         );
 136  2
         return this;
 137  
     }
 138  
 
 139  
     /**
 140  
      * Verifies HTTP response body content against provided matcher,
 141  
      * and throws {@link AssertionError} in case of mismatch.
 142  
      * @param matcher The matcher to use
 143  
      * @return This object
 144  
      */
 145  
     public RestResponse assertBody(final Matcher<String> matcher) {
 146  53
         MatcherAssert.assertThat(
 147  
             String.format(
 148  
                 "HTTP response body content is not valid:%n%s",
 149  
                 this
 150  
             ),
 151  
             this.body(), matcher
 152  
         );
 153  53
         return this;
 154  
     }
 155  
 
 156  
     /**
 157  
      * Verifies HTTP response body content against provided matcher,
 158  
      * and throws {@link AssertionError} in case of mismatch.
 159  
      * @param matcher The matcher to use
 160  
      * @return This object
 161  
      */
 162  
     public RestResponse assertBinary(final Matcher<byte[]> matcher) {
 163  2
         MatcherAssert.assertThat(
 164  
             String.format(
 165  
                 "HTTP response binary content is not valid:%n%s",
 166  
                 this
 167  
             ), this.binary(),
 168  
             matcher
 169  
         );
 170  2
         return this;
 171  
     }
 172  
 
 173  
     /**
 174  
      * Verifies HTTP header against provided matcher, and throws
 175  
      * {@link AssertionError} in case of mismatch.
 176  
      *
 177  
      * <p>The iterator for the matcher will always be a real object an never
 178  
      * {@code NULL}, even if such a header is absent in the response. If the
 179  
      * header is absent the iterable will be empty.
 180  
      *
 181  
      * @param name Name of the header to match
 182  
      * @param matcher The matcher to use
 183  
      * @return This object
 184  
      */
 185  
     public RestResponse assertHeader(final String name,
 186  
         final Matcher<Iterable<String>> matcher) {
 187  10
         Iterable<String> values = this.headers().get(name);
 188  10
         if (values == null) {
 189  3
             values = Collections.emptyList();
 190  
         }
 191  10
         MatcherAssert.assertThat(
 192  
             String.format(
 193  
                 "HTTP header '%s' is not valid:%n%s",
 194  
                 name, this
 195  
             ),
 196  
             values, matcher
 197  
         );
 198  10
         return this;
 199  
     }
 200  
 
 201  
     /**
 202  
      * Verifies HTTP header against provided matcher, and throws
 203  
      * {@link AssertionError} in case of mismatch.
 204  
      * @param name Name of the header to match
 205  
      * @param value The value to expect in one of the headers
 206  
      * @return This object
 207  
      * @since 0.9
 208  
      */
 209  
     public RestResponse assertHeader(final String name, final String value) {
 210  2
         return this.assertHeader(name, Matchers.hasItems(value));
 211  
     }
 212  
 
 213  
     /**
 214  
      * Jump to a new location.
 215  
      * @param uri Destination to jump to
 216  
      * @return New request
 217  
      */
 218  
     @SuppressWarnings("PMD.UseConcurrentHashMap")
 219  
     public Request jump(final URI uri) {
 220  5
         Request req = this.back().uri()
 221  
             .set(this.back().uri().get().resolve(uri))
 222  
             .back();
 223  5
         final Map<String, List<String>> headers = this.headers();
 224  5
         if (headers.containsKey(HttpHeaders.SET_COOKIE)) {
 225  2
             for (final String header : headers.get(HttpHeaders.SET_COOKIE)) {
 226  6
                 for (final HttpCookie cookie : HttpCookie.parse(header)) {
 227  6
                     req = req.header(
 228  
                         HttpHeaders.COOKIE,
 229  
                         String.format(
 230  
                             "%s=%s", cookie.getName(), cookie.getValue()
 231  
                         )
 232  
                     );
 233  6
                 }
 234  6
             }
 235  
         }
 236  5
         return req;
 237  
     }
 238  
 
 239  
     /**
 240  
      * Follow LOCATION header.
 241  
      * @return New request
 242  
      */
 243  
     public Request follow() {
 244  2
         this.assertHeader(
 245  
             HttpHeaders.LOCATION,
 246  
             Matchers.not(Matchers.emptyIterableOf(String.class))
 247  
         );
 248  2
         return this.jump(
 249  
             URI.create(this.headers().get(HttpHeaders.LOCATION).get(0))
 250  
         );
 251  
     }
 252  
 
 253  
     /**
 254  
      * Get one cookie by name.
 255  
      * @param name Cookie name
 256  
      * @return Cookie found
 257  
      */
 258  
     @SuppressWarnings("PMD.UseConcurrentHashMap")
 259  
     public Cookie cookie(final String name) {
 260  1
         final Map<String, List<String>> headers = this.headers();
 261  1
         MatcherAssert.assertThat(
 262  
             "cookies should be set in HTTP header",
 263  
             headers.containsKey(HttpHeaders.SET_COOKIE)
 264  
         );
 265  1
         final Iterator<String> iterator =
 266  
             headers.get(HttpHeaders.SET_COOKIE).iterator();
 267  1
         final Object first = iterator.next();
 268  
         // @checkstyle MagicNumber (1 line)
 269  1
         final StringBuilder buf = new StringBuilder(256);
 270  1
         if (first != null) {
 271  1
             buf.append(first);
 272  
         }
 273  1
         while (iterator.hasNext()) {
 274  0
             buf.append(',');
 275  0
             final Object obj = iterator.next();
 276  0
             if (obj != null) {
 277  0
                 buf.append(obj);
 278  
             }
 279  0
         }
 280  1
         final String header = buf.toString();
 281  1
         Cookie cookie = null;
 282  1
         for (final HttpCookie candidate : HttpCookie.parse(header)) {
 283  1
             if (candidate.getName().equals(name)) {
 284  1
                 cookie = RestResponse.cookie(candidate);
 285  1
                 break;
 286  
             }
 287  0
         }
 288  1
         MatcherAssert.assertThat(
 289  
             Logger.format(
 290  
                 "cookie '%s' not found in Set-Cookie header: '%s'",
 291  
                 name, header
 292  
             ),
 293  
             cookie,
 294  
             Matchers.notNullValue()
 295  
         );
 296  1
         assert cookie != null;
 297  1
         return cookie;
 298  
     }
 299  
 
 300  
     /**
 301  
      * Convert HTTP cookie to a standard one.
 302  
      * @param cookie HTTP cookie
 303  
      * @return Regular one
 304  
      */
 305  
     private static Cookie cookie(final HttpCookie cookie) {
 306  1
         return new Cookie(
 307  
             cookie.getName(),
 308  
             cookie.getValue(),
 309  
             cookie.getPath(),
 310  
             cookie.getDomain(),
 311  
             cookie.getVersion()
 312  
         );
 313  
     }
 314  
 
 315  
     /**
 316  
      * Status matcher.
 317  
      */
 318  
     private static final class StatusMatch extends CustomMatcher<Response> {
 319  
         /**
 320  
          * HTTP status to check.
 321  
          */
 322  
         private final transient int status;
 323  
         /**
 324  
          * Ctor.
 325  
          * @param msg Message to show
 326  
          * @param sts HTTP status to check
 327  
          */
 328  
         StatusMatch(final String msg, final int sts) {
 329  114
             super(msg);
 330  114
             this.status = sts;
 331  114
         }
 332  
         @Override
 333  
         public boolean matches(final Object resp) {
 334  114
             return Response.class.cast(resp).status() == this.status;
 335  
         }
 336  
     }
 337  
 
 338  
 }