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 a named HTTP response header.
023 *
024 * <p>
025 * Obtain instances via {@link ResponseAssertion#header(String)}.
026 *
027 * <h5 class='section'>See Also:</h5><ul>
028 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
029 * </ul>
030 *
031 * @since 9.2.1
032 */
033@SuppressWarnings({
034   "resource" // Eclipse resource analysis: response is borrowed; caller closes it
035})
036public final class ResponseHeaderAssertion {
037
038   private final String name;
039
040   private final RestResponse response;
041   private final ResponseAssertion parent;
042
043   ResponseHeaderAssertion(String name, RestResponse response, ResponseAssertion parent) {
044      this.name = name;
045      this.response = response;
046      this.parent = parent;
047   }
048
049   /**
050    * Asserts that this header is present in the response.
051    *
052    * @return This object (for chaining).
053    * @throws AssertionError If the header is absent.
054    */
055   public ResponseHeaderAssertion isPresent() {
056      if (response.getFirstHeader(name) == null)
057         throw new AssertionError("Expected header '" + name + "' to be present but was absent");
058      return this;
059   }
060
061   /**
062    * Asserts that this header is absent from the response.
063    *
064    * @return This object (for chaining).
065    * @throws AssertionError If the header is present.
066    */
067   public ResponseHeaderAssertion isAbsent() {
068      var h = response.getFirstHeader(name);
069      if (h != null)
070         throw new AssertionError("Expected header '" + name + "' to be absent but was present with value: " + h.value());
071      return this;
072   }
073
074   /**
075    * Asserts that this header's value equals the expected value (case-sensitive).
076    *
077    * @param expected The expected value. Must not be <jk>null</jk>.
078    * @return This object (for chaining).
079    * @throws AssertionError If the value does not match.
080    */
081   @SuppressWarnings({
082      "java:S1201" // 'equals(String)' is an intentional fluent-assertion DSL method (asserts header equality), not an override of Object.equals(Object); renaming would break this public API.
083   })
084   public ResponseHeaderAssertion equals(String expected) {
085      var h = response.getFirstHeader(name);
086      var actual = h != null ? h.value() : null;
087      if (!expected.equals(actual))
088         throw new AssertionError("Header '" + name + "': expected '" + expected + "' but got '" + actual + "'");
089      return this;
090   }
091
092   /**
093    * Asserts that this header's value contains the given substring.
094    *
095    * @param substring The substring to look for. Must not be <jk>null</jk>.
096    * @return This object (for chaining).
097    * @throws AssertionError If the value is absent or does not contain the substring.
098    */
099   public ResponseHeaderAssertion contains(String substring) {
100      var h = response.getFirstHeader(name);
101      var actual = h != null ? h.value() : null;
102      if (actual == null || !actual.contains(substring))
103         throw new AssertionError("Header '" + name + "' value '" + actual + "' does not contain '" + substring + "'");
104      return this;
105   }
106
107   /**
108    * Asserts that this header's value starts with the given prefix.
109    *
110    * @param prefix The expected prefix. Must not be <jk>null</jk>.
111    * @return This object (for chaining).
112    * @throws AssertionError If the value is absent or does not start with the prefix.
113    */
114   public ResponseHeaderAssertion startsWith(String prefix) {
115      var h = response.getFirstHeader(name);
116      var actual = h != null ? h.value() : null;
117      if (actual == null || !actual.startsWith(prefix))
118         throw new AssertionError("Header '" + name + "' value '" + actual + "' does not start with '" + prefix + "'");
119      return this;
120   }
121
122   /**
123    * Asserts that this header's integer value equals the expected value.
124    *
125    * @param expected The expected integer value.
126    * @return This object (for chaining).
127    * @throws AssertionError If the value is absent, not a number, or does not match.
128    */
129   public ResponseHeaderAssertion integerEquals(int expected) {
130      var h = response.getFirstHeader(name);
131      if (h == null)
132         throw new AssertionError("Header '" + name + "' is absent");
133      int actual;
134      try {
135         actual = Integer.parseInt(h.value().trim());
136      } catch (NumberFormatException e) {
137         throw new AssertionError("Header '" + name + "' value '" + h.value() + "' is not a valid integer");
138      }
139      if (actual != expected)
140         throw new AssertionError("Header '" + name + "': expected " + expected + " but got " + actual);
141      return this;
142   }
143
144   /**
145    * Returns the parent {@link ResponseAssertion} to continue assertion chaining.
146    *
147    * @return The parent assertion. Never <jk>null</jk>.
148    */
149   public ResponseAssertion and() {
150      return parent;
151   }
152}