1 package com.atlassian.sal.core.net;
2
3 import java.io.IOException;
4 import java.net.SocketException;
5 import java.util.concurrent.atomic.AtomicInteger;
6
7 import com.atlassian.sal.api.net.Request.MethodType;
8 import com.atlassian.sal.api.net.ResponseConnectTimeoutException;
9 import com.atlassian.sal.api.net.ResponseException;
10 import com.atlassian.sal.api.net.ResponseHandler;
11 import com.atlassian.sal.api.net.ResponseProtocolException;
12 import com.atlassian.sal.api.net.ResponseReadTimeoutException;
13 import com.atlassian.sal.api.net.ResponseStatusException;
14 import com.atlassian.sal.api.net.ResponseTransportException;
15 import com.atlassian.sal.api.user.UserManager;
16 import com.atlassian.sal.core.net.auth.HttpClientAuthenticator;
17 import com.atlassian.sal.core.trusted.CertificateFactory;
18 import com.atlassian.security.auth.trustedapps.EncryptedCertificate;
19
20 import org.apache.commons.httpclient.ConnectTimeoutException;
21 import org.apache.commons.httpclient.Header;
22 import org.apache.commons.httpclient.HostConfiguration;
23 import org.apache.commons.httpclient.HttpClient;
24 import org.apache.commons.httpclient.HttpException;
25 import org.apache.commons.httpclient.HttpMethod;
26 import org.apache.commons.httpclient.NoHttpResponseException;
27 import org.apache.commons.httpclient.methods.GetMethod;
28 import org.apache.commons.httpclient.methods.PostMethod;
29 import org.apache.commons.httpclient.methods.PutMethod;
30 import org.apache.commons.httpclient.params.HttpClientParams;
31 import org.easymock.classextension.EasyMock;
32 import org.easymock.classextension.IMocksControl;
33 import org.junit.After;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.mockito.Mock;
37 import org.mockito.Mockito;
38 import org.mockito.runners.MockitoJUnitRunner;
39
40 import junit.framework.TestCase;
41
42 import static org.easymock.EasyMock.isA;
43 import static org.hamcrest.Matchers.hasItemInArray;
44 import static org.junit.Assert.assertThat;
45 import static org.mockito.Mockito.mock;
46 import static org.mockito.Mockito.times;
47 import static org.mockito.Mockito.when;
48
49 @RunWith (MockitoJUnitRunner.class)
50 public class TestHttpClientRequest extends TestCase
51 {
52 @Mock
53 private CertificateFactory certificateFactory;
54 @Mock
55 private UserManager userManager;
56 @Mock
57 private HttpClient httpClient;
58 @Mock
59 private ResponseHandler responseHandler;
60 @Mock
61 private PutMethod putMethod;
62 @Mock
63 private PostMethod postMethod;
64 @Mock
65 private GetMethod getMethod;
66 @Mock
67 private EncryptedCertificate encryptedCertificate;
68
69
70 @After
71 public void tearDown()
72 {
73 System.clearProperty(HttpClientParams.MAX_REDIRECTS);
74 }
75
76 @Test
77 public void testAuthentication() throws IOException, ResponseException
78 {
79 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
80 final HttpClient mockHttpClient = httpClientMockControl.createMock(HttpClient.class);
81 httpClientMockControl.replay();
82
83
84
85 final AtomicInteger authenticatorCounter = new AtomicInteger(0);
86
87
88 HttpClientRequest request = new HttpClientRequest(mockHttpClient, MethodType.GET, "http://url",
89 mock(CertificateFactory.class), mock(UserManager.class));
90
91
92 final HttpClientAuthenticator authenticator = new HttpClientAuthenticator()
93 {
94 public void process(HttpClient httpClient, HttpMethod method)
95 {
96 assertEquals("It should use mockClient", httpClient, mockHttpClient);
97 assertTrue("We asked it for GetMethod", method instanceof GetMethod);
98 authenticatorCounter.addAndGet(1);
99 }
100 };
101
102
103 request.addAuthentication(authenticator);
104 request.addAuthentication(authenticator);
105
106
107 request.execute(EasyMock.createMock(ResponseHandler.class));
108
109
110 assertEquals("Two authenticator should be called.", 2, authenticatorCounter.intValue());
111 }
112
113 @Test
114 public void verifyThatTrustedAuthentcationUsesAbsoluteUrl() throws ResponseException
115 {
116
117 String targetUrl = "http://url/abc";
118
119 String id = "1";
120 when(encryptedCertificate.getID()).thenReturn(id);
121
122
123 when(certificateFactory.createCertificate("barry", targetUrl)).thenReturn(encryptedCertificate);
124
125
126 HttpClientRequestFactory httpClientRequestFactory = new HttpClientRequestFactory(certificateFactory, userManager);
127 HttpClientRequest request = httpClientRequestFactory.createRequest(MethodType.GET, targetUrl);
128
129
130 request.addTrustedTokenAuthentication("barry");
131
132
133 HttpClientAuthenticator httpClientAuthenticator = request.getAuthenticators().get(0);
134
135
136 HttpMethod httpMethod = new GetMethod();
137 httpClientAuthenticator.process(httpClient, httpMethod);
138
139
140 assertThat(httpMethod.getRequestHeaders(), hasItemInArray(new Header("X-Seraph-Trusted-App-ID", id)));
141 }
142
143 @Test(expected = IllegalArgumentException.class)
144 public void verifyThatRelativeUrlCannotBeUsed() throws ResponseException
145 {
146
147 String targetUrl = "/abc";
148
149 String id = "1";
150 when(encryptedCertificate.getID()).thenReturn(id);
151
152
153 when(certificateFactory.createCertificate("barry", targetUrl)).thenReturn(encryptedCertificate);
154
155
156 HttpClientRequestFactory httpClientRequestFactory = new HttpClientRequestFactory(certificateFactory, userManager);
157 httpClientRequestFactory.createRequest(MethodType.GET, targetUrl);
158 }
159
160 @Test
161 public void testConnectTimeout() throws IOException, ResponseException
162 {
163
164 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
165 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
166 httpClientMock.executeMethod(isA(GetMethod.class));
167 httpClientMockControl.andThrow(new ConnectTimeoutException("errordescription"));
168 httpClientMockControl.replay();
169
170 HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
171
172 try
173 {
174 request.execute(EasyMock.createMock(ResponseHandler.class));
175 fail("Should throw ResponseConnectTimeoutException");
176 }
177 catch (ResponseConnectTimeoutException e)
178 {
179
180 assertEquals(ConnectTimeoutException.class, e.getCause().getClass());
181 assertEquals("errordescription", e.getMessage());
182 }
183 }
184
185 @Test
186 public void testReadTimeout() throws IOException, ResponseException
187 {
188
189 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
190 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
191 httpClientMock.executeMethod(isA(GetMethod.class));
192 httpClientMockControl.andThrow(new NoHttpResponseException("errordescription"));
193 httpClientMockControl.replay();
194
195 HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
196
197 try
198 {
199 request.execute(EasyMock.createMock(ResponseHandler.class));
200 fail("Should throw ResponseConnectTimeoutException");
201 }
202 catch (ResponseReadTimeoutException e)
203 {
204
205 assertEquals(NoHttpResponseException.class, e.getCause().getClass());
206 assertEquals("errordescription", e.getMessage());
207 }
208 }
209
210 @Test
211 public void testHttpException() throws IOException, ResponseException
212 {
213
214 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
215 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
216 httpClientMock.executeMethod(isA(GetMethod.class));
217 httpClientMockControl.andThrow(new HttpException("errordescription"));
218 httpClientMockControl.replay();
219
220 HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
221
222 try
223 {
224 request.execute(EasyMock.createMock(ResponseHandler.class));
225 fail("Should throw ResponseProtocolException");
226 }
227 catch (ResponseProtocolException e)
228 {
229
230 assertEquals(HttpException.class, e.getCause().getClass());
231 assertEquals("errordescription", e.getMessage());
232 }
233 }
234
235 @Test
236 public void testSocketException() throws IOException, ResponseException
237 {
238
239 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
240 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
241 httpClientMock.executeMethod(isA(GetMethod.class));
242 httpClientMockControl.andThrow(new SocketException("errordescription"));
243 httpClientMockControl.replay();
244
245 HttpClientRequest request = createMockRequest(httpClientMock, MethodType.GET, "http://url");
246
247 try
248 {
249 request.execute(EasyMock.createMock(ResponseHandler.class));
250 fail("Should throw ResponseTransportException");
251 }
252 catch (ResponseTransportException e)
253 {
254
255 assertEquals(SocketException.class, e.getCause().getClass());
256 assertEquals("errordescription", e.getMessage());
257 }
258 }
259
260 @Test
261 public void testStatusException() throws IOException, ResponseException
262 {
263
264 final IMocksControl mockControl = EasyMock.createNiceControl();
265 final GetMethod mockGetMethod = mockControl.createMock(GetMethod.class);
266 mockGetMethod.getStatusCode();
267 mockControl.andReturn(400);
268 mockControl.anyTimes();
269 mockControl.replay();
270
271
272 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
273 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
274 httpClientMock.executeMethod(mockGetMethod);
275 httpClientMockControl.andReturn(400);
276 httpClientMockControl.replay();
277
278 HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
279
280 try
281 {
282 request.execute();
283 fail("Should throw ResponseStatusException");
284 }
285 catch (ResponseStatusException e)
286 {
287
288 assertNotNull(e.getResponse());
289 assertEquals(400, e.getResponse().getStatusCode());
290 }
291 }
292
293 @Test
294 public void testMaxNumberOfRedirectionReached() throws IOException, ResponseException
295 {
296 final GetMethod mockGetMethod = mock(GetMethod.class);
297 when(mockGetMethod.getResponseHeader("location")).thenReturn(new Header("location", "http://someRedirectionUrl"));
298
299 final HttpClient httpClientMock = mock(HttpClient.class);
300 when(httpClientMock.getHostConfiguration()).thenReturn(new HostConfiguration());
301 when(httpClientMock.executeMethod(mockGetMethod)).thenReturn(302);
302
303 HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
304
305
306 try
307 {
308 request.execute(mock(ResponseHandler.class));
309 fail("Should throw ResponseProtocolException - maximum retries reached.");
310 }
311 catch (ResponseProtocolException e)
312 {
313
314 }
315
316
317
318 Mockito.verify(mockGetMethod, times(HttpClientRequest.MAX_REDIRECTS + 1)).getResponseHeader("location");
319 Mockito.verify(httpClientMock, times(HttpClientRequest.MAX_REDIRECTS + 1)).executeMethod(mockGetMethod);
320 }
321
322 @Test
323 public void testOverriddenMaxNumberOfRedirectionReached() throws IOException, ResponseException
324 {
325
326 int redirectsAllowed = 10;
327 System.setProperty(HttpClientParams.MAX_REDIRECTS, "" + redirectsAllowed);
328
329 final GetMethod mockGetMethod = mock(GetMethod.class);
330 when(mockGetMethod.getResponseHeader("location")).thenReturn(new Header("location", "http://someRedirectionUrl"));
331
332 final HttpClient httpClientMock = mock(HttpClient.class);
333 when(httpClientMock.getHostConfiguration()).thenReturn(new HostConfiguration());
334 when(httpClientMock.executeMethod(mockGetMethod)).thenReturn(302);
335
336 HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
337
338
339 try
340 {
341 request.execute(mock(ResponseHandler.class));
342 fail("Should throw ResponseProtocolException - maximum retries reached.");
343 }
344 catch (ResponseProtocolException e)
345 {
346
347 }
348
349
350
351 Mockito.verify(mockGetMethod, times(redirectsAllowed + 1)).getResponseHeader("location");
352 Mockito.verify(httpClientMock, times(redirectsAllowed + 1)).executeMethod(mockGetMethod);
353 }
354
355 @Test
356 public void testNoFollowRedirect() throws IOException
357 {
358
359 final IMocksControl mockControl = EasyMock.createNiceControl();
360 final GetMethod mockGetMethod = mockControl.createMock(GetMethod.class);
361 mockGetMethod.getResponseHeader("location");
362 mockControl.andReturn(new Header("location", "http://someRedirectionUrl")).anyTimes();
363 mockControl.replay();
364
365
366 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
367 final HttpClient httpClientMock = httpClientMockControl.createMock(HttpClient.class);
368 httpClientMock.executeMethod(mockGetMethod);
369 httpClientMockControl.andReturn(302).anyTimes();
370 httpClientMockControl.replay();
371
372 HttpClientRequest request = createMockRequest(httpClientMock, mockGetMethod, MethodType.GET, "http://url");
373 request.setFollowRedirects(false);
374
375
376 try
377 {
378 request.execute(EasyMock.createMock(ResponseHandler.class));
379
380 }
381 catch (ResponseException e)
382 {
383 fail(e.getMessage());
384 }
385
386
387 mockControl.verify();
388 }
389
390 @Test
391 public void testFollowRedirectForPostMethodsNotPossible() throws Exception
392 {
393
394 HttpClientRequest request = createMockRequest(MethodType.POST, "http://url");
395 try
396 {
397 request.setFollowRedirects(true);
398 fail("Should have thrown an exception because we can't follow redirects for Post methods");
399 } catch (IllegalStateException ex)
400 {
401
402 assertEquals("Entity enclosing requests cannot be redirected without user intervention!", ex.getMessage());
403 }
404 }
405
406 @Test
407 public void testFollowRedirectForPutMethodsNotPossible() throws Exception
408 {
409 HttpClientRequest request = createMockRequest(MethodType.PUT, "http://url");
410
411 try
412 {
413 request.setFollowRedirects(true);
414 fail("Should have thrown an exception because we can't follow redirects for Post methods");
415 } catch (IllegalStateException ex)
416 {
417
418 assertEquals("Entity enclosing requests cannot be redirected without user intervention!", ex.getMessage());
419 }
420 }
421
422 @Test
423 public void testExecutePostMethodNoFollowRedirects() throws Exception
424 {
425 final HttpClient httpClientMock = mock(HttpClient.class);
426 final PostMethod postMethod = mock(PostMethod.class);
427
428 HttpClientRequest request = createMockRequest(httpClientMock, postMethod, MethodType.POST, "http://url");
429
430 request.execute(new ResponseHandler<HttpClientResponse>(){
431 public void handle(final HttpClientResponse response) throws ResponseException
432 {
433
434 }
435 });
436 Mockito.verify(postMethod).setFollowRedirects(false);
437 }
438
439 @Test
440 public void testExecutePutMethodNoFollowRedirects() throws Exception
441 {
442 final HttpClient httpClientMock = mock(HttpClient.class);
443 final PutMethod putMethod = mock(PutMethod.class);
444
445 HttpClientRequest request = createMockRequest(httpClientMock, putMethod, MethodType.PUT, "http://url");
446
447 request.execute(new ResponseHandler<HttpClientResponse>(){
448 public void handle(final HttpClientResponse response) throws ResponseException
449 {
450
451 }
452 });
453 Mockito.verify(putMethod).setFollowRedirects(false);
454 }
455
456 @Test
457 public void testAddRequestParametersFails()
458 {
459
460 try
461 {
462 HttpClientRequest request = createMockRequest(MethodType.GET, "http://url");
463 request.addRequestParameters("doIt", "quickly!");
464 fail("Should throw exception that only the POST method can have parameters.");
465 }
466 catch (UnsupportedOperationException e)
467 {
468
469 }
470
471
472 try
473 {
474 HttpClientRequest request = createMockRequest(MethodType.PUT, "http://url");
475 request.addRequestParameters("Isaid", "doIt", "now");
476 fail("Should throw exception that only the POST method can have parameters.");
477 }
478 catch (UnsupportedOperationException e)
479 {
480
481 }
482
483
484 try
485 {
486 HttpClientRequest request = createMockRequest(MethodType.POST, "http://url");
487 request.addRequestParameters("doIt", "quickly!", "now");
488 fail("Should throw exception that You must enter an even number of arguments.");
489 }
490 catch (IllegalArgumentException e)
491 {
492
493 }
494 }
495
496 @Test
497 public void testAddRequestParameters() throws IOException, ResponseException
498 {
499
500 final IMocksControl mockControl = EasyMock.createNiceControl();
501 final PostMethod mockPostMethod = mockControl.createMock(PostMethod.class);
502 mockPostMethod.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
503 mockPostMethod.addParameter("a", "b");
504 mockPostMethod.addParameter("a", "b");
505 mockPostMethod.addParameter("a", "c");
506 mockPostMethod.addParameter("x", "y");
507 mockControl.replay();
508
509
510 final IMocksControl httpClientMockControl = EasyMock.createNiceControl();
511 final HttpClient mockHttpClient = httpClientMockControl.createMock(HttpClient.class);
512 httpClientMockControl.replay();
513
514
515
516 HttpClientRequest request = createMockRequest(mockHttpClient, mockPostMethod, MethodType.POST, "http://url");
517
518
519 request.addRequestParameters("a", "b", "a", "b", "a", "c", "x", "y");
520 request.execute(EasyMock.createMock(ResponseHandler.class));
521
522
523 mockControl.verify();
524 }
525
526 private HttpClientRequest createMockRequest(MethodType methodType, String url)
527 {
528 return createMockRequest(EasyMock.createMock(HttpClient.class), methodType, url);
529 }
530
531 private HttpClientRequest createMockRequest(HttpClient client, MethodType methodType, String url)
532 {
533 return new HttpClientRequest(client, methodType, url,
534 mock(CertificateFactory.class), mock(UserManager.class));
535 }
536
537 private HttpClientRequest createMockRequest(HttpClient client, final HttpMethod method, MethodType methodType,
538 String url)
539 {
540 return new HttpClientRequest(client, methodType, url, mock(CertificateFactory.class), mock(UserManager.class))
541 {
542 @Override
543 protected HttpMethod makeMethod()
544 {
545 return method;
546 }
547 };
548 }
549 }