1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 using System.Collections.ObjectModel; 5 using System.Globalization; 6 using System.Linq; 7 using System.Net; 8 using System.Net.Http; 9 using System.Net.Http.Formatting; 10 using System.Net.Http.Headers; 11 using System.Security.Principal; 12 using System.Threading; 13 using System.Threading.Tasks; 14 using System.Web.Http.Controllers; 15 using System.Web.Http.Dispatcher; 16 using System.Web.Http.Filters; 17 using System.Web.Http.ModelBinding; 18 using System.Web.Http.Routing; 19 using System.Web.Http.Services; 20 using Microsoft.TestCommon; 21 using Moq; 22 using Xunit; 23 using Assert = Microsoft.TestCommon.AssertEx; 24 25 namespace System.Web.Http 26 { 27 public class ApiControllerTest 28 { 29 private readonly HttpActionContext _actionContextInstance = ContextUtil.CreateActionContext(); 30 private readonly HttpConfiguration _configurationInstance = new HttpConfiguration(); 31 private readonly HttpActionDescriptor _actionDescriptorInstance = new Mock<HttpActionDescriptor>() { CallBase = true }.Object; 32 33 [Fact] Setting_CustomActionInvoker()34 public void Setting_CustomActionInvoker() 35 { 36 // Arrange 37 ApiController api = new UsersController(); 38 string responseText = "Hello World"; 39 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(); 40 41 HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 42 controllerContext.ControllerDescriptor = controllerDescriptor; 43 44 Mock<IHttpActionInvoker> mockInvoker = new Mock<IHttpActionInvoker>(); 45 mockInvoker 46 .Setup(invoker => invoker.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())) 47 .Returns(() => 48 { 49 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); 50 tcs.TrySetResult(new HttpResponseMessage() { Content = new StringContent(responseText) }); 51 return tcs.Task; 52 }); 53 controllerDescriptor.HttpActionInvoker = mockInvoker.Object; 54 55 // Act 56 HttpResponseMessage message = api.ExecuteAsync( 57 controllerContext, 58 CancellationToken.None).Result; 59 60 // Assert 61 Assert.Equal(responseText, message.Content.ReadAsStringAsync().Result); 62 } 63 64 [Fact] Setting_CustomActionSelector()65 public void Setting_CustomActionSelector() 66 { 67 // Arrange 68 ApiController api = new UsersController(); 69 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(); 70 71 HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 72 controllerContext.ControllerDescriptor = controllerDescriptor; 73 74 Mock<IHttpActionSelector> mockSelector = new Mock<IHttpActionSelector>(); 75 mockSelector 76 .Setup(invoker => invoker.SelectAction(It.IsAny<HttpControllerContext>())) 77 .Returns(() => 78 { 79 Func<HttpResponseMessage> testDelegate = 80 () => new HttpResponseMessage { Content = new StringContent("This is a test") }; 81 return new ReflectedHttpActionDescriptor 82 { 83 Configuration = controllerContext.Configuration, 84 ControllerDescriptor = controllerDescriptor, 85 MethodInfo = testDelegate.Method 86 }; 87 }); 88 controllerDescriptor.HttpActionSelector = mockSelector.Object; 89 90 // Act 91 HttpResponseMessage message = api.ExecuteAsync( 92 controllerContext, 93 CancellationToken.None).Result; 94 95 // Assert 96 Assert.Equal("This is a test", message.Content.ReadAsStringAsync().Result); 97 } 98 99 [Fact] Default_Get()100 public void Default_Get() 101 { 102 // Arrange 103 ApiController api = new UsersController(); 104 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Get }); 105 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 106 107 // Act 108 HttpResponseMessage message = api.ExecuteAsync( 109 controllerContext, 110 CancellationToken.None).Result; 111 112 // Assert 113 Assert.Equal("Default User", message.Content.ReadAsStringAsync().Result); 114 } 115 116 [Fact] Default_Post()117 public void Default_Post() 118 { 119 // Arrange 120 ApiController api = new UsersController(); 121 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Post }); 122 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 123 124 // Act 125 HttpResponseMessage message = api.ExecuteAsync( 126 controllerContext, 127 CancellationToken.None).Result; 128 129 // Assert 130 Assert.Equal("User Posted", message.Content.ReadAsStringAsync().Result); 131 } 132 133 [Fact] Default_Put()134 public void Default_Put() 135 { 136 // Arrange 137 ApiController api = new UsersController(); 138 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Put }); 139 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 140 141 // Act 142 HttpResponseMessage message = api.ExecuteAsync( 143 controllerContext, 144 CancellationToken.None).Result; 145 146 // Assert 147 Assert.Equal("User Updated", message.Content.ReadAsStringAsync().Result); 148 } 149 150 [Fact] Default_Delete()151 public void Default_Delete() 152 { 153 // Arrange 154 ApiController api = new UsersController(); 155 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, request: new HttpRequestMessage() { Method = HttpMethod.Delete }); 156 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersController)); 157 158 // Act 159 HttpResponseMessage message = api.ExecuteAsync( 160 controllerContext, 161 CancellationToken.None).Result; 162 163 // Assert 164 Assert.Equal("User Deleted", message.Content.ReadAsStringAsync().Result); 165 } 166 167 [Fact] Route_ActionName()168 public void Route_ActionName() 169 { 170 // Arrange 171 ApiController api = new UsersRpcController(); 172 HttpRouteData route = new HttpRouteData(new HttpRoute()); 173 route.Values.Add("action", "Admin"); 174 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Post }); 175 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController)); 176 177 // Act 178 HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result; 179 User user = message.Content.ReadAsAsync<User>().Result; 180 181 // Assert 182 Assert.Equal("Yao", user.FirstName); 183 Assert.Equal("Huang", user.LastName); 184 } 185 186 [Fact] Route_Get_Action_With_Route_Parameters()187 public void Route_Get_Action_With_Route_Parameters() 188 { 189 // Arrange 190 ApiController api = new UsersRpcController(); 191 HttpRouteData route = new HttpRouteData(new HttpRoute()); 192 route.Values.Add("action", "EchoUser"); 193 route.Values.Add("firstName", "RouteFirstName"); 194 route.Values.Add("lastName", "RouteLastName"); 195 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Post }); 196 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController)); 197 198 // Act 199 HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result; 200 User user = message.Content.ReadAsAsync<User>().Result; 201 202 // Assert 203 Assert.Equal("RouteFirstName", user.FirstName); 204 Assert.Equal("RouteLastName", user.LastName); 205 } 206 207 [Fact] Route_Get_Action_With_Query_Parameters()208 public void Route_Get_Action_With_Query_Parameters() 209 { 210 // Arrange 211 ApiController api = new UsersRpcController(); 212 HttpRouteData route = new HttpRouteData(new HttpRoute()); 213 route.Values.Add("action", "EchoUser"); 214 215 Uri requestUri = new Uri("http://localhost/?firstName=QueryFirstName&lastName=QueryLastName"); 216 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() 217 { 218 Method = HttpMethod.Post, 219 RequestUri = requestUri 220 }); 221 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController)); 222 223 // Act 224 HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result; 225 User user = message.Content.ReadAsAsync<User>().Result; 226 227 // Assert 228 Assert.Equal("QueryFirstName", user.FirstName); 229 Assert.Equal("QueryLastName", user.LastName); 230 } 231 232 [Fact] Route_Post_Action_With_Content_Parameter()233 public void Route_Post_Action_With_Content_Parameter() 234 { 235 // Arrange 236 ApiController api = new UsersRpcController(); 237 HttpRouteData route = new HttpRouteData(new HttpRoute()); 238 route.Values.Add("action", "EchoUserObject"); 239 User postedUser = new User() 240 { 241 FirstName = "SampleFirstName", 242 LastName = "SampleLastName" 243 }; 244 245 HttpRequestMessage request = new HttpRequestMessage() { Method = HttpMethod.Post }; 246 247 // Create a serialized request because this test directly calls the controller 248 // which would have normally been working with a serialized request content. 249 string serializedUserAsString = null; 250 using (HttpRequestMessage tempRequest = new HttpRequestMessage() { Content = new ObjectContent<User>(postedUser, new XmlMediaTypeFormatter()) }) 251 { 252 serializedUserAsString = tempRequest.Content.ReadAsStringAsync().Result; 253 } 254 255 StringContent stringContent = new StringContent(serializedUserAsString); 256 stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/xml"); 257 request.Content = stringContent; 258 259 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: request); 260 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "test", typeof(UsersRpcController)); 261 262 // Act 263 HttpResponseMessage message = api.ExecuteAsync( 264 controllerContext, 265 CancellationToken.None).Result; 266 User user = message.Content.ReadAsAsync<User>().Result; 267 268 // Assert 269 Assert.Equal(postedUser.FirstName, user.FirstName); 270 Assert.Equal(postedUser.LastName, user.LastName); 271 } 272 273 [Fact] Invalid_Action_In_Route()274 public void Invalid_Action_In_Route() 275 { 276 // Arrange 277 ApiController api = new UsersController(); 278 HttpRouteData route = new HttpRouteData(new HttpRoute()); 279 string actionName = "invalidOp"; 280 route.Values.Add("action", actionName); 281 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(instance: api, routeData: route, request: new HttpRequestMessage() { Method = HttpMethod.Get }); 282 Type controllerType = typeof(UsersController); 283 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, controllerType.Name, controllerType); 284 285 // Act & Assert 286 var exception = Assert.Throws<HttpResponseException>(() => 287 { 288 HttpResponseMessage message = api.ExecuteAsync(controllerContext, CancellationToken.None).Result; 289 }); 290 291 Assert.Equal(HttpStatusCode.NotFound, exception.Response.StatusCode); 292 var content = Assert.IsType<ObjectContent<string>>(exception.Response.Content); 293 Assert.Equal("No action was found on the controller 'UsersController' that matches the name 'invalidOp'.", 294 content.Value); 295 } 296 297 [Fact] ExecuteAsync_InvokesAuthorizationFilters_ThenInvokesModelBinding_ThenInvokesActionFilters_ThenInvokesAction()298 public void ExecuteAsync_InvokesAuthorizationFilters_ThenInvokesModelBinding_ThenInvokesActionFilters_ThenInvokesAction() 299 { 300 List<string> log = new List<string>(); 301 Mock<ApiController> controllerMock = new Mock<ApiController>() { CallBase = true }; 302 var controllerContextMock = new Mock<HttpControllerContext>(); 303 304 Mock<IActionValueBinder> binderMock = new Mock<IActionValueBinder>(); 305 Mock<HttpActionBinding> actionBindingMock = new Mock<HttpActionBinding>(); 306 actionBindingMock.Setup(b => b.ExecuteBindingAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())).Returns(() => Task.Factory.StartNew(() => { log.Add("model binding"); })); 307 binderMock.Setup(b => b.GetBinding(It.IsAny<HttpActionDescriptor>())).Returns(actionBindingMock.Object); 308 HttpConfiguration configuration = new HttpConfiguration(); 309 310 HttpControllerContext controllerContext = controllerContextMock.Object; 311 controllerContext.Configuration = configuration; 312 controllerContext.ControllerDescriptor = new HttpControllerDescriptor(configuration, "test", typeof(object)); 313 var actionFilterMock = CreateActionFilterMock((ac, ct, cont) => 314 { 315 log.Add("action filters"); 316 return cont(); 317 }); 318 var authFilterMock = CreateAuthorizationFilterMock((ac, ct, cont) => 319 { 320 log.Add("auth filters"); 321 return cont(); 322 }); 323 324 var selectorMock = new Mock<IHttpActionSelector>(); 325 326 Mock<HttpActionDescriptor> actionDescriptorMock = new Mock<HttpActionDescriptor>(); 327 actionDescriptorMock.Setup(ad => ad.ActionBinding).Returns(actionBindingMock.Object); 328 actionDescriptorMock.Setup(ad => ad.GetFilterPipeline()). 329 Returns(new Collection<FilterInfo>(new List<FilterInfo>() { new FilterInfo(actionFilterMock.Object, FilterScope.Action), new FilterInfo(authFilterMock.Object, FilterScope.Action) })); 330 331 selectorMock.Setup(s => s.SelectAction(controllerContext)).Returns(actionDescriptorMock.Object); 332 333 ApiController controller = controllerMock.Object; 334 var invokerMock = new Mock<IHttpActionInvoker>(); 335 invokerMock.Setup(i => i.InvokeActionAsync(It.IsAny<HttpActionContext>(), It.IsAny<CancellationToken>())) 336 .Returns(() => Task.Factory.StartNew(() => 337 { 338 log.Add("action"); 339 return new HttpResponseMessage(); 340 })); 341 controllerContext.ControllerDescriptor.HttpActionInvoker = invokerMock.Object; 342 controllerContext.ControllerDescriptor.HttpActionSelector = selectorMock.Object; 343 controllerContext.ControllerDescriptor.ActionValueBinder = binderMock.Object; 344 345 var task = controller.ExecuteAsync(controllerContext, CancellationToken.None); 346 347 Assert.NotNull(task); 348 task.WaitUntilCompleted(); 349 Assert.Equal(new string[] { "auth filters", "model binding", "action filters", "action" }, log.ToArray()); 350 } 351 352 [Fact] GetFilters_QueriesFilterProvidersFromServices()353 public void GetFilters_QueriesFilterProvidersFromServices() 354 { 355 // Arrange 356 Mock<DefaultServices> servicesMock = new Mock<DefaultServices> { CallBase = true }; 357 Mock<IFilterProvider> filterProviderMock = new Mock<IFilterProvider>(); 358 servicesMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new object[] { filterProviderMock.Object }).Verifiable(); 359 _configurationInstance.Services = servicesMock.Object; 360 361 HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object; 362 actionDescriptorMock.Configuration = _configurationInstance; 363 364 // Act 365 actionDescriptorMock.GetFilterPipeline(); 366 367 // Assert 368 servicesMock.Verify(); 369 } 370 371 [Fact] GetFilters_UsesFilterProvidersToGetFilters()372 public void GetFilters_UsesFilterProvidersToGetFilters() 373 { 374 // Arrange 375 Mock<DefaultServices> servicesMock = new Mock<DefaultServices> { CallBase = true }; 376 Mock<IFilterProvider> filterProviderMock = new Mock<IFilterProvider>(); 377 servicesMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new[] { filterProviderMock.Object }); 378 _configurationInstance.Services = servicesMock.Object; 379 380 HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object; 381 actionDescriptorMock.Configuration = _configurationInstance; 382 383 // Act 384 actionDescriptorMock.GetFilterPipeline().ToList(); 385 386 // Assert 387 filterProviderMock.Verify(fp => fp.GetFilters(_configurationInstance, actionDescriptorMock)); 388 } 389 390 [Fact] RequestPropertyGetterSetterWorks()391 public void RequestPropertyGetterSetterWorks() 392 { 393 Assert.Reflection.Property(new Mock<ApiController>().Object, 394 c => c.Request, expectedDefaultValue: null, allowNull: false, 395 roundTripTestValue: new HttpRequestMessage()); 396 } 397 398 [Fact] ConfigurationPropertyGetterSetterWorks()399 public void ConfigurationPropertyGetterSetterWorks() 400 { 401 Assert.Reflection.Property(new Mock<ApiController>().Object, 402 c => c.Configuration, expectedDefaultValue: null, allowNull: false, 403 roundTripTestValue: new HttpConfiguration()); 404 } 405 406 [Fact] ModelStatePropertyGetterWorks()407 public void ModelStatePropertyGetterWorks() 408 { 409 // Arrange 410 ApiController controller = new Mock<ApiController>().Object; 411 412 // Act 413 ModelStateDictionary expected = new ModelStateDictionary(); 414 expected.Add("a", new ModelState() { Value = new ValueProviders.ValueProviderResult("result", "attempted", CultureInfo.InvariantCulture) }); 415 416 controller.ModelState.Add("a", new ModelState() { Value = new ValueProviders.ValueProviderResult("result", "attempted", CultureInfo.InvariantCulture) }); 417 418 // Assert 419 Assert.Equal(expected.Count, controller.ModelState.Count); 420 } 421 422 // TODO: Move these tests to ActionDescriptorTest 423 [Fact] GetFilters_OrdersFilters()424 public void GetFilters_OrdersFilters() 425 { 426 // Arrange 427 HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object; 428 actionDescriptorMock.Configuration = _configurationInstance; 429 430 var globalFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Global); 431 var actionFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Action); 432 var controllerFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Controller); 433 Mock<DefaultServices> servicesMock = BuildFilterProvidingServicesMock(_configurationInstance, actionDescriptorMock, globalFilter, actionFilter, controllerFilter); 434 _configurationInstance.Services = servicesMock.Object; 435 436 // Act 437 var result = actionDescriptorMock.GetFilterPipeline().ToArray(); 438 439 // Assert 440 Assert.Equal(new[] { globalFilter, controllerFilter, actionFilter }, result); 441 } 442 443 [Fact] GetFilters_RemovesDuplicateUniqueFiltersKeepingMostSpecificScope()444 public void GetFilters_RemovesDuplicateUniqueFiltersKeepingMostSpecificScope() 445 { 446 // Arrange 447 HttpActionDescriptor actionDescriptorMock = new Mock<HttpActionDescriptor>() { CallBase = true }.Object; 448 actionDescriptorMock.Configuration = _configurationInstance; 449 450 var multiActionFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Action); 451 var multiGlobalFilter = new FilterInfo(new TestMultiFilter(), FilterScope.Global); 452 var uniqueControllerFilter = new FilterInfo(new TestUniqueFilter(), FilterScope.Controller); 453 var uniqueActionFilter = new FilterInfo(new TestUniqueFilter(), FilterScope.Action); 454 Mock<DefaultServices> servicesMock = BuildFilterProvidingServicesMock( 455 _configurationInstance, actionDescriptorMock, 456 multiActionFilter, multiGlobalFilter, uniqueControllerFilter, uniqueActionFilter); 457 _configurationInstance.Services = servicesMock.Object; 458 459 // Act 460 var result = actionDescriptorMock.GetFilterPipeline().ToArray(); 461 462 // Assert 463 Assert.Equal(new[] { multiGlobalFilter, multiActionFilter, uniqueActionFilter }, result); 464 } 465 466 [Fact] InvokeActionWithActionFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation()467 public void InvokeActionWithActionFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation() 468 { 469 // Arrange 470 List<string> log = new List<string>(); 471 Mock<IActionFilter> globalFilterMock = CreateActionFilterMock((ctx, ct, continuation) => 472 { 473 log.Add("globalFilter"); 474 return continuation(); 475 }); 476 Mock<IActionFilter> actionFilterMock = CreateActionFilterMock((ctx, ct, continuation) => 477 { 478 log.Add("actionFilter"); 479 return continuation(); 480 }); 481 Func<Task<HttpResponseMessage>> innerAction = () => Task<HttpResponseMessage>.Factory.StartNew(() => 482 { 483 log.Add("innerAction"); 484 return null; 485 }); 486 List<IActionFilter> filters = new List<IActionFilter>() { 487 globalFilterMock.Object, 488 actionFilterMock.Object, 489 }; 490 491 // Act 492 var result = ApiController.InvokeActionWithActionFilters(_actionContextInstance, CancellationToken.None, filters, innerAction); 493 494 // Assert 495 Assert.NotNull(result); 496 var resultTask = result(); 497 Assert.NotNull(resultTask); 498 resultTask.WaitUntilCompleted(); 499 Assert.Equal(new[] { "globalFilter", "actionFilter", "innerAction" }, log.ToArray()); 500 globalFilterMock.Verify(); 501 actionFilterMock.Verify(); 502 } 503 504 [Fact] InvokeActionWithAuthorizationFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation()505 public void InvokeActionWithAuthorizationFilters_ChainsFiltersInOrderFollowedByInnerActionContinuation() 506 { 507 // Arrange 508 List<string> log = new List<string>(); 509 Mock<IAuthorizationFilter> globalFilterMock = CreateAuthorizationFilterMock((ctx, ct, continuation) => 510 { 511 log.Add("globalFilter"); 512 return continuation(); 513 }); 514 Mock<IAuthorizationFilter> actionFilterMock = CreateAuthorizationFilterMock((ctx, ct, continuation) => 515 { 516 log.Add("actionFilter"); 517 return continuation(); 518 }); 519 Func<Task<HttpResponseMessage>> innerAction = () => Task<HttpResponseMessage>.Factory.StartNew(() => 520 { 521 log.Add("innerAction"); 522 return null; 523 }); 524 List<IAuthorizationFilter> filters = new List<IAuthorizationFilter>() { 525 globalFilterMock.Object, 526 actionFilterMock.Object, 527 }; 528 529 // Act 530 var result = ApiController.InvokeActionWithAuthorizationFilters(_actionContextInstance, CancellationToken.None, filters, innerAction); 531 532 // Assert 533 Assert.NotNull(result); 534 var resultTask = result(); 535 Assert.NotNull(resultTask); 536 resultTask.WaitUntilCompleted(); 537 Assert.Equal(new[] { "globalFilter", "actionFilter", "innerAction" }, log.ToArray()); 538 globalFilterMock.Verify(); 539 actionFilterMock.Verify(); 540 } 541 542 [Fact] InvokeActionWithExceptionFilters_IfActionTaskIsSuccessful_ReturnsSuccessTask()543 public void InvokeActionWithExceptionFilters_IfActionTaskIsSuccessful_ReturnsSuccessTask() 544 { 545 // Arrange 546 List<string> log = new List<string>(); 547 var response = new HttpResponseMessage(); 548 var actionTask = TaskHelpers.FromResult(response); 549 var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) => 550 { 551 log.Add("exceptionFilter"); 552 return Task.Factory.StartNew(() => { }); 553 }); 554 var filters = new[] { exceptionFilterMock.Object }; 555 556 // Act 557 var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters); 558 559 // Assert 560 Assert.NotNull(result); 561 result.WaitUntilCompleted(); 562 Assert.Equal(TaskStatus.RanToCompletion, result.Status); 563 Assert.Same(response, result.Result); 564 Assert.Equal(new string[] { }, log.ToArray()); 565 } 566 567 [Fact] InvokeActionWithExceptionFilters_IfActionTaskIsCanceled_ReturnsCanceledTask()568 public void InvokeActionWithExceptionFilters_IfActionTaskIsCanceled_ReturnsCanceledTask() 569 { 570 // Arrange 571 List<string> log = new List<string>(); 572 var actionTask = TaskHelpers.Canceled<HttpResponseMessage>(); 573 var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) => 574 { 575 log.Add("exceptionFilter"); 576 return Task.Factory.StartNew(() => { }); 577 }); 578 var filters = new[] { exceptionFilterMock.Object }; 579 580 // Act 581 var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters); 582 583 // Assert 584 Assert.NotNull(result); 585 result.WaitUntilCompleted(); 586 Assert.Equal(TaskStatus.Canceled, result.Status); 587 Assert.Equal(new string[] { }, log.ToArray()); 588 } 589 590 [Fact] InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsFaultedTaskIfNotHandled()591 public void InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsFaultedTaskIfNotHandled() 592 { 593 // Arrange 594 List<string> log = new List<string>(); 595 var exception = new Exception(); 596 var actionTask = TaskHelpers.FromError<HttpResponseMessage>(exception); 597 Exception exceptionSeenByFilter = null; 598 var exceptionFilterMock = CreateExceptionFilterMock((ec, ct) => 599 { 600 exceptionSeenByFilter = ec.Exception; 601 log.Add("exceptionFilter"); 602 return Task.Factory.StartNew(() => { }); 603 }); 604 var filters = new[] { exceptionFilterMock.Object }; 605 606 // Act 607 var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters); 608 609 // Assert 610 Assert.NotNull(result); 611 result.WaitUntilCompleted(); 612 Assert.Equal(TaskStatus.Faulted, result.Status); 613 Assert.Same(exception, result.Exception.InnerException); 614 Assert.Same(exception, exceptionSeenByFilter); 615 Assert.Equal(new string[] { "exceptionFilter" }, log.ToArray()); 616 } 617 618 [Fact] InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsResultIfHandled()619 public void InvokeActionWithExceptionFilters_IfActionTaskIsFaulted_ExecutesFiltersAndReturnsResultIfHandled() 620 { 621 // Arrange 622 List<string> log = new List<string>(); 623 var exception = new Exception(); 624 var actionTask = TaskHelpers.FromError<HttpResponseMessage>(exception); 625 HttpResponseMessage globalFilterResponse = new HttpResponseMessage(); 626 HttpResponseMessage actionFilterResponse = new HttpResponseMessage(); 627 HttpResponseMessage resultSeenByGlobalFilter = null; 628 var globalFilterMock = CreateExceptionFilterMock((ec, ct) => 629 { 630 log.Add("globalFilter"); 631 resultSeenByGlobalFilter = ec.Response; 632 ec.Response = globalFilterResponse; 633 return Task.Factory.StartNew(() => { }); 634 }); 635 var actionFilterMock = CreateExceptionFilterMock((ec, ct) => 636 { 637 log.Add("actionFilter"); 638 ec.Response = actionFilterResponse; 639 return Task.Factory.StartNew(() => { }); 640 }); 641 var filters = new[] { globalFilterMock.Object, actionFilterMock.Object }; 642 643 // Act 644 var result = ApiController.InvokeActionWithExceptionFilters(actionTask, _actionContextInstance, CancellationToken.None, filters); 645 646 // Assert 647 Assert.NotNull(result); 648 result.WaitUntilCompleted(); 649 Assert.Equal(TaskStatus.RanToCompletion, result.Status); 650 Assert.Same(globalFilterResponse, result.Result); 651 Assert.Same(actionFilterResponse, resultSeenByGlobalFilter); 652 Assert.Equal(new string[] { "actionFilter", "globalFilter" }, log.ToArray()); 653 } 654 655 [Fact, RestoreThreadPrincipal] User_ReturnsThreadPrincipal()656 public void User_ReturnsThreadPrincipal() 657 { 658 // Arrange 659 ApiController controller = new Mock<ApiController>().Object; 660 IPrincipal principal = new GenericPrincipal(new GenericIdentity("joe"), new string[0]); 661 Thread.CurrentPrincipal = principal; 662 663 // Act 664 IPrincipal result = controller.User; 665 666 // Assert 667 Assert.Same(result, principal); 668 } 669 670 [Fact] ApiControllerCannotBeReused()671 public void ApiControllerCannotBeReused() 672 { 673 // Arrange 674 var config = new HttpConfiguration(); 675 var singletonController = new Mock<ApiController> { CallBase = true }.Object; 676 var mockDescriptor = new Mock<HttpControllerDescriptor>(config, "MyMock", singletonController.GetType()) { CallBase = true }; 677 mockDescriptor.Setup(d => d.CreateController(It.IsAny<HttpRequestMessage>())).Returns(singletonController); 678 var mockSelector = new Mock<DefaultHttpControllerSelector>(config) { CallBase = true }; 679 mockSelector.Setup(s => s.SelectController(It.IsAny<HttpRequestMessage>())).Returns(mockDescriptor.Object); 680 config.Routes.MapHttpRoute("default", "", new { controller = "MyMock" }); 681 config.Services.Replace(typeof(IHttpControllerSelector), mockSelector.Object); 682 var server = new HttpServer(config); 683 var invoker = new HttpMessageInvoker(server); 684 685 // Act 686 HttpResponseMessage response1 = invoker.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://localhost/"), CancellationToken.None).Result; 687 HttpResponseMessage response2 = invoker.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://localhost/"), CancellationToken.None).Result; 688 689 // Assert 690 Assert.NotEqual(HttpStatusCode.InternalServerError, response1.StatusCode); 691 Assert.Equal(HttpStatusCode.InternalServerError, response2.StatusCode); 692 Assert.Contains("Cannot reuse an 'ApiController' instance. 'ApiController' has to be constructed per incoming message.", response2.Content.ReadAsStringAsync().Result); 693 } 694 695 [Fact] ApiControllerPutsSelfInRequestResourcesToBeDisposed()696 public void ApiControllerPutsSelfInRequestResourcesToBeDisposed() 697 { 698 // Arrange 699 var config = new HttpConfiguration(); 700 config.Routes.MapHttpRoute("default", "", new { controller = "SpyDispose" }); 701 var server = new HttpServer(config); 702 var invoker = new HttpMessageInvoker(server); 703 var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/"); 704 invoker.SendAsync(request, CancellationToken.None).WaitUntilCompleted(); 705 706 // Act 707 request.DisposeRequestResources(); 708 709 // Assert 710 Assert.True(SpyDisposeController.DisposeWasCalled); 711 } 712 CreateAuthorizationFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation)713 private Mock<IAuthorizationFilter> CreateAuthorizationFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation) 714 { 715 Mock<IAuthorizationFilter> filterMock = new Mock<IAuthorizationFilter>(); 716 filterMock.Setup(f => f.ExecuteAuthorizationFilterAsync(It.IsAny<HttpActionContext>(), 717 CancellationToken.None, 718 It.IsAny<Func<Task<HttpResponseMessage>>>())) 719 .Returns(implementation) 720 .Verifiable(); 721 return filterMock; 722 } 723 CreateActionFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation)724 private Mock<IActionFilter> CreateActionFilterMock(Func<HttpActionContext, CancellationToken, Func<Task<HttpResponseMessage>>, Task<HttpResponseMessage>> implementation) 725 { 726 Mock<IActionFilter> filterMock = new Mock<IActionFilter>(); 727 filterMock.Setup(f => f.ExecuteActionFilterAsync(It.IsAny<HttpActionContext>(), 728 CancellationToken.None, 729 It.IsAny<Func<Task<HttpResponseMessage>>>())) 730 .Returns(implementation) 731 .Verifiable(); 732 return filterMock; 733 } 734 CreateExceptionFilterMock(Func<HttpActionExecutedContext, CancellationToken, Task> implementation)735 private Mock<IExceptionFilter> CreateExceptionFilterMock(Func<HttpActionExecutedContext, CancellationToken, Task> implementation) 736 { 737 Mock<IExceptionFilter> filterMock = new Mock<IExceptionFilter>(); 738 filterMock.Setup(f => f.ExecuteExceptionFilterAsync(It.IsAny<HttpActionExecutedContext>(), 739 CancellationToken.None)) 740 .Returns(implementation) 741 .Verifiable(); 742 return filterMock; 743 } 744 BuildFilterProvidingServicesMock(HttpConfiguration configuration, HttpActionDescriptor action, params FilterInfo[] filters)745 private static Mock<DefaultServices> BuildFilterProvidingServicesMock(HttpConfiguration configuration, HttpActionDescriptor action, params FilterInfo[] filters) 746 { 747 var servicesMock = new Mock<DefaultServices> { CallBase = true }; 748 var filterProviderMock = new Mock<IFilterProvider>(); 749 servicesMock.Setup(r => r.GetServices(typeof(IFilterProvider))).Returns(new[] { filterProviderMock.Object }); 750 filterProviderMock.Setup(fp => fp.GetFilters(configuration, action)).Returns(filters); 751 return servicesMock; 752 } 753 754 /// <summary> 755 /// Simple IFilter implementation with AllowMultiple = true 756 /// </summary> 757 public class TestMultiFilter : IFilter 758 { 759 public bool AllowMultiple 760 { 761 get { return true; } 762 } 763 } 764 765 /// <summary> 766 /// Simple IFilter implementation with AllowMultiple = false 767 /// </summary> 768 public class TestUniqueFilter : IFilter 769 { 770 public bool AllowMultiple 771 { 772 get { return false; } 773 } 774 } 775 } 776 777 public class SpyDisposeController : System.Web.Http.ApiController 778 { 779 public static bool DisposeWasCalled = false; 780 SpyDisposeController()781 public SpyDisposeController() 782 { 783 } 784 Get()785 public void Get() { } 786 Dispose(bool disposing)787 protected override void Dispose(bool disposing) 788 { 789 DisposeWasCalled = true; 790 base.Dispose(disposing); 791 } 792 } 793 } 794