LEFT | RIGHT |
| 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 } |
LEFT | RIGHT |