1 package com.atlassian.httpclient.api;
2
3 import com.atlassian.util.concurrent.Promise;
4 import com.google.common.base.Function;
5 import com.google.common.collect.ImmutableSet;
6 import com.google.common.util.concurrent.SettableFuture;
7 import org.junit.Assert;
8 import org.junit.Before;
9 import org.junit.Test;
10 import org.junit.runner.RunWith;
11 import org.mockito.Mock;
12 import org.mockito.Mockito;
13 import org.mockito.runners.MockitoJUnitRunner;
14
15 import javax.annotation.Nullable;
16 import java.util.Set;
17 import java.util.concurrent.atomic.AtomicInteger;
18
19 import static com.atlassian.httpclient.api.ResponsePromises.toResponsePromise;
20 import static com.atlassian.util.concurrent.Promises.forFuture;
21 import static com.atlassian.util.concurrent.Promises.forListenableFuture;
22 import static com.google.common.collect.Iterables.toArray;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.verifyNoMoreInteractions;
25 import static org.mockito.Mockito.when;
26
27 @RunWith(MockitoJUnitRunner.class)
28 public final class ResponseTransformationTest {
29 @Mock
30 private Response response;
31
32 @Mock
33 private Function<Response, Object> informationalFunction;
34
35 @Mock
36 private Function<Response, Object> successfulFunction;
37
38 @Mock
39 private Function<Response, Object> okFunction;
40
41 @Mock
42 private Function<Response, Object> createdFunction;
43
44 @Mock
45 private Function<Response, Object> noContentFunction;
46
47 @Mock
48 private Function<Response, Object> redirectionFunction;
49
50 @Mock
51 private Function<Response, Object> clientErrorFunction;
52
53 @Mock
54 private Function<Response, Object> notFoundFunction;
55
56 @Mock
57 private Function<Response, Object> seeOtherFunction;
58
59 @Mock
60 private Function<Response, Object> notModifiedFunction;
61
62 @Mock
63 private Function<Response, Object> badRequestFunction;
64
65 @Mock
66 private Function<Response, Object> unauthorizedFunction;
67
68 @Mock
69 private Function<Response, Object> forbiddenFunction;
70
71 @Mock
72 private Function<Response, Object> conflictFunction;
73
74 @Mock
75 private Function<Response, Object> internalServerErrorFunction;
76
77 @Mock
78 private Function<Response, Object> serviceUnavailableFunction;
79
80 @Mock
81 private Function<Response, Object> serverErrorFunction;
82
83 @Mock
84 private Function<Response, Object> othersFunction;
85
86 private Set<Function<Response, Object>> allFunctions;
87
88 private SettableFuture<Response> responseSettableFuture;
89
90 @Before
91 public void setUp() {
92 responseSettableFuture = SettableFuture.create();
93 allFunctions = ImmutableSet.<Function<Response, Object>>builder()
94 .add(informationalFunction)
95 .add(successfulFunction)
96 .add(okFunction)
97 .add(noContentFunction)
98 .add(createdFunction)
99 .add(redirectionFunction)
100 .add(clientErrorFunction)
101 .add(notFoundFunction)
102 .add(serverErrorFunction)
103 .add(othersFunction)
104 .build();
105 }
106
107 @Test
108 public void testInformationalFunctionCalledOn1xx() {
109 for (int statusCode = HttpStatus.CONTINUE.code; statusCode < HttpStatus.OK.code; statusCode++) {
110 testFunctionCalledForStatus(rangesBuilder(), informationalFunction, statusCode);
111 resetAllMocks();
112 }
113 }
114
115 @Test
116 public void testInformationalFunctionCalledOn2xx() {
117 for (int statusCode = HttpStatus.OK.code; statusCode < HttpStatus.MULTIPLE_CHOICES.code; statusCode++) {
118 testFunctionCalledForStatus(rangesBuilder(), successfulFunction, statusCode);
119 resetAllMocks();
120 }
121 }
122
123 @Test
124 public void testOkFunctionCalledOn200() {
125 testFunctionCalledForStatus(okFunction, HttpStatus.OK.code);
126 }
127
128 @Test
129 public void testCreatedFunctionCalledOn201() {
130 testFunctionCalledForStatus(createdFunction, HttpStatus.CREATED.code);
131 }
132
133 @Test
134 public void testNoContentFunctionCalledOn204() {
135 testFunctionCalledForStatus(noContentFunction, HttpStatus.NO_CONTENT.code);
136 }
137
138
139 @Test
140 public void testInformationalFunctionCalledOn3xx() {
141 for (int statusCode = HttpStatus.MULTIPLE_CHOICES.code; statusCode < HttpStatus.BAD_REQUEST.code; statusCode++) {
142 testFunctionCalledForStatus(rangesBuilder(), redirectionFunction, statusCode);
143 resetAllMocks();
144 }
145 }
146
147 @Test
148 public void testSeeOtherFunctionCalledOn303() {
149 testFunctionCalledForStatus(seeOtherFunction, HttpStatus.SEE_OTHER.code);
150 }
151
152 @Test
153 public void testSeeOtherFunctionCalledOn304() {
154 testFunctionCalledForStatus(notModifiedFunction, HttpStatus.NOT_MODIFIED.code);
155 }
156
157 @Test
158 public void testInformationalFunctionCalledOn4xx() {
159 for (int statusCode = HttpStatus.BAD_REQUEST.code; statusCode < HttpStatus.INTERNAL_SERVER_ERROR.code; statusCode++) {
160 testFunctionCalledForStatus(rangesBuilder(), clientErrorFunction, statusCode);
161 resetAllMocks();
162 }
163 }
164
165 @Test
166 public void testBadRequestFunctionCalledOn400() {
167 testFunctionCalledForStatus(badRequestFunction, HttpStatus.BAD_REQUEST.code);
168 }
169
170 @Test
171 public void testBadRequestFunctionCalledOn401() {
172 testFunctionCalledForStatus(unauthorizedFunction, HttpStatus.UNAUTHORIZED.code);
173 }
174
175 @Test
176 public void testForbiddenFunctionCalledOn403() {
177 testFunctionCalledForStatus(forbiddenFunction, HttpStatus.FORBIDDEN.code);
178 }
179
180 @Test
181 public void testNotFoundFunctionCalledOn404() {
182 testFunctionCalledForStatus(notFoundFunction, HttpStatus.NOT_FOUND.code);
183 }
184
185 @Test
186 public void testConflictFunctionCalledOn409() {
187 testFunctionCalledForStatus(conflictFunction, HttpStatus.CONFLICT.code);
188 }
189
190 @Test
191 public void testInformationalFunctionCalledOn5xx() {
192 for (int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.code; statusCode < 600; statusCode++) {
193 testFunctionCalledForStatus(rangesBuilder(), serverErrorFunction, statusCode);
194 resetAllMocks();
195 }
196 }
197
198 @Test
199 public void testInternalServerErrorFunctionCalledOn500() {
200 testFunctionCalledForStatus(internalServerErrorFunction, HttpStatus.INTERNAL_SERVER_ERROR.code);
201 }
202
203 @Test
204 public void testServiceUnavailableFunctionCalledOn503() {
205 testFunctionCalledForStatus(serviceUnavailableFunction, HttpStatus.SERVICE_UNAVAILABLE.code);
206 }
207
208 @Test(expected = IllegalStateException.class)
209 public void testFailThrowsException() {
210 ResponseTransformation<Object> responseTransformation = newBuilder().fail(new Function<Throwable, Object>() {
211 @Override
212 public Object apply(@Nullable Throwable input) {
213 throw new IllegalStateException("foo");
214 }
215 }).build();
216 responseSettableFuture.setException(new RuntimeException());
217 Promise<Object> promise = toResponsePromise(forListenableFuture(responseSettableFuture)).transform(responseTransformation);
218 promise.claim();
219 }
220
221 @Test
222 public void testDelayedExecutionExecutesOnce() {
223 final AtomicInteger counter = new AtomicInteger(0);
224 when(this.response.getStatusCode()).thenReturn(200);
225
226 ResponsePromise responsePromise = toResponsePromise(forListenableFuture(responseSettableFuture));
227
228 ResponseTransformation<String> responseTransformation = DefaultResponseTransformation.<String>builder()
229 .ok(new Function<Response, String>() {
230 @Override
231 public String apply(@Nullable Response input) {
232 return "foo" + counter.getAndIncrement();
233 }
234 })
235 .fail(new Function<Throwable, String>() {
236 @Override
237 public String apply(@Nullable Throwable input) {
238 throw new IllegalStateException();
239 }
240 }).build();
241 Promise<String> promise = responsePromise.transform(responseTransformation);
242 responseSettableFuture.set(this.response);
243 Assert.assertEquals("foo0", promise.claim());
244 }
245
246 @Test
247 public void testDelayedExecutionExecutesDoneOnceWithException() {
248 final AtomicInteger counter = new AtomicInteger(0);
249
250 when(this.response.getStatusCode()).thenReturn(200);
251
252 ResponseTransformation<String> responseTransformation = DefaultResponseTransformation.<String>builder()
253 .ok(new Function<Response, String>() {
254 @Override
255 public String apply(@Nullable Response input) {
256 throw new IllegalArgumentException("foo" + counter.getAndIncrement());
257 }
258 })
259 .fail(new Function<Throwable, String>() {
260 @Override
261 public String apply(@Nullable Throwable input) {
262 return null;
263 }
264 }).build();
265 Promise<String> promise = toResponsePromise(forListenableFuture(responseSettableFuture)).transform(responseTransformation);
266
267 responseSettableFuture.set(this.response);
268 try {
269 promise.claim();
270 } catch (IllegalArgumentException ex) {
271 Assert.assertEquals("foo0", ex.getMessage());
272 }
273 }
274
275 @Test
276 public void testDelayedExecutionExecutesFailOnce() {
277 final AtomicInteger counter = new AtomicInteger(0);
278 when(this.response.getStatusCode()).thenReturn(200);
279
280 ResponseTransformation<String> responseTransformation = DefaultResponseTransformation.<String>builder()
281 .fail(new Function<Throwable, String>() {
282 @Override
283 public String apply(@Nullable Throwable input) {
284 return "foo" + counter.getAndIncrement();
285 }
286 })
287 .ok(new Function<Response, String>() {
288 @Override
289 public String apply(@Nullable Response input) {
290 throw new IllegalStateException();
291 }
292 })
293 .build();
294 responseSettableFuture.set(this.response);
295 Promise<String> promise = toResponsePromise(forListenableFuture(responseSettableFuture)).transform(responseTransformation);
296 Assert.assertEquals("foo0", promise.claim());
297 }
298
299 @Test
300 public void testNotSuccessfulFunctionCalled() {
301
302
303 for (int statusCode = HttpStatus.CONTINUE.code; statusCode < 600; statusCode++) {
304 final ResponseTransformation<Object> transformation = newBuilder()
305 .notSuccessful(clientErrorFunction)
306 .others(successfulFunction).build();
307 if (HttpStatus.OK.code <= statusCode && statusCode < HttpStatus.MULTIPLE_CHOICES.code) {
308 testFunctionCalledForStatus(transformation, successfulFunction, statusCode);
309 } else {
310 testFunctionCalledForStatus(transformation, clientErrorFunction, statusCode);
311 }
312 resetAllMocks();
313 }
314 }
315
316 @Test
317 public void testErrorFunctionCalled() {
318
319 for (int statusCode = HttpStatus.CONTINUE.code; statusCode < 600; statusCode++) {
320 final ResponseTransformation<Object> responseTransformation = newBuilder()
321 .error(clientErrorFunction)
322 .others(successfulFunction).build();
323 if (HttpStatus.BAD_REQUEST.code <= statusCode && statusCode < 600) {
324 testFunctionCalledForStatus(responseTransformation, clientErrorFunction, statusCode);
325 } else {
326 testFunctionCalledForStatus(responseTransformation, successfulFunction, statusCode);
327 }
328 resetAllMocks();
329 }
330 }
331
332 @Test
333 public void testOthersFunctionCalled() {
334 testFunctionCalledForStatus(
335 newBuilder().others(othersFunction).build(),
336 othersFunction,
337 HttpStatus.OK.code);
338 }
339
340 @Test
341 public void testFunctionAddedOnNonStandardHttpStatus() {
342 newBuilder().on(601, new Function<Response, Object>() {
343 @Override
344 public Object apply(@Nullable Response input) {
345 return null;
346 }
347 });
348 }
349
350 @Test(expected = IllegalMonitorStateException.class)
351 public void testFailCanThrowExceptions() {
352 responseSettableFuture.setException(new Throwable("Some message"));
353 ResponsePromise responsePromise = toResponsePromise(forListenableFuture(responseSettableFuture));
354 DefaultResponseTransformation.<String>builder()
355 .ok(new Function<Response, String>() {
356 @Override
357 public String apply(Response input) {
358 return "Ok";
359 }
360 })
361 .fail(new Function<Throwable, String>() {
362 @Override
363 public String apply(Throwable input) {
364 throw new IllegalMonitorStateException();
365 }
366 }).build().apply(responsePromise).claim();
367 }
368
369 @Test(expected = IllegalMonitorStateException.class)
370 public void testFailExceptionsWithoutHandlersAreThrownAtClaim() {
371 responseSettableFuture.setException(new IllegalMonitorStateException("Some message"));
372 newBuilder().build().apply(toResponsePromise(forListenableFuture(responseSettableFuture))).claim();
373 }
374
375 private void testFunctionCalledForStatus(Function<Response, Object> function, int statusCode) {
376 testFunctionCalledForStatus(
377 newBuilder()
378 .informational(informationalFunction)
379 .ok(okFunction)
380 .created(createdFunction)
381 .noContent(noContentFunction)
382 .notFound(notFoundFunction)
383 .seeOther(seeOtherFunction)
384 .notModified(notModifiedFunction)
385 .badRequest(badRequestFunction)
386 .unauthorized(unauthorizedFunction)
387 .forbidden(forbiddenFunction)
388 .conflict(conflictFunction)
389 .internalServerError(internalServerErrorFunction)
390 .serviceUnavailable(serviceUnavailableFunction)
391 .others(othersFunction).build(),
392 function,
393 statusCode);
394 }
395
396 private void testFunctionCalledForStatus(ResponseTransformation<Object> responseTransformation, Function<Response, Object> function, int statusCode) {
397 when(response.getStatusCode()).thenReturn(statusCode);
398 responseSettableFuture.set(response);
399
400 responseTransformation.apply(toResponsePromise(forFuture(responseSettableFuture)));
401
402 verify(function).apply(response);
403 verifyNoMoreInteractions(allFunctionsAsArray());
404 }
405
406 private ResponseTransformation.Builder<Object> newBuilder() {
407 return DefaultResponseTransformation.builder();
408 }
409
410 private ResponseTransformation<Object> rangesBuilder() {
411 return newBuilder()
412 .successful(successfulFunction)
413 .informational(informationalFunction)
414 .redirection(redirectionFunction)
415 .clientError(clientErrorFunction)
416 .serverError(serverErrorFunction)
417 .others(othersFunction)
418 .build();
419 }
420
421 private Function[] allFunctionsAsArray() {
422 return toArray(allFunctions, Function.class);
423 }
424
425 private void resetAllMocks() {
426 Mockito.reset(allFunctionsAsArray());
427 responseSettableFuture = SettableFuture.create();
428 }
429 }