Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(294)

Delta Between Two Patch Sets: Src/GoogleApis.Tests/Apis/Http/ConfigurableMessageHandlerTest.cs

Issue 12566043: Issue 369: Change default behavior of an HTTP request (Closed) Base URL: https://google-api-dotnet-client.googlecode.com/hg/
Left Patch Set: Created 10 years, 11 months ago
Right Patch Set: Miceli review Created 10 years, 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | Src/GoogleApis.Tests/Apis/Requests/ClientServiceRequestTest.cs » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /*
2 Copyright 2013 Google Inc
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
17 using System;
18 using System.Collections.Generic;
19 using System.IO;
20 using System.Linq;
21 using System.Net;
22 using System.Net.Http;
23 using System.Net.Http.Headers;
24 using System.Text;
25 using System.Threading;
26 using System.Threading.Tasks;
27
28 using NUnit.Framework;
29
30 using Google.Apis.Http;
31 using Google.Apis.Util;
32 using Google.Apis.Testing;
33
34 namespace Google.Apis.Tests.Apis.Http
35 {
36 /// <summary> Tests for <see cref="Google.Apis.Http.ConfigurableMessageHandl er"/>. </summary>
37 [TestFixture]
38 public class ConfigurableMessageHandlerTest
39 {
40 #region Handlers
41
42 /// <summary> Unsuccessful handler which always returns <c>true</c>. </s ummary>
43 private class TrueUnsuccessfulResponseHandler : IHttpUnsuccessfulRespons eHandler
44 {
45 public bool HandleResponse(HandleUnsuccessfulResponseArgs args)
46 {
47 return true;
48 }
49 }
50
51 /// <summary> Message handler which returns a new successful (and empty) response. </summary>
52 private class MockMessageHandler : HttpMessageHandler
53 {
54 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMe ssage request,
55 CancellationToken cancellationToken)
56 {
57 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompleti onSource<HttpResponseMessage>();
58 tcs.SetResult(new HttpResponseMessage());
59 return tcs.Task;
60 }
61 }
62
63 #endregion
64
65 #region Redirect
66
67 /// <summary> Redirect message handler which return redirect response. < /summary>
68 private class RedirectMessageHandler : CountableMessageHandler
69 {
70 /// <summary> Gets or sets the redirect location Uri string. </summa ry>
71 private string Location { get; set; }
72
73 /// <summary> Constructs a new redirect message handler with the giv en location. </summary>
74 public RedirectMessageHandler(string location)
75 {
76 Location = location;
77 }
78
79 protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsyncCore(
80 HttpRequestMessage request, CancellationToken cancellationToken)
81 {
82 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompleti onSource<HttpResponseMessage>();
83 var response = new HttpResponseMessage();
84
85 response.StatusCode = HttpStatusCode.Redirect;
86 response.Headers.Location = new Uri(Location Calls);
87 response.RequestMessage = request;
88
89 if (Calls == 1)
90 {
91 // First call the message should contain If-* headers
92 Assert.That(request.RequestUri, Is.EqualTo(new Uri(Location) ));
93 Assert.That(request.Headers.IfMatch.Count == 1);
94 Assert.That(request.Headers.IfNoneMatch.Count == 1);
95 Assert.That(request.Headers.IfModifiedSince.HasValue);
96 Assert.That(request.Headers.IfUnmodifiedSince.HasValue);
97 }
98 else
99 {
100 // After first call the message should not contain If-* head ers
101 Assert.That(request.RequestUri, Is.EqualTo(new Uri(Location (Calls - 1))));
102 Assert.That(request.Headers.IfMatch.Count == 0);
103 Assert.That(request.Headers.IfNoneMatch.Count == 0);
104 Assert.IsNull(request.Headers.IfModifiedSince);
105 Assert.IsNull(request.Headers.IfUnmodifiedSince);
106 }
107
108 tcs.SetResult(response);
109 return tcs.Task;
110 }
111 }
112
113 /// <summary> Tests that the message handler handles redirect messages s uccessfully. </summary>
114 [Test]
115 public void SendAsync_Redirect()
116 {
117 var location = "https://google.com";
118 var redirectHandler = new RedirectMessageHandler(location);
119 var configurableHanlder = new ConfigurableMessageHandler(redirectHan dler)
120 {
121 NumTries = 8
122 };
123 using (var client = new HttpClient(configurableHanlder))
124 {
125 var request = new HttpRequestMessage(HttpMethod.Get, location);
126 request.Headers.IfModifiedSince = new DateTimeOffset(DateTime.No w);
127 request.Headers.IfUnmodifiedSince = new DateTimeOffset(DateTime. Now);
128 request.Headers.IfMatch.Add(new EntityTagHeaderValue("\"a\""));
129 request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("\"b\"" ));
130
131 HttpResponseMessage response = client.SendAsync(request).Result;
132
133 Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redir ect));
134 Assert.That(response.Headers.Location, Is.EqualTo(new Uri(locati on configurableHanlder.NumTries)));
135 Assert.That(redirectHandler.Calls, Is.EqualTo(configurableHanlde r.NumTries));
136 }
137 }
138
139 /// <summary>·
140 /// Tests that the message handler doesn't handle redirect messages when follow redirect is <c>false</c>.·
141 /// </summary>
142 [Test]
143 public void SendAsync_Redirect_FollowRedirectFalse()
144 {
145 const int tries = 12;
146 var location = "https://google.com";
147 var redirectHandler = new RedirectMessageHandler(location);
148 var configurableHanlder = new ConfigurableMessageHandler(redirectHan dler)
149 {
150 NumTries = tries,
151 FollowRedirect = false
152 };
153 using (var client = new HttpClient(configurableHanlder))
154 {
155 var request = new HttpRequestMessage(HttpMethod.Get, location);
156 request.Headers.IfModifiedSince = new DateTimeOffset(DateTime.No w);
157 request.Headers.IfUnmodifiedSince = new DateTimeOffset(DateTime. Now);
158 request.Headers.IfMatch.Add(new EntityTagHeaderValue("\"a\""));
159 request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("\"b\"" ));
160
161 HttpResponseMessage response = client.SendAsync(request).Result;
162
163 // there should be only one request because follow redirect is f alse
164 Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redir ect));
165 Assert.That(response.Headers.Location, Is.EqualTo(new Uri(locati on 1)));
166 Assert.That(redirectHandler.Calls, Is.EqualTo(1));
167 }
168 }
169
170 #endregion
171
172 #region Execute interceptor
173
174 /// <summary>·
175 /// Mock interceptor handler which verifies that an interceptor is being called on a request.·
176 /// </summary>
177 private class InterceptorMessageHandler : CountableMessageHandler
178 {
179 /// <summary> Gets or sets an injected response message which will b e returned on send. </summary>
180 public HttpResponseMessage InjectedResponseMessage { get; set; }
181
182 const string InjectedHeader = "Some-Header";
183 const string InjectedValue = "123";
184
185 protected override Task<HttpResponseMessage> SendAsyncCore(HttpReque stMessage request,
186 CancellationToken cancellationToken)
187 {
188 Assert.That(request.Headers.GetValues(InjectedHeader).First(), I s.EqualTo(InjectedValue));
189
190 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompleti onSource<HttpResponseMessage>();
191 tcs.SetResult(InjectedResponseMessage);
192 return tcs.Task;
193 }
194
195 /// <summary> A mock interceptor which inject a header to a request. </summary>
196 internal class Interceptor : IHttpExecuteInterceptor
197 {
198 public int Calls { get; set; }
199
200 public void Intercept(HttpRequestMessage request)
201 {
202 Calls;
203 request.Headers.Add(InjectedHeader, InjectedValue);
204 }
205 }
206 }
207
208 /// <summary> Tests that execute interceptor is called on successful res ponse. </summary>
209 [Test]
210 public void SendAsync_ExecuteInterceptor()
211 {
212 SubtestSendAsyncExecuteInterceptor(HttpStatusCode.OK);
213 }
214
215 /// <summary>·
216 /// Tests that execute interceptor is called once on unsuccessful reques t. In this test unsuccessful response·
217 /// handler isn't plugged to the handler.·
218 /// </summary>
219 [Test]
220 public void SendAsync_ExecuteInterceptor_AbnormalResponse()
221 {
222 SubtestSendAsyncExecuteInterceptor(HttpStatusCode.BadRequest);
223 }
224
225 /// <summary> Tests that execute interceptor is called. </summary>
226 private void SubtestSendAsyncExecuteInterceptor(HttpStatusCode code)
227 {
228 var handler = new InterceptorMessageHandler();
229 handler.InjectedResponseMessage = new HttpResponseMessage()
230 {
231 StatusCode = code
232 };
233
234 var configurableHanlder = new ConfigurableMessageHandler(handler);
235 var interceptor = new InterceptorMessageHandler.Interceptor();
236 configurableHanlder.ExecuteInterceptors.Add(interceptor);
237
238 using (var client = new HttpClient(configurableHanlder))
239 {
240 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-execute-interceptor");
241
242 HttpResponseMessage response = client.SendAsync(request).Result;
243 Assert.That(interceptor.Calls, Is.EqualTo(1));
244 Assert.That(handler.Calls, Is.EqualTo(1));
245 }
246 }
247
248 /// <summary>·
249 /// Tests that execute interceptor is called for each request. In this c ase an unsuccessful response handler is·
250 /// plugged to the handler
251 /// </summary>
252 [Test]
253 public void SendAsync_ExecuteInterceptor_AbnormalResponse_UnsuccessfulRe sponseHandler()
254 {
255 var handler = new InterceptorMessageHandler();
256 handler.InjectedResponseMessage = new HttpResponseMessage()
257 {
258 StatusCode = HttpStatusCode.ServiceUnavailable
259 };
260
261 var configurableHanlder = new ConfigurableMessageHandler(handler);
262 var interceptor = new InterceptorMessageHandler.Interceptor();
263 configurableHanlder.ExecuteInterceptors.Add(interceptor);
264 configurableHanlder.UnsuccessfulResponseHandlers.Add(new TrueUnsucce ssfulResponseHandler());
265
266 using (var client = new HttpClient(configurableHanlder))
267 {
268 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-execute-interceptor");
269
270 HttpResponseMessage response = client.SendAsync(request).Result;
271 Assert.That(interceptor.Calls, Is.EqualTo(configurableHanlder.Nu mTries));
272 Assert.That(handler.Calls, Is.EqualTo(configurableHanlder.NumTri es));
273 }
274 }
275
276 #endregion
277
278 #region Unsuccessful reponse handler
279
280 /// <summary>·
281 /// Mock unsuccessful response handler which verifies that unsuccessful response handler is being called.
282 /// </summary>
283 private class UnsuccessfulResponseMessageHandler : CountableMessageHandl er
284 {
285 /// <summary> Gets or sets the status code to return on the response .</summary>
286 public HttpStatusCode ResponseStatusCode { get; set; }
287
288 /// <summary> Gets or sets the cancellation token source.</summary>
289 public CancellationTokenSource CancellationTokenSource { get; set; }
290
291 /// <summary>·
292 /// Gets or sets the request number to invoke the Cancel method on < see cref="CancellationTokenSource"/>.
293 /// </summary>
294 public int CancelRequestNum { get; set; }
295
296 protected override Task<HttpResponseMessage> SendAsyncCore(HttpReque stMessage request,
297 CancellationToken cancellationToken)
298 {
299 if (Calls == CancelRequestNum)
300 {
301 CancellationTokenSource.Cancel();
302 }
303
304 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompleti onSource<HttpResponseMessage>();
305 tcs.SetResult(new HttpResponseMessage { StatusCode = ResponseSta tusCode });
306 return tcs.Task;
307 }
308
309 /// <summary> Unsuccessful response handler which "handles" only ser vice unavailable responses. </summary>
310 internal class ServiceUnavailableResponseHandler : IHttpUnsuccessful ResponseHandler
311 {
312 public int Calls { get; set; }
313
314 public bool HandleResponse(HandleUnsuccessfulResponseArgs args)
315 {
316 Calls;
317 return args.Response.StatusCode.Equals(HttpStatusCode.Servic eUnavailable);
318 }
319 }
320 }
321
322 /// <summary> Test helper for testing unsuccessful response handlers. </ summary>
323 private void SubtestSendAsyncUnsuccessfulReponseHanlder(HttpStatusCode c ode)
324 {
325 var handler = new UnsuccessfulResponseMessageHandler { ResponseStatu sCode = code };
326
327 var configurableHanlder = new ConfigurableMessageHandler(handler);
328 var unsuccessfulHandler = new UnsuccessfulResponseMessageHandler.Ser viceUnavailableResponseHandler();
329 configurableHanlder.UnsuccessfulResponseHandlers.Add(unsuccessfulHan dler);
330
331 using (var client = new HttpClient(configurableHanlder))
332 {
333 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-unsuccessful-handler");
334
335 HttpResponseMessage response = client.SendAsync(request).Result;
336 Assert.That(response.StatusCode, Is.EqualTo(code));
337
338 // if service unavailable, retry will occur because we plugged u nsuccessful response handler which·
339 // handles service unavailable responses
340 if (code == HttpStatusCode.ServiceUnavailable)
341 {
342 Assert.That(unsuccessfulHandler.Calls, Is.EqualTo(configurab leHanlder.NumTries));
343 Assert.That(handler.Calls, Is.EqualTo(configurableHanlder.Nu mTries));
344 }
345 else
346 {
347 // if status is OK, there isn't any call to unsuccessful res ponse handler
348 Assert.That(unsuccessfulHandler.Calls, Is.EqualTo(code != Ht tpStatusCode.OK ? 1 : 0));
349 Assert.That(handler.Calls, Is.EqualTo(1));
350 }
351 }
352 }
353
354 /// <summary> Tests that unsuccessful response handler isn't called when the response is successful. </summary>
355 [Test]
356 public void SendAsync_UnsuccessfulReponseHanlder_SuccessfulReponse()
357 {
358 SubtestSendAsyncUnsuccessfulReponseHanlder(HttpStatusCode.OK);
359 }
360
361 /// <summary>·
362 /// Tests that unsuccessful response handler is called when the response is unsuccessful, but the handler can't
363 /// handle the abnormal response (e.g. different status code).
364 /// </summary>
365 [Test]
366 public void SendAsync_UnsuccessfulReponseHanlder_AbnormalResponse_Differ entStatusCode()
367 {
368 SubtestSendAsyncUnsuccessfulReponseHanlder(HttpStatusCode.BadGateway );
369 }
370
371 /// <summary>·
372 /// Tests that unsuccessful response handler is called when the response is unsuccessful and the handler can·
373 /// handle the abnormal response (e.g. same status code).
374 /// </summary>
375 [Test]
376 public void SendAsync_UnsuccessfulReponseHanlder_AbnormalResponse_SameSt atusCode()
377 {
378 SubtestSendAsyncUnsuccessfulReponseHanlder(HttpStatusCode.ServiceUna vailable);
379 }
380
381 /// <summary> Tests abnormal response when unsuccessful response handler isn't plugged. </summary>
382 [Test]
383 public void SendAsync_AbnormalResponse_WithoutUnsuccessfulReponseHandler ()
384 {
385 var handler = new UnsuccessfulResponseMessageHandler
386 {
387 ResponseStatusCode = HttpStatusCode.ServiceUnavailable
388 };
389
390 var configurableHanlder = new ConfigurableMessageHandler(handler);
391 using (var client = new HttpClient(configurableHanlder))
392 {
393 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-unsuccessful-handler");
394
395 HttpResponseMessage response = client.SendAsync(request).Result;
396 Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Servi ceUnavailable));
397 Assert.That(handler.Calls, Is.EqualTo(1));
398 }
399 }
400
401 #endregion
402
403 #region Exception Handler
404
405 /// <summary> Mock exception message handler which verifies that excepti on handler is being called. </summary>
406 private class ExceptionMessageHandler : CountableMessageHandler
407 {
408 public ExceptionMessageHandler()
409 {
410 Exception = new Exception(ExceptionMessage);
411 }
412
413 /// <summary> Gets or sets indication if exception should be thrown. </summary>
414 public bool ThrowException { get; set; }
415
416 /// <summary>·
417 /// Gets or sets a specific exception to throw. Default value is <se ealso cref="System.Exception"/>·
418 /// with <see cref="ExceptionMessage"/>. </summary>
419 public Exception Exception { get; set; }
420
421 /// <summary>·
422 /// The exception message which is thrown in case <see cref="ThrowEx ception"/> is <c>true</c>.·
423 /// </summary>
424 public const string ExceptionMessage = "Exception from execute";
425
426 protected override Task<HttpResponseMessage> SendAsyncCore(HttpReque stMessage request,
427 CancellationToken cancellationToken)
428 {
429 if (ThrowException)
430 {
431 throw Exception;
432 }
433
434 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompleti onSource<HttpResponseMessage>();
435 tcs.SetResult(new HttpResponseMessage());
436 return tcs.Task;
437 }
438
439 /// <summary> Mock Exception handler which "handles" the exception. </summary>
440 internal class ExceptionHandler : IHttpExceptionHandler
441 {
442 public int Calls { get; set; }
443 public bool Handle { get; set; }
444
445 public ExceptionHandler(bool handle = true)
446 {
447 Handle = handle;
448 }
449
450 public bool HandleException(HandleExceptionArgs args)
451 {
452 Calls;
453 return Handle;
454 }
455 }
456 }
457
458 /// <summary> Subtest for exception handler which tests that exception h andler is invoked. </summary>
459 private void SubtestSendAsyncExceptionHandler(bool throwException, bool handle)
460 {
461 var handler = new ExceptionMessageHandler { ThrowException = throwEx ception };
462
463 var configurableHanlder = new ConfigurableMessageHandler(handler);
464 var exceptionHandler = new ExceptionMessageHandler.ExceptionHandler { Handle = handle };
465 configurableHanlder.ExceptionHandlers.Add(exceptionHandler);
466
467 using (var client = new HttpClient(configurableHanlder))
468 {
469 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-exception-handler");
470 try
471 {
472 HttpResponseMessage response = client.SendAsync(request).Res ult;
473 if (throwException)
474 {
475 Assert.Fail("SendAsync should throw an exception");
476 }
477 }
478 catch (AggregateException ae)
479 {
480 Assert.That(ae.InnerException.Message, Is.EqualTo(ExceptionM essageHandler.ExceptionMessage));
481 }
482
483 // if exception is thrown, check if it's handles. if so, there s hould be num tries calls, otherwise
484 // only 1
485 if (throwException)
486 {
487 Assert.That(exceptionHandler.Calls, Is.EqualTo(handle ? conf igurableHanlder.NumTries : 1));
488 }
489 // exception wasn't supposed to be thrown, so no call to excepti on handler should be made
490 else
491 {
492 Assert.That(exceptionHandler.Calls, Is.EqualTo(0));
493 }
494
495 Assert.That(handler.Calls, Is.EqualTo(throwException & handle ? configurableHanlder.NumTries : 1));
496 }
497 }
498
499
500 /// <summary> Tests that the exception handler isn't called on successfu l response. </summary>
501 [Test]
502 public void SendAsync_ExceptionHandler_SuccessReponse()
503 {
504 SubtestSendAsyncExceptionHandler(false, true);
505 }
506
507 /// <summary>·
508 /// Tests that the exception handler is called when exception is thrown on execute, but it can't handle the·
509 /// exception.·
510 /// </summary>
511 [Test]
512 public void SendAsync_ExceptionHandler_ThrowException_DontHandle()
513 {
514 SubtestSendAsyncExceptionHandler(true, false);
515 }
516
517 /// <summary>·
518 /// Tests that the exception handler is called when exception is thrown on execute, and it handles the·
519 /// exception.·
520 /// </summary>
521 [Test]
522 public void SendAsync_ExceptionHandler_ThrowException_Handle()
523 {
524 SubtestSendAsyncExceptionHandler(true, true);
525 }
526
527 /// <summary> Tests an exception is thrown on execute and there is no ex ception handler. </summary>
528 [Test]
529 public void SendAsync_ThrowException_WithoutExceptionHandler()
530 {
531 var handler = new ExceptionMessageHandler { ThrowException = true };
532
533 var configurableHanlder = new ConfigurableMessageHandler(handler);
534
535 using (var client = new HttpClient(configurableHanlder))
536 {
537 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-exception-handler");
538 try
539 {
540 HttpResponseMessage response = client.SendAsync(request).Res ult;
541 Assert.Fail("SendAsync should throw an exception");
542 }
543 catch (AggregateException ae)
544 {
545 Assert.That(ae.InnerException.Message, Is.EqualTo(ExceptionM essageHandler.ExceptionMessage));
546 }
547 catch (Exception)
548 {
549 Assert.Fail("AggregateException was suppose to be thrown");
550 }
551 Assert.That(handler.Calls, Is.EqualTo(1));
552 }
553 }
554
555 #endregion
556
557 #region Back-off
558
559 #region Exception
560
561 /// <summary>·
562 /// Tests that back-off handler works as expected when exception is thro wn.·
563 /// Use default max time span (2 minutes).
564 /// </summary>
565 [Test]
566 public void SendAsync_BackOffExceptionHandler_Throw_Max2Minutes()
567 {
568 // create exponential back-off without delta interval, so expected s econds are exactly 1, 2, 4, 8, etc.
569 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero));
570 SubtestSendAsync_BackOffExceptionHandler(true, initializer);
571 }
572
573 /// <summary>·
574 /// Tests that back-off handler works as expected when exception is thro wn.·
575 /// Max time span is set to 200 milliseconds (as a result the back-off h andler can't handle the exception).
576 /// </summary>
577 [Test]
578 public void SendAsync_BackOffExceptionHandler_Throw_Max200Milliseconds()
579 {
580 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero))
581 {
582 MaxTimeSpan = TimeSpan.FromMilliseconds(200)
583 };
584 SubtestSendAsync_BackOffExceptionHandler(true, initializer);
585 }
586
587 /// <summary>·
588 /// Tests that back-off handler works as expected when exception is thro wn.·
589 /// Max time span is set to 1 hour.
590 /// </summary>
591 [Test]
592 public void SendAsync_BackOffExceptionHandler_Throw_Max1Hour()
593 {
863 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero))
595 {
596 MaxTimeSpan = TimeSpan.FromHours(1)
597 };
598 SubtestSendAsync_BackOffExceptionHandler(true, initializer);
599 }
600
601 /// <summary>·
602 /// Tests that back-off handler works as expected when·
603 /// <seealso cref="System.Threading.Tasks.TaskCanceledException"/>> is t hrown.·
604 /// </summary>
605 [Test]
606 public void SendAsync_BackOffExceptionHandler_ThrowCanceledException()
607 {
608 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero));
609 SubtestSendAsync_BackOffExceptionHandler(true, initializer, new Task CanceledException());
610 }
611
612 /// <summary>
613 /// Tests that back-off handler works as expected with the not defaulted exception handler.·
614 /// </summary>
615 [Test]
616 public void SendAsync_BackOffExceptionHandler_DifferentHandler()
617 {
618 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero));
619 initializer.HandleExceptionFunc = e => (e is InvalidCastException);
620 SubtestSendAsync_BackOffExceptionHandler(true, initializer, new Inva lidCastException());
621
622 initializer.HandleExceptionFunc = e => !(e is InvalidCastException);
623 SubtestSendAsync_BackOffExceptionHandler(true, initializer, new Inva lidCastException());
624 }
625
626 /// <summary> Tests that back-off handler works as expected when excepti on isn't thrown. </summary>
627 [Test]
628 public void SendAsync_BackOffExceptionHandler_DontThrow()
629 {
630 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero));
631 SubtestSendAsync_BackOffExceptionHandler(false, initializer);
632 }
633
634 /// <summary> Subtest that back-off handler works as expected when excep tion is or isn't thrown. </summary>
635 private void SubtestSendAsync_BackOffExceptionHandler(bool throwExceptio n,
636 BackOffHandler.Initializer initializer, Exception exceptionToThrow = null)
637 {
638 var handler = new ExceptionMessageHandler { ThrowException = throwEx ception };
639 if (exceptionToThrow != null)
640 {
641 handler.Exception = exceptionToThrow;
642 }
643
644 var configurableHanlder = new ConfigurableMessageHandler(handler);
645 var boHandler = new MockBackOffHandler(initializer);
646 configurableHanlder.ExceptionHandlers.Add(boHandler);
647
648 int boHandleCount = 0;
649 // if an exception should be thrown and the handler can handle it th en calculate the handle count by the·
650 // lg(MaxTimeSpan)
651 if (throwException && initializer.HandleExceptionFunc(exceptionToThr ow))
652 {
653 boHandleCount = Math.Min((int)Math.Floor(Math.Log(boHandler.MaxT imeSpan.TotalSeconds, 2)) 1,
654 configurableHanlder.NumTries - 1);
655 boHandleCount = boHandleCount >= 0 ? boHandleCount : 0;
656 }
657
658 using (var client = new HttpClient(configurableHanlder))
659 {
660 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-exception-handler");
661 try
662 {
663 HttpResponseMessage response = client.SendAsync(request).Res ult;
664 Assert.False(throwException);
665 }
666 catch (AggregateException ae)
667 {
668 Assert.True(throwException);
669 Assert.That(ae.InnerException.Message, Is.EqualTo(handler.Ex ception.Message));
670 }
671
672 Assert.That(boHandler.Waits.Count, Is.EqualTo(boHandleCount));
673 // check the exponential behavior - wait 1, 2, 4, 8, ... seconds .
674 if (throwException)
675 {
676 for (int i = 0; i < boHandler.Waits.Count; i)
677 {
678 Assert.That(boHandler.Waits[i].TotalSeconds, Is.EqualTo( (int)Math.Pow(2, i)));
679 }
680 }
681 Assert.That(handler.Calls, Is.EqualTo(boHandleCount 1));
682 }
683 }
684
685 #endregion
686
687 #region Unsuccessful Response Handler
688
689 /// <summary>·
690 /// Tests that back-off handler works as expected when the server return s 5xx and the maximum time span is set
691 /// to 5 seconds.
692 /// </summary>
693 [Test]
694 public void SendAsync_BackOffUnsuccessfulResponseHandler_ServiceUnavaila ble_Max5Seconds()
695 {
696 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero))
697 {
698 MaxTimeSpan = TimeSpan.FromSeconds(5)
699 };
700 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer);
701 }
702
703 /// <summary>·
704 /// Tests that back-off handler works as expected when the server return s 5xx and the maximum time span is set
705 /// to 10 hours.
706 /// </summary>
707 [Test]
708 public void SendAsync_BackOffUnsuccessfulResponseHandler_ServiceUnavaila ble_Max10Hours()
709 {
710 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero))
711 {
712 MaxTimeSpan = TimeSpan.FromHours(10)
713 };
714 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer);
715 }
716
717 /// <summary>·
718 /// Tests that back-off handler isn't be called when the server returns a successful response.
719 /// </summary>
720 [Test]
721 public void SendAsync_BackOffUnsuccessfulResponseHandler_OK()
722 {
723 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero));
724 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.O K, initializer);
725 }
726
727 /// <summary> Tests that back-off handler is canceled when cancellation token is used.</summary>
728 [Test]
1 public void SendAsync_BackOffUnsuccessfulResponseHandler_Cancel() 729 public void SendAsync_BackOffUnsuccessfulResponseHandler_Cancel()
2 { 730 {
3 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero)); 731 // test back-off with maximum 30 minutes per single request
732 var initializer = new BackOffHandler.Initializer(new ExponentialBack Off(TimeSpan.Zero))
733 {
734 MaxTimeSpan = TimeSpan.FromMinutes(30)
735 };
4 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer, 2); 736 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer, 2);
5 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer, 6); 737 SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpStatusCode.S erviceUnavailable, initializer, 6);
6 } 738 }
7 739
8 /// <summary>· 740 /// <summary>·
9 /// Subtest that back-off handler works as expected when a successful or abnormal response is returned. 741 /// Subtest that back-off handler works as expected when a successful or abnormal response is returned.
10 /// For testing the back-off handler in case of a canceled request, set the <code>cancelRequestNum</code> 742 /// For testing the back-off handler in case of a canceled request, set the <code>cancelRequestNum</code>
11 /// parameter to the index of the request you want to cancel. 743 /// parameter to the index of the request you want to cancel.
12 /// </summary> 744 /// </summary>
13 private void SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpSta tusCode statusCode, 745 private void SubtestSendAsync_BackOffUnsuccessfulResponseHandler(HttpSta tusCode statusCode,
14 BackOffHandler.Initializer initializer, int cancelRequestNum = 0) 746 BackOffHandler.Initializer initializer, int cancelRequestNum = 0, in t numTries = 10)
15 { 747 {
16 var handler = new UnsuccessfulResponseMessageHandler { ResponseStatu sCode = statusCode }; 748 var handler = new UnsuccessfulResponseMessageHandler { ResponseStatu sCode = statusCode };
17 749
750 CancellationToken cancellationToken = CancellationToken.None;
751 bool cancel = cancelRequestNum > 0;
752
753 if (cancel)
754 {
755 CancellationTokenSource tcs = new CancellationTokenSource();
756 handler.CancellationTokenSource = tcs;
757 handler.CancelRequestNum = cancelRequestNum;
758 cancellationToken = tcs.Token;
759 }
760
761 var configurableHanlder = new ConfigurableMessageHandler(handler)
762 {
763 NumTries = numTries
764 };
765 var boHandler = new MockBackOffHandler(initializer);
766 configurableHanlder.UnsuccessfulResponseHandlers.Add(boHandler);
767
768 int boHandleCount = 0;
769 if (initializer.HandleUnsuccessfulResponseFunc != null &&
770 initializer.HandleUnsuccessfulResponseFunc(new HttpResponseMessa ge { StatusCode = statusCode }))
771 {
772 boHandleCount = Math.Min((int)Math.Floor(Math.Log(boHandler.MaxT imeSpan.TotalSeconds, 2)) 1,
773 configurableHanlder.NumTries - 1);
774 boHandleCount = boHandleCount >= 0 ? boHandleCount : 0;
775 if (cancel)
776 {
777 boHandleCount = Math.Min(boHandleCount, cancelRequestNum);
778 }
779 }
780
781 using (var client = new HttpClient(configurableHanlder))
782 {
783 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-exception-handler");
784 try
785 {
786 HttpResponseMessage response = client.SendAsync(request, can cellationToken).Result;
787 Assert.False(cancel);
788 }
789 catch (AggregateException ae)
790 {
791 // a canceled request should throw an exception
792 Assert.IsInstanceOf<TaskCanceledException>(ae.InnerException );
793 Assert.True(cancel);
794 }
795
796 Assert.That(boHandler.Waits.Count, Is.EqualTo(boHandleCount));
797
798 // check the exponential behavior - wait 1, 2, 4, 8, ... seconds .
799 for (int i = 0; i < boHandler.Waits.Count; i)
800 {
801 Assert.That(boHandler.Waits[i].TotalSeconds, Is.EqualTo((int )Math.Pow(2, i)));
802 }
803
804 // if the request was canceled the number of calls to the messag e handler is equal to the number of·
805 // calls to back-off handler
806 Assert.That(handler.Calls, Is.EqualTo(boHandleCount (cancel ? 0 : 1)));
807 }
808 }
809
810 #endregion
811
812 #endregion
813
814 #region Content
815
816 /// <summary> Mock message handler which verifies that the content is co rrect on retry. </summary>
817 private class ContentMessageHandler : CountableMessageHandler
818 {
819 public const int NumFails = 4;
820 public string ReadContent;
821
822 protected override async Task<HttpResponseMessage> SendAsyncCore(Htt pRequestMessage request,
823 CancellationToken cancellationToken)
824 {
825 if (Calls < NumFails)
826 {
827 return new HttpResponseMessage() { StatusCode = HttpStatusCo de.ServiceUnavailable };
828 }
829
830 ReadContent = await request.Content.ReadAsStringAsync();
831 return new HttpResponseMessage();
832 }
833 }
834
835 /// <summary>·
836 /// Defines the different content types we test in <see cref="SubtestSen dAsyncRetryContent"/>.
837 /// </summary>
838 private enum ContentType
839 {
840 String,
841 Stream,
842 ByteArray
843 }
844
845 /// <summary> Tests that retry works with different kind of contents (St ring, Stream and ByteArray). </summary>
846 private void SubtestSendAsyncRetryContent(ContentType type)
847 {
848 var content = "test-content";
849 var contentHandler = new ContentMessageHandler();
850 var configurableHanlder = new ConfigurableMessageHandler(contentHand ler)
851 {
852 NumTries = 10
853 };
854 configurableHanlder.UnsuccessfulResponseHandlers.Add(new TrueUnsucce ssfulResponseHandler());
855 using (var client = new HttpClient(configurableHanlder))
856 {
857 var request = new HttpRequestMessage(HttpMethod.Put, "https://te st-unsuccessful-handler");
858 // set the right content
859 switch (type)
860 {
861 case ContentType.String:
862 request.Content = new StringContent(content);
863 break;
864 case ContentType.Stream:
865 {
866 var stream = new MemoryStream();
867 var buffer = Encoding.UTF8.GetBytes(content);
868 stream.Write(buffer, 0, buffer.Length);
869 stream.Position = 0;
870 request.Content = new StreamContent(stream);
871 }
872 break;
873 case ContentType.ByteArray:
874 request.Content = new ByteArrayContent(Encoding.UTF8.Get Bytes(content));
875 break;
876 }
877
878 HttpResponseMessage response = client.SendAsync(request).Result;
879 Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
880 Assert.That(contentHandler.Calls, Is.EqualTo(ContentMessageHandl er.NumFails));
881 Assert.That(contentHandler.ReadContent, Is.EqualTo(content));
882 }
883 }
884
885 /// <summary> Tests that a string content works as expected on retry. </ summary>
886 [Test]
887 public void SendAsync_Retry_CorrectStringContent()
888 {
889 SubtestSendAsyncRetryContent(ContentType.String);
890 }
891
892 /// <summary> Tests that a stream content works as expected on retry. </ summary>
893 [Test]
894 public void SendAsync_Retry_CorrectStreamContent()
895 {
896 SubtestSendAsyncRetryContent(ContentType.Stream);
897 }
898
899 /// <summary> Tests that a byte array content works as expected on retry . </summary>
900 [Test]
901 public void SendAsync_Retry_CorrectByteArrayContent()
902 {
903 SubtestSendAsyncRetryContent(ContentType.ByteArray);
904 }
905
906 #endregion
907
908 /// <summary> Tests setting number of tries. </summary>
909 [Test]
910 public void NumTries_Setter()
911 {
912 var configurableHanlder = new ConfigurableMessageHandler(new HttpCli entHandler());
913
914 // valid values
915 configurableHanlder.NumTries = ConfigurableMessageHandler.MaxAllowed NumTries;
916 configurableHanlder.NumTries = ConfigurableMessageHandler.MaxAllowed NumTries - 1;
917 configurableHanlder.NumTries = 1;
918
919 // test invalid values
920 try
921 {
922 configurableHanlder.NumTries = ConfigurableMessageHandler.MaxAll owedNumTries 1;
923 Assert.Fail();
924 }
925 catch (ArgumentOutOfRangeException ex)
926 {
927 Assert.True(ex.Message.Contains("Parameter name: NumTries"));
928 }
929 try
930 {
931 configurableHanlder.NumTries = 0;
932 Assert.Fail();
933 }
934 catch (ArgumentOutOfRangeException ex)
935 {
936 Assert.True(ex.Message.Contains("Parameter name: NumTries"));
937 }
938 try
939 {
940 configurableHanlder.NumTries = -2;
941 Assert.Fail();
942 }
943 catch (ArgumentOutOfRangeException ex)
944 {
945 Assert.True(ex.Message.Contains("Parameter name: NumTries"));
946 }
947 }
948
949 /// <summary>·
950 /// Tests the number of tries in case of unsuccessful response when unsu ccessful response handler is plugged to·
951 /// the message handler.·
952 /// </summary>
953 [Test]
954 public void SendAsync_NumTries()
955 {
956 SubtestSendAsyncNumTries(5, false);
957 SubtestSendAsyncNumTries(5);
958 SubtestSendAsyncNumTries(1);
959 SubtestSendAsyncNumTries(1, false);
960 SubtestSendAsyncNumTries(10);
961 SubtestSendAsyncNumTries(10, false);
962 }
963
964 /// <summary>
965 /// Tests the retry mechanism. In case the abnormal response is handled, there should be retries, but otherwise
966 /// there should not be any retry.
967 /// </summary>
968 /// <param name="numTries"></param>
969 /// <param name="handle"></param>
970 private void SubtestSendAsyncNumTries(int numTries, bool handle = true)
971 {
972 var handler = new UnsuccessfulResponseMessageHandler
973 {
974 ResponseStatusCode = HttpStatusCode.ServiceUnavailable
975 };
976 var configurableHanlder = new ConfigurableMessageHandler(handler)
977 {
978 NumTries = numTries
979 };
980 if (handle)
981 {
982 var unsuccessfulHandler = new UnsuccessfulResponseMessageHandler .ServiceUnavailableResponseHandler();
983 configurableHanlder.UnsuccessfulResponseHandlers.Add(unsuccessfu lHandler);
984 }
985
986 using (var client = new HttpClient(configurableHanlder))
987 {
988 client.GetAsync("http://num-retres");
989 Assert.That(handler.Calls, Is.EqualTo(handle ? numTries : 1));
990 }
991 }
992
993 /// <summary> Tests that the configurable message handler sets the User- Agent header. </summary>
994 [Test]
995 public void SendAsync_UserAgent()
996 {
997 var apiVersion = string.Format("google-api-dotnet-client/{0} (gzip)" , Utilities.GetLibraryVersion());
998 const string applicationName = "NO NAME";
999
1000 var handler = new MockMessageHandler();
1001 var configurableHanlder = new ConfigurableMessageHandler(handler);
1002
1003 using (var client = new HttpClient(configurableHanlder))
1004 {
1005 // without application name
1006 var request = new HttpRequestMessage(HttpMethod.Get, "https://te st-user-agent");
1007 HttpResponseMessage response = client.SendAsync(request).Result;
1008 var userAgent = string.Join(" ", request.Headers.GetValues("User -Agent").ToArray());
1009 Assert.That(userAgent, Is.EqualTo(apiVersion));
1010
1011 // with application name
1012 configurableHanlder.ApplicationName = applicationName;
1013 request = new HttpRequestMessage(HttpMethod.Get, "https://test-u ser-agent");
1014 response = client.SendAsync(request).Result;
1015 userAgent = string.Join(" ", request.Headers.GetValues("User-Age nt").ToArray());
1016 Assert.That(userAgent, Is.EqualTo(applicationName " " apiVer sion));
1017 }
1018 }
1019 }
1020 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b