View Javadoc
1   /*
2    * Copyright 2023 Adrian Herscu
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package dev.aherscu.qa.jgiven.commons.steps;
17  
18  import static dev.aherscu.qa.testing.utils.StringUtilsExtensions.*;
19  import static org.assertj.core.api.Assertions.assertThat;
20  import static org.hamcrest.MatcherAssert.assertThat;
21  import static org.testng.Assert.*;
22  
23  import java.io.*;
24  import java.nio.charset.*;
25  import java.util.function.*;
26  
27  import javax.annotation.concurrent.*;
28  
29  import org.apache.commons.io.*;
30  import org.hamcrest.*;
31  
32  import com.google.common.collect.*;
33  import com.tngtech.jgiven.annotation.*;
34  
35  import dev.aherscu.qa.jgiven.commons.formatters.*;
36  import dev.aherscu.qa.jgiven.commons.model.*;
37  import dev.aherscu.qa.jgiven.commons.utils.*;
38  import dev.aherscu.qa.testing.utils.assertions.*;
39  import lombok.*;
40  import lombok.extern.slf4j.*;
41  import net.jodah.failsafe.*;
42  
43  /**
44   * Generic verifications.
45   *
46   * @param <SELF>
47   *            the type of the subclass
48   *
49   * @param <T>
50   *            type of scenario
51   *
52   * @author aherscu
53   */
54  @ThreadSafe
55  @Slf4j
56  @SuppressWarnings({ "boxing" })
57  public class GenericVerifications<T extends AnyScenarioType, SELF extends GenericVerifications<T, SELF>>
58      extends StageEx<SELF>
59      implements ScenarioType<T> {
60  
61      /**
62       * Logs the construction of this stage.
63       */
64      public GenericVerifications() {
65          log.trace("then stage {} constructed", this); //$NON-NLS-1$
66      }
67  
68      /**
69       * Repeatedly executes specified block on <strong>same thread</strong>,
70       * verifying its outcome matches the expected.
71       *
72       * @param step
73       *            the block to execute
74       * @param additionalRetryPolicies
75       *            additional retry policies
76       * @return {@link #self()}
77       * @throws AssertionError
78       *             if the supplied object does not match
79       */
80      @SafeVarargs
81      public final SELF eventually(
82          final Function<SELF, SELF> step,
83          final Policy<SELF>... additionalRetryPolicies) {
84          return eventually(new StepWithDescription<>(EMPTY, step),
85              additionalRetryPolicies);
86      }
87  
88      /**
89       * Executes specified step on <strong>same thread</strong>, and repeats it
90       * upon {@link AssertionError}. The interval and duration of these
91       * repetitions are configured via {@link #beforeScenarioConfigurePolling()}.
92       *
93       * @param step
94       *            the block to execute
95       * @param additionalRetryPolicies
96       *            additional retry policies
97       * @return {@link #self()}
98       * @throws AssertionError
99       *             if the supplied object does not match
100      *
101      * @see #beforeScenarioConfigurePolling()
102      */
103     @SafeVarargs
104     public final SELF eventually(final StepWithDescription<SELF> step,
105         final Policy<SELF>... additionalRetryPolicies) {
106         try {
107             return Failsafe
108                 .with(Lists.asList(retryPolicy, additionalRetryPolicies))
109                 .get(() -> step.apply(self()));
110         } catch (final Throwable t) {
111             log.error("eventually got {}", t.getMessage());
112             throw t;
113         }
114     }
115 
116     /**
117      * Asserts supplied object matches specified matcher on <strong>same
118      * thread</strong>. Upon {@link AssertionError}, asks for updated object and
119      * asserts again. Ignores all exceptions. The interval and duration of these
120      * assertions are configured via {@link #beforeScenarioConfigurePolling()}.
121      *
122      * @param objectToBeAsserted
123      *            the supplied object to assert upon
124      * @param matcher
125      *            the matcher
126      * @param <V>
127      *            the type of object to assert upon
128      * @param additionalRetryPolicies
129      *            additional retry policies
130      * @return {@link #self()}
131      * @throws AssertionError
132      *             if the supplied object does not match
133      *
134      * @see #beforeScenarioConfigurePolling()
135      */
136     @SafeVarargs
137     public final <V> SELF eventually_assert_that(
138         final java.util.function.Supplier<V> objectToBeAsserted,
139         final Matcher<V> matcher,
140         final Policy<SELF>... additionalRetryPolicies) {
141         return eventually(self -> {
142             final V value;
143             try {
144                 value = objectToBeAsserted.get();
145             } catch (final Throwable t) {
146                 log.trace("while evaluating got {}", t.getMessage());
147                 throw t;
148             }
149             log.trace("asserting value {} against {}",
150                 prettified(value),
151                 matcher);
152             assertThat(value, matcher);
153             return self;
154         }, additionalRetryPolicies);
155     }
156 
157     /**
158      * Verifies that the expected is true.
159      *
160      * @param matcher
161      *            the matching rule
162      * @return {@link #self()}
163      */
164     public SELF should_succeed(final Matcher<Boolean> matcher) {
165         assertThat(true, matcher);
166         return self();
167     }
168 
169     /**
170      * Verifies the contents of the specified JSON file.
171      *
172      * @param jsonFile
173      *            the JSON file
174      * @param expectedContents
175      *            the expected contents
176      * @return {@link #self()}
177      */
178     @SneakyThrows(IOException.class)
179     @As("the JSON file $ should contain")
180     public SELF the_JSON_file_$_should_contain(
181         final File jsonFile,
182         @JsonAssertionsFormatter.Annotation final Iterable<? extends JsonAssertion<?>> expectedContents) {
183         try (val jsonInputStream = new FileInputStream(jsonFile)) {
184             // FIXME: duplicated from RestClientThen#the_response_contains
185             val jsonVerifier = JsonAssert.with(jsonInputStream);
186             for (val pair : expectedContents) {
187                 if (null == pair.getValue()) {
188                     jsonVerifier.assertNotDefined(pair.getKey());
189                 } else {
190                     jsonVerifier.assertEquals(pair.getKey(), pair.getValue());
191                 }
192             }
193         }
194         return self();
195     }
196 
197     /**
198      * Verifies the contents of the specified file.
199      *
200      * <p>
201      * <strong>IMPORTANT</strong>: not suitable for large files
202      * </p>
203      *
204      * @param file
205      *            the type of IDX file
206      * @param contents
207      *            the contents
208      * @return {@link #self()}
209      */
210     public SELF the_file_$_should_contain(
211         final File file,
212         final String contents) {
213         assertThat(file).hasContent(contents);
214         return self();
215     }
216 
217     /**
218      * Verifies the contents of the specified file.
219      *
220      * <p>
221      * <strong>IMPORTANT</strong>: not suitable for large files
222      * </p>
223      *
224      * @param file
225      *            the type of IDX file
226      * @param expected
227      *            the contents to match
228      * @return {@link #self()}
229      */
230     @SneakyThrows(IOException.class)
231     public SELF the_file_$_should_match(
232         final File file,
233         final Matcher<String> expected) {
234         assertTrue(expected.matches(IOUtils
235             .toString(file.toURI(), StandardCharsets.UTF_8)));
236         return self();
237     }
238 
239     @Override
240     protected void beforeScenarioConfigurePolling() {
241         super.beforeScenarioConfigurePolling();
242         retryPolicy.handle(AssertionError.class);
243     }
244 }