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