diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index 8c6b91df0a..b073c599ef 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -47,6 +47,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal _controllerContext = controllerContext; } + // Internal for testing + internal ControllerContext ControllerContext => _controllerContext; + protected override void ReleaseResources() { if (_controller != null && _cacheEntry.ControllerReleaser != null) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index 351ee0ea6b..c451c143fc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -30,2249 +30,118 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal { - public class ControllerActionInvokerTest + public class ControllerActionInvokerTest : CommonResourceInvokerTest { - // Intentionally choosing an uncommon exception type. - private readonly Exception _actionException = new DivideByZeroException(); - - private readonly ContentResult _result = new ContentResult() { Content = "Hello, world!" }; - - private readonly TestController _controller = new TestController(); - [Fact] - public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow() + public async Task Invoke_Success_LogsCorrectValues() { // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnException(It.IsAny())) - .Verifiable(); + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var logger = loggerFactory.CreateLogger(); - var invoker = CreateInvoker(filter.Object, actionThrows: false); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnException(It.IsAny()), Times.Never()); - } - - [Fact] - public async Task InvokeAction_DoesNotAsyncInvokeExceptionFilter_WhenActionDoesNotThrow() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object, actionThrows: false); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Never()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_WhenActionThrows() - { - // Arrange - Exception exception = null; - IActionResult resultFromAction = null; - var expected = new Mock(MockBehavior.Strict); - expected - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnException(It.IsAny())) - .Verifiable(); - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - exception = context.Exception; - resultFromAction = context.Result; - - // Handle the exception - context.Result = expected.Object; - }) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); - - Assert.Same(_actionException, exception); - Assert.Null(resultFromAction); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_WhenActionThrows() - { - // Arrange - Exception exception = null; - IActionResult resultFromAction = null; - var expected = new Mock(MockBehavior.Strict); - expected - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - exception = context.Exception; - resultFromAction = context.Result; - - // Handle the exception - context.Result = expected.Object; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - filter2.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Once()); - - Assert.Same(_actionException, exception); - Assert.Null(resultFromAction); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() - { - // Arrange - var filter1 = new Mock(MockBehavior.Strict); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - filter2.ToString(); - context.Exception = null; - }) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnException(It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - context.Result = result.Object; - context.Exception = null; - }) - .Verifiable(); - - // Result filters are never used when an exception bubbles up to exception filters. - var resultFilter = new Mock(MockBehavior.Strict); + var displayName = "A.B.C"; + var actionDescriptor = Mock.Of(a => a.DisplayName == displayName); + actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ActionMethod)); + actionDescriptor.ControllerTypeInfo = typeof(TestController).GetTypeInfo(); + actionDescriptor.FilterDescriptors = new List(); + actionDescriptor.Parameters = new List(); + actionDescriptor.BoundProperties = new List(); + var filter = Mock.Of(); var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); + new[] { filter }, + actionDescriptor, + controller: new TestController(), + logger: logger); // Act await invoker.InvokeAsync(); // Assert - filter2.Verify( - f => f.OnException(It.IsAny()), - Times.Once()); + Assert.Single(sink.Scopes); + Assert.Equal(displayName, sink.Scopes[0].Scope?.ToString()); - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + Assert.Equal(4, sink.Writes.Count); + Assert.Equal($"Executing action {displayName}", sink.Writes[0].State?.ToString()); + Assert.Equal($"Executing action method {displayName} with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString()); + Assert.Equal($"Executed action method {displayName}, returned result Microsoft.AspNetCore.Mvc.ContentResult.", sink.Writes[2].State?.ToString()); + // This message has the execution time embedded, which we don't want to verify. + Assert.StartsWith($"Executed action {displayName} ", sink.Writes[3].State?.ToString()); } [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() + public async Task Invoke_WritesDiagnostic_ActionSelected() { // Arrange - var filter1 = new Mock(MockBehavior.Strict); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - context.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnException(It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - context.Result = result.Object; - context.ExceptionHandled = true; - }) - .Verifiable(); - - // Result filters are never used when an exception bubbles up to exception filters. - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnException(It.IsAny()), - Times.Once()); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() - { - // Arrange - var filter1 = new Mock(MockBehavior.Strict); - var filter2 = new Mock(MockBehavior.Strict); - - filter2 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - context.Exception = null; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var filterMetadata = new IFilterMetadata[] { filter1.Object, filter2.Object }; - var invoker = CreateInvoker(filterMetadata, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - var filter2 = new Mock(MockBehavior.Strict); - - filter2 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - context.Exception = null; - context.Result = result.Object; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - // Result filters are never used when an exception bubbles up to exception filters. - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Once()); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() - { - // Arrange - var filter1 = new Mock(MockBehavior.Strict); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - context.ExceptionHandled = true; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - var filter2 = new Mock(MockBehavior.Strict); - - filter2 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - context.ExceptionHandled = true; - context.Result = result.Object; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - // Result filters are never used when an exception bubbles up to exception filters. - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter2.Verify( - f => f.OnExceptionAsync(It.IsAny()), - Times.Once()); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncExceptionFilter_SettingResultDoesNotShortCircuit() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnExceptionAsync(It.IsAny())) - .Callback(context => - { - context.Result = result.Object; - }) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(c => { }) // Does nothing, we just want to verify that it was called. - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnExceptionAsync(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_UnhandledExceptionIsThrown() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnException(It.IsAny())) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object, actionThrows: true); - - // Act - await Assert.ThrowsAsync(_actionException.GetType(), async () => await invoker.InvokeAsync()); - - // Assert - filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_ResultIsExecuted_WithoutResultFilters() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnException(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter.Object, resultFilter.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesExceptionFilter_SettingResultDoesNotShortCircuit() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnException(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnException(It.IsAny())) - .Callback(c => { }) // Does nothing, we just want to verify that it was called. - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnException(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAuthorizationFilter() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnAuthorization(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncAuthorizationFilter() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnAuthorizationAsync(It.IsAny())) - .Returns(context => Task.FromResult(true)) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify( - f => f.OnAuthorizationAsync(It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAuthorizationFilter_ShortCircuit() - { - // Arrange - var challenge = new Mock(MockBehavior.Strict); - challenge - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnAuthorization(It.IsAny())) - .Callback(c => Task.FromResult(true)) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnAuthorization(It.IsAny())) - .Callback(c => c.Result = challenge.Object) - .Verifiable(); - - var filter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object, filter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - challenge.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); - Assert.False(invoker.ControllerState.CreateCalled); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncAuthorizationFilter_ShortCircuit() - { - // Arrange - var challenge = new Mock(MockBehavior.Strict); - challenge - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnAuthorizationAsync(It.IsAny())) - .Returns((context) => - { - return Task.FromResult(true); - }) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnAuthorizationAsync(It.IsAny())) - .Returns((context) => - { - context.Result = challenge.Object; - return Task.FromResult(true); - }); - - var filter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - challenge.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - filter1.Verify( - f => f.OnAuthorizationAsync(It.IsAny()), - Times.Once()); - - Assert.False(invoker.ControllerState.CreateCalled); - } - - [Fact] - public async Task InvokeAction_ExceptionInAuthorizationFilter_CannotBeHandledByOtherFilters() - { - // Arrange - var expected = new InvalidCastException(); - - var exceptionFilter = new Mock(MockBehavior.Strict); - exceptionFilter - .Setup(f => f.OnException(It.IsAny())) - .Callback(context => - { - // Mark as handled - context.Result = new EmptyResult(); - }) - .Verifiable(); - - var authorizationFilter1 = new Mock(MockBehavior.Strict); - authorizationFilter1 - .Setup(f => f.OnAuthorization(It.IsAny())) - .Callback(c => { throw expected; }) - .Verifiable(); - - // None of these filters should run - var authorizationFilter2 = new Mock(MockBehavior.Strict); - var resourceFilter = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] + var actionDescriptor = new ControllerActionDescriptor() { - exceptionFilter.Object, - authorizationFilter1.Object, - authorizationFilter2.Object, - resourceFilter.Object, - actionFilter.Object, - resultFilter.Object, - }); + FilterDescriptors = new List(), + Parameters = new List(), + BoundProperties = new List(), + }; - // Act - var thrown = await Assert.ThrowsAsync(invoker.InvokeAsync); + actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ActionMethod)); + actionDescriptor.ControllerTypeInfo = typeof(TestController).GetTypeInfo(); - // Assert - Assert.Same(expected, thrown); - exceptionFilter.Verify(f => f.OnException(It.IsAny()), Times.Never()); - authorizationFilter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); - } + var listener = new TestDiagnosticListener(); - [Fact] - public async Task InvokeAction_InvokesAuthorizationFilter_ChallengeNotSeenByResultFilters() - { - // Arrange - var challenge = new Mock(MockBehavior.Strict); - challenge - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); + var routeData = new RouteData(); + routeData.Values.Add("tag", "value"); - var authorizationFilter = new Mock(MockBehavior.Strict); - authorizationFilter - .Setup(f => f.OnAuthorization(It.IsAny())) - .Callback(c => c.Result = challenge.Object) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object }); + var filter = Mock.Of(); + var invoker = CreateInvoker( + new[] { filter }, + actionDescriptor, + controller: new TestController(), + diagnosticListener: listener, + routeData: routeData); // Act await invoker.InvokeAsync(); // Assert - authorizationFilter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); - challenge.Verify(c => c.ExecuteResultAsync(It.IsAny()), Times.Once()); + Assert.NotNull(listener.BeforeAction?.ActionDescriptor); + Assert.NotNull(listener.BeforeAction?.HttpContext); + + var routeValues = listener.BeforeAction?.RouteData?.Values; + Assert.NotNull(routeValues); + + Assert.Equal(1, routeValues.Count); + Assert.Contains(routeValues, kvp => kvp.Key == "tag" && string.Equals(kvp.Value, "value")); } [Fact] - public async Task InvokeAction_InvokesActionFilter() + public async Task Invoke_WritesDiagnostic_ActionInvoked() { // Arrange - IActionResult result = null; - - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => result = c.Result) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - Assert.Same(_result, result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter() - { - // Arrange - IActionResult result = null; - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (context, next) => - { - var resultContext = await next(); - result = resultContext.Result; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - Assert.Same(_result, result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_ShortCircuit() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] + var actionDescriptor = new ControllerActionDescriptor() { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter.Object, - }); + FilterDescriptors = new List(), + Parameters = new List(), + BoundProperties = new List(), + }; - // Act - await invoker.InvokeAsync(); + actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ActionMethod)); + actionDescriptor.ControllerTypeInfo = typeof(TestController).GetTypeInfo(); - // Assert - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Same(context.Result, result.Object); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Notice we're not calling next - c.Result = result.Object; - return Task.FromResult(true); - }) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter1 = new Mock(MockBehavior.Strict); - resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - var resultFilter2 = new Mock(MockBehavior.Strict); - resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] - { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter1.Object, - resultFilter2.Object, - }); - - // Act - await invoker.InvokeAsync(); - - // Assert - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Same(context.Result, result.Object); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() - { - // Arrange - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Notice we're not calling next - return Task.FromResult(true); - }) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] - { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter.Object, - }); - - // Act - await invoker.InvokeAsync(); - - // Assert - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() - { - // Arrange - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - c.Result = new EmptyResult(); - await next(); - }) - .Verifiable(); - - var message = - "If an IAsyncActionFilter provides a result value by setting the Result property of " + - "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + - "ActionExecutionDelegate."; - - var invoker = CreateInvoker(actionFilter.Object); - - // Act & Assert - await ExceptionAssert.ThrowsAsync( - async () => await invoker.InvokeAsync(), - message); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() - { - // Arrange - ActionExecutedContext context = null; - - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - Assert.Same(_actionException, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() - { - // Arrange - var exception = new DataMisalignedException(); - ActionExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback(c => { throw exception; }) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); - - Assert.Same(exception, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() - { - // Arrange - var exception = new DataMisalignedException(); - ActionExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - - // Handle the exception so the test doesn't throw. - Assert.False(context.ExceptionHandled); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter2 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => { throw exception; }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - - Assert.Same(exception, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_HandleException() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - - c.Result = result.Object; - }) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + var listener = new TestDiagnosticListener(); + var filter = Mock.Of(); var invoker = CreateInvoker( - new IFilterMetadata[] { actionFilter.Object, resultFilter.Object }, - actionThrows: true); + new[] { filter }, + actionDescriptor, + controller: new TestController(), + diagnosticListener: listener); // Act await invoker.InvokeAsync(); // Assert - actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + Assert.NotNull(listener.AfterAction?.ActionDescriptor); + Assert.NotNull(listener.AfterAction?.HttpContext); } - [Fact] - public async Task InvokeAction_InvokesResultFilter() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (context, next) => await next()) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify( - f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesResultFilter_ShortCircuit_WithCancel() - { - // Arrange - ResultExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - filter1 - .Setup(f => f.OnResultExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Callback(c => - { - filter2.ToString(); - c.Cancel = true; - }) - .Verifiable(); - - var filter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - filter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Never()); - - Assert.True(context.Canceled); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithCancel() - { - // Arrange - ResultExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - filter1 - .Setup(f => f.OnResultExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Not calling next here - c.Cancel = true; - return Task.FromResult(true); - }) - .Verifiable(); - - var filter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - filter2.Verify( - f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - Assert.True(context.Canceled); - Assert.IsType(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel() - { - // Arrange - ResultExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - filter1 - .Setup(f => f.OnResultExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Not calling next here - return Task.FromResult(true); - }) - .Verifiable(); - - var filter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - filter2.Verify( - f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - Assert.True(context.Canceled); - Assert.IsType(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel_CallNext() - { - // Arrange - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - // Not calling next here - c.Cancel = true; - await next(); - }) - .Verifiable(); - - var message = - "If an IAsyncResultFilter cancels execution by setting the Cancel property of " + - "ResultExecutingContext to 'true', then it cannot call the next filter by invoking " + - "ResultExecutionDelegate."; - - var invoker = CreateInvoker(filter.Object); - - // Act & Assert - await ExceptionAssert.ThrowsAsync( - async () => await invoker.InvokeAsync(), - message); - } - - [Fact] - public async Task InvokeAction_InvokesResultFilter_ExceptionGoesUnhandled() - { - // Arrange - var exception = new DataMisalignedException(); - - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Throws(exception) - .Verifiable(); - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await Assert.ThrowsAsync(exception.GetType(), async () => await invoker.InvokeAsync()); - - // Assert - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - - filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResult() - { - // Arrange - ResultExecutedContext context = null; - var exception = new DataMisalignedException(); - - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Throws(exception) - .Verifiable(); - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - filter - .Setup(f => f.OnResultExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(exception, context.Exception); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - - filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResult() - { - // Arrange - ResultExecutedContext context = null; - var exception = new DataMisalignedException(); - - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Throws(exception) - .Verifiable(); - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - c.Result = result.Object; - - context = await next(); - - // Handle the exception - Assert.False(context.ExceptionHandled); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(exception, context.Exception); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - - filter.Verify( - f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResultFilter() - { - // Arrange - ResultExecutedContext context = null; - var exception = new DataMisalignedException(); - - var resultFilter1 = new Mock(MockBehavior.Strict); - resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter1 - .Setup(f => f.OnResultExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var resultFilter2 = new Mock(MockBehavior.Strict); - resultFilter2 - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Throws(exception) - .Verifiable(); - - var resultFilter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(exception, context.Exception); - - resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResultFilter() - { - // Arrange - ResultExecutedContext context = null; - var exception = new DataMisalignedException(); - - var resultFilter1 = new Mock(MockBehavior.Strict); - resultFilter1 - .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - - // Handle the exception - Assert.False(context.ExceptionHandled); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var resultFilter2 = new Mock(MockBehavior.Strict); - resultFilter2 - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Throws(exception) - .Verifiable(); - - var resultFilter3 = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(exception, context.Exception); - - resultFilter1.Verify( - f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter() - { - // Arrange - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - await next(); - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesResourceFilter() - { - // Arrange - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecuting(It.IsAny())) - .Verifiable(); - resourceFilter - .Setup(f => f.OnResourceExecuted(It.IsAny())) - .Verifiable(); - - var invoker = CreateInvoker(resourceFilter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - resourceFilter.Verify( - f => f.OnResourceExecuted(It.IsAny()), - Times.Once()); - resourceFilter.Verify( - f => f.OnResourceExecuted(It.IsAny()), - Times.Once()); - - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromAction() - { - // Arrange - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var invoker = CreateInvoker(resourceFilter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(_result, context.Result); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter() - { - // Arrange - var expected = Mock.Of(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback((c) => - { - c.Result = expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Result); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter() - { - // Arrange - var expected = Mock.Of(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var exceptionFilter = new Mock(MockBehavior.Strict); - exceptionFilter - .Setup(f => f.OnException(It.IsAny())) - .Callback((c) => - { - c.Result = expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Result); - Assert.Null(context.Exception); - Assert.Null(context.ExceptionDispatchInfo); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromResultFilter() - { - // Arrange - var expected = Mock.Of(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Loose); - resultFilter - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Callback((c) => - { - c.Result = expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Result); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromAction() - { - // Arrange - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(_actionException, context.Exception); - Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback((c) => - { - throw expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var exceptionFilter = new Mock(MockBehavior.Strict); - exceptionFilter - .Setup(f => f.OnException(It.IsAny())) - .Callback((c) => - { - throw expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Loose); - resultFilter - .Setup(f => f.OnResultExecuting(It.IsAny())) - .Callback((c) => - { - throw expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_BySettingNull() - { - // Arrange - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - - Assert.Same(_actionException, context.Exception); - Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException); - - context.Exception = null; - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_ThrowsUnhandledException() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resourceFilter2 = new Mock(MockBehavior.Strict); - resourceFilter2 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - throw expected; - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); - - // Act - var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); - - // Assert - Assert.Same(expected, exception); - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - } - - [Fact] - public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuting_ThrowsUnhandledException() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resourceFilter2 = new Mock(MockBehavior.Strict); - resourceFilter2 - .Setup(f => f.OnResourceExecuting(It.IsAny())) - .Callback((c) => - { - throw expected; - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); - - // Act - var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); - - // Assert - Assert.Same(expected, exception); - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - } - - [Fact] - public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuted_ThrowsUnhandledException() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resourceFilter2 = new Mock(MockBehavior.Loose); - resourceFilter2 - .Setup(f => f.OnResourceExecuted(It.IsAny())) - .Callback((c) => - { - throw expected; - }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); - - // Act - var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); - - // Assert - Assert.Same(expected, exception); - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit() - { - // Arrange - var expected = new Mock(MockBehavior.Strict); - expected - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resourceFilter2 = new Mock(MockBehavior.Strict); - resourceFilter2 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - c.Result = expected.Object; - return Task.FromResult(true); - }) - .Verifiable(); - - var resourceFilter3 = new Mock(MockBehavior.Strict); - var exceptionFilter = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 - resourceFilter2.Object, // This filter will short circuit - resourceFilter3.Object, // This shouldn't run - it will throw if it does - exceptionFilter.Object, // This shouldn't run - it will throw if it does - actionFilter.Object, // This shouldn't run - it will throw if it does - resultFilter.Object // This shouldn't run - it will throw if it does - }, - // The action won't run - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - Assert.Same(expected.Object, context.Result); - Assert.True(context.Canceled); - Assert.False(invoker.ControllerState.CreateCalled); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit_WithoutResult() - { - // Arrange - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var resourceFilter2 = new Mock(MockBehavior.Strict); - resourceFilter2 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - return Task.FromResult(true); - }) - .Verifiable(); - - var resourceFilter3 = new Mock(MockBehavior.Strict); - var exceptionFilter = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 - resourceFilter2.Object, // This filter will short circuit - resourceFilter3.Object, // This shouldn't run - it will throw if it does - exceptionFilter.Object, // This shouldn't run - it will throw if it does - actionFilter.Object, // This shouldn't run - it will throw if it does - resultFilter.Object // This shouldn't run - it will throw if it does - }, - // The action won't run - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Null(context.Result); - Assert.True(context.Canceled); - Assert.False(invoker.ControllerState.CreateCalled); - } - - [Fact] - public async Task InvokeAction_InvokesResourceFilter_ShortCircuit() - { - // Arrange - var expected = new Mock(MockBehavior.Strict); - expected - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ResourceExecutedContext context = null; - var resourceFilter1 = new Mock(MockBehavior.Strict); - resourceFilter1 - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }); - - var resourceFilter2 = new Mock(MockBehavior.Strict); - resourceFilter2 - .Setup(f => f.OnResourceExecuting(It.IsAny())) - .Callback((c) => - { - c.Result = expected.Object; - }); - - var resourceFilter3 = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); - var resultFilter = new Mock(MockBehavior.Strict); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 - resourceFilter2.Object, - resourceFilter3.Object, // This shouldn't run - it will throw if it does - actionFilter.Object, // This shouldn't run - it will throw if it does - resultFilter.Object // This shouldn't run - it will throw if it does - }, - // The action won't run - actionThrows: true); - - // Act - await invoker.InvokeAsync(); - - // Assert - expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - Assert.Same(expected.Object, context.Result); - Assert.True(context.Canceled); - Assert.False(invoker.ControllerState.CreateCalled); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_InvalidShortCircuit() - { - // Arrange - var message = - "If an IAsyncResourceFilter provides a result value by setting the Result property of " + - "ResourceExecutingContext to a non-null value, then it cannot call the next filter by invoking " + - "ResourceExecutionDelegate."; - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - // This is not valid. - c.Result = Mock.Of(); - context = await next(); - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, }); - - // Act - var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); - - // Assert - Assert.Equal(message, exception.Message); - } - - [Fact] - public async Task InvokeAction_AuthorizationFilter_ChallengePreventsResourceFiltersFromRunning() - { - // Arrange - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - await next(); - }) - .Verifiable(); - - var authorizationFilter = new Mock(MockBehavior.Strict); - authorizationFilter - .Setup(f => f.OnAuthorization(It.IsAny())) - .Callback((c) => - { - c.Result = _result; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resourceFilter.Object, }); - - // Act - await invoker.InvokeAsync(); - - // Assert - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Never()); - - Assert.False(invoker.ControllerState.CreateCalled); - } [Fact] public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext() @@ -2297,7 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal await invoker.InvokeAsync(); // Assert - var controllerContext = invoker.ControllerState.ControllerContext; + var controllerContext = Assert.IsType(invoker).ControllerContext; Assert.NotNull(controllerContext); Assert.Equal(2, controllerContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory1, controllerContext.ValueProviderFactories[0]); @@ -2315,6 +184,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { resourceExecutingContext.ValueProviderFactories.RemoveAt(0); }); + var valueProviderFactory1 = Mock.Of(); var valueProviderFactory2 = Mock.Of(); var valueProviderFactories = new List(); @@ -2328,28 +198,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal await invoker.InvokeAsync(); // Assert - var controllerContext = invoker.ControllerState.ControllerContext; + var controllerContext = Assert.IsType(invoker).ControllerContext; Assert.NotNull(controllerContext); Assert.Equal(1, controllerContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]); } - [Fact] - public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter() - { - // Arrange - var expected = 147; - var filter = new MockAuthorizationFilter(expected); - var invoker = CreateInvoker( - filter, - actionThrows: false, - maxAllowedErrorsInModelState: expected); - - // Act & Assert - // The authorization filter asserts if MaxAllowedErrors was set to the right value. - await invoker.InvokeAsync(); - } - [Fact] public async Task InvokeAction_AsyncAction_TaskReturnType() { @@ -2940,243 +794,101 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(5, context.Object.Items["Result"]); } - [Fact] - public async Task Invoke_Success_LogsCorrectValues() - { - // Arrange - var sink = new TestSink(); - var loggerFactory = new TestLoggerFactory(sink, enabled: true); - var logger = loggerFactory.CreateLogger(); - - var displayName = "A.B.C"; - var mockActionDescriptor = new Mock(); - mockActionDescriptor - .SetupGet(ad => ad.DisplayName) - .Returns(displayName); - var actionDescriptor = mockActionDescriptor.Object; - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); - actionDescriptor.FilterDescriptors = new List(); - actionDescriptor.Parameters = new List(); - actionDescriptor.BoundProperties = new List(); - - var filter = Mock.Of(); - var invoker = CreateInvoker( - new[] { filter }, - actionDescriptor, - controller: null, - logger: logger); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Single(sink.Scopes); - Assert.Equal(displayName, sink.Scopes[0].Scope?.ToString()); - - Assert.Equal(4, sink.Writes.Count); - Assert.Equal($"Executing action {displayName}", sink.Writes[0].State?.ToString()); - Assert.Equal($"Executing action method {displayName} with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString()); - Assert.Equal($"Executed action method {displayName}, returned result Microsoft.AspNetCore.Mvc.ContentResult.", sink.Writes[2].State?.ToString()); - // This message has the execution time embedded, which we don't want to verify. - Assert.StartsWith($"Executed action {displayName} ", sink.Writes[3].State?.ToString()); - } - - [Fact] - public async Task Invoke_WritesDiagnostic_ActionSelected() - { - // Arrange - var actionDescriptor = new ControllerActionDescriptor() - { - FilterDescriptors = new List(), - Parameters = new List(), - BoundProperties = new List(), - }; - - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); - - var listener = new TestDiagnosticListener(); - - var routeData = new RouteData(); - routeData.Values.Add("tag", "value"); - - var filter = Mock.Of(); - var invoker = CreateInvoker( - new[] { filter }, - actionDescriptor, - controller: null, - diagnosticListener: listener, - routeData: routeData); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.NotNull(listener.BeforeAction?.ActionDescriptor); - Assert.NotNull(listener.BeforeAction?.HttpContext); - - var routeValues = listener.BeforeAction?.RouteData?.Values; - Assert.NotNull(routeValues); - - Assert.Equal(1, routeValues.Count); - Assert.Contains(routeValues, kvp => kvp.Key == "tag" && string.Equals(kvp.Value, "value")); - } - - [Fact] - public async Task Invoke_WritesDiagnostic_ActionInvoked() - { - // Arrange - var actionDescriptor = new ControllerActionDescriptor() - { - FilterDescriptors = new List(), - Parameters = new List(), - BoundProperties = new List(), - }; - - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); - - var listener = new TestDiagnosticListener(); - - var filter = Mock.Of(); - var invoker = CreateInvoker( - new[] { filter }, - actionDescriptor, - controller: null, - diagnosticListener: listener); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.NotNull(listener.AfterAction?.ActionDescriptor); - Assert.NotNull(listener.AfterAction?.HttpContext); - } - - [Fact] - public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter() - { - // Arrange - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - var context = await next(); - Assert.Same(_actionException, context.Exception); - context.ExceptionHandled = true; - }); - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - await next(); - }); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - await next(); - }); - - var invoker = CreateInvoker( - new IFilterMetadata[] - { - resourceFilter.Object, - actionFilter1.Object, - actionFilter2.Object, - }, - // The action won't run - actionThrows: true); - - // Act & Assert - await invoker.InvokeAsync(); - } - - private TestControllerActionInvoker CreateInvoker( - IFilterMetadata filter, - bool actionThrows = false, - int maxAllowedErrorsInModelState = 200, - List valueProviderFactories = null) - { - return CreateInvoker(new[] { filter }, actionThrows, maxAllowedErrorsInModelState, valueProviderFactories); - } - - private TestControllerActionInvoker CreateInvoker( + protected override ResourceInvoker CreateInvoker( IFilterMetadata[] filters, - bool actionThrows = false, - int maxAllowedErrorsInModelState = 200, + Exception exception = null, List valueProviderFactories = null) { var actionDescriptor = new ControllerActionDescriptor() { + ControllerTypeInfo = typeof(TestController).GetTypeInfo(), FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), }; - if (actionThrows) + if (exception == Exception) { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ThrowingActionMethod)); + actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ThrowingActionMethod)); + } + else if (exception != null) + { + throw new InvalidOperationException($"Unexpected exception {exception}."); } else { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); + actionDescriptor.MethodInfo = typeof(TestController).GetMethod(nameof(TestController.ActionMethod)); } - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); - return CreateInvoker(filters, actionDescriptor, null, maxAllowedErrorsInModelState, valueProviderFactories); + return CreateInvoker( + filters, + actionDescriptor, + new TestController(), + valueProviderFactories: valueProviderFactories); } - private TestControllerActionInvoker CreateInvoker( + // Used by tests which directly test different types of signatures for controller methods. + private ControllerActionInvoker CreateInvoker( IFilterMetadata[] filters, string methodName, - IDictionary arguments, - int maxAllowedErrorsInModelState = 200) + IDictionary arguments) { var actionDescriptor = new ControllerActionDescriptor() { + ControllerTypeInfo = typeof(TestController).GetTypeInfo(), FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), - MethodInfo = typeof(TestController).GetMethod(methodName), - ControllerTypeInfo = typeof(TestController).GetTypeInfo(), }; - foreach (var argument in arguments) + var method = typeof(TestController).GetTypeInfo().GetMethod(methodName); + Assert.NotNull(method); + actionDescriptor.MethodInfo = method; + + foreach (var kvp in arguments) { - actionDescriptor.Parameters.Add(new ParameterDescriptor + actionDescriptor.Parameters.Add(new ControllerParameterDescriptor() { - Name = argument.Key, - ParameterType = argument.Value.GetType(), + Name = kvp.Key, + ParameterInfo = method.GetParameters().Where(p => p.Name == kvp.Key).Single(), }); } - var testControllerState = new TestControllerState(actionDescriptor, _controller, arguments); - return CreateInvoker(filters, actionDescriptor, _controller, maxAllowedErrorsInModelState, testControllerState: testControllerState); + return CreateInvoker(filters, actionDescriptor, new TestController(), arguments); } - private TestControllerActionInvoker CreateInvoker( + private ControllerActionInvoker CreateInvoker( IFilterMetadata[] filters, ControllerActionDescriptor actionDescriptor, object controller, - int maxAllowedErrorsInModelState = 200, - List valueProviderFactories = null, - TestControllerState testControllerState = null, + IDictionary arguments = null, + IList valueProviderFactories = null, RouteData routeData = null, ILogger logger = null, object diagnosticListener = null) { + Assert.NotNull(actionDescriptor.MethodInfo); + + if (arguments == null) + { + arguments = new Dictionary(); + } + + if (valueProviderFactories == null) + { + valueProviderFactories = new List(); + } + + if (routeData == null) + { + routeData = new RouteData(); + } + + if (logger == null) + { + logger = new NullLoggerFactory().CreateLogger(); + } + var httpContext = new DefaultHttpContext(); var options = new MvcOptions(); var mvcOptionsAccessor = new TestOptionsManager(options); @@ -3210,67 +922,59 @@ namespace Microsoft.AspNetCore.Mvc.Internal options.OutputFormatters.Add(formatter.Object); - if (testControllerState == null) - { - testControllerState = new TestControllerState(actionDescriptor, controller ?? this); - } - - if (routeData == null) - { - routeData = new RouteData(); - } - - var actionContext = new ActionContext( - httpContext: httpContext, - routeData: routeData, - actionDescriptor: actionDescriptor); - - if (valueProviderFactories == null) - { - valueProviderFactories = new List(); - } - - if (logger == null) - { - logger = new NullLoggerFactory().CreateLogger(); - } - var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); if (diagnosticListener != null) { diagnosticSource.SubscribeWithAdapter(diagnosticListener); } - var invoker = new TestControllerActionInvoker( - filters, - testControllerState, + var objectMethodExecutor = ObjectMethodExecutor.Create( + actionDescriptor.MethodInfo, + actionDescriptor.ControllerTypeInfo, + ParameterDefaultValues.GetParameterDefaultValues(actionDescriptor.MethodInfo)); + + var cacheEntry = new ControllerActionInvokerCacheEntry( + new FilterItem[0], + (c) => controller, + null, + (_, __, args) => + { + foreach (var item in arguments) + { + args[item.Key] = item.Value; + } + + return Task.CompletedTask; + }, + objectMethodExecutor); + + var actionContext = new ActionContext(httpContext, routeData, actionDescriptor); + var controllerContext = new ControllerContext(actionContext) + { + ValueProviderFactories = valueProviderFactories, + }; + + var invoker = new ControllerActionInvoker( logger, diagnosticSource, - actionContext, - valueProviderFactories.AsReadOnly(), - maxAllowedErrorsInModelState); + controllerContext, + cacheEntry, + filters); return invoker; } - public IActionResult ActionMethod() + public sealed class TestController { - return _result; - } + public IActionResult ActionMethod() + { + return Result; + } - public ObjectResult ThrowingActionMethod() - { - throw _actionException; - } + public ObjectResult ThrowingActionMethod() + { + throw Exception; + } - public class Person - { - public string Name { get; set; } - - public int Age { get; set; } - } - - private sealed class TestController - { public IActionResult ActionMethodWithDefaultValues(int value = 5) { return new TestActionResult { Value = value }; @@ -3418,7 +1122,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private sealed class TestActionResult : IActionResult + public sealed class TestActionResult : IActionResult { public int Value { get; set; } @@ -3429,123 +1133,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private class TestControllerState - { - private readonly ControllerActionDescriptor _descriptor; - private readonly object _controller; - - public TestControllerState( - ControllerActionDescriptor descriptor, - object controller) - : this(descriptor, controller, new Dictionary()) - { - } - - public TestControllerState( - ControllerActionDescriptor descriptor, - object controller, - IDictionary actionArguments) - { - _descriptor = descriptor; - _controller = controller; - - var objectMethodExecutor = ObjectMethodExecutor.Create( - _descriptor.MethodInfo, - _descriptor.ControllerTypeInfo, - ParameterDefaultValues.GetParameterDefaultValues(_descriptor.MethodInfo)); - - CacheEntry = new ControllerActionInvokerCacheEntry( - new FilterItem[0], - CreateController, - ReleaseController, - (_, __, args) => - { - foreach (var item in actionArguments) - { - args[item.Key] = item.Value; - } - - return Task.CompletedTask; - }, - objectMethodExecutor); - } - - public bool CreateCalled { get; private set; } - - public bool ReleaseCalled { get; private set; } - - public ControllerContext ControllerContext { get; private set; } - - public ControllerActionInvokerCacheEntry CacheEntry { get; } - - public object CreateController(ControllerContext context) - { - ControllerContext = context; - CreateCalled = true; - return _controller; - } - - public void ReleaseController(ControllerContext context, object controller) - { - Assert.NotNull(controller); - Assert.Same(_controller, controller); - ReleaseCalled = true; - } - - public void Verify() - { - if (CreateCalled && !ReleaseCalled) - { - Assert.False(true, "ReleaseController should have been called."); - } - } - } - - private class TestControllerActionInvoker : ControllerActionInvoker - { - public TestControllerActionInvoker( - IFilterMetadata[] filters, - TestControllerState testControllerState, - ILogger logger, - DiagnosticSource diagnosticSource, - ActionContext actionContext, - IReadOnlyList valueProviderFactories, - int maxAllowedErrorsInModelState) - : base( - logger, - diagnosticSource, - CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), - testControllerState.CacheEntry, - filters) - { - ControllerState = testControllerState; - } - - public TestControllerState ControllerState { get; } - - public async override Task InvokeAsync() - { - await base.InvokeAsync(); - - // Make sure that the controller was disposed in every test that creates ones. - ControllerState.Verify(); - } - - private static ControllerContext CreatControllerContext( - ActionContext actionContext, - IReadOnlyList valueProviderFactories, - int maxAllowedErrorsInModelState) - { - var controllerContext = new ControllerContext(actionContext) - { - ValueProviderFactories = valueProviderFactories.ToList() - }; - controllerContext.ModelState.MaxAllowedErrors = maxAllowedErrorsInModelState; - - return controllerContext; - } - } - private static ObjectMethodExecutor CreateExecutor(ControllerActionDescriptor actionDescriptor) { return ObjectMethodExecutor.Create( diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs index 0455f79937..4d09b72d6f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs @@ -248,15 +248,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (actionThrows) { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ThrowingActionMethod)); + actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest.TestController).GetMethod( + nameof(ControllerActionInvokerTest.TestController.ThrowingActionMethod)); } else { - actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( - nameof(ControllerActionInvokerTest.ActionMethod)); + actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest.TestController).GetMethod( + nameof(ControllerActionInvokerTest.TestController.ActionMethod)); } - actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); + actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest.TestController).GetTypeInfo(); return CreateInvoker(filters, actionDescriptor, _controller); } diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs new file mode 100644 index 0000000000..37bffa11a6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs @@ -0,0 +1,2316 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public abstract class CommonResourceInvokerTest + { + protected static readonly ContentResult Result = new ContentResult() { Content = "Hello, world!" }; + + // Intentionally choosing an uncommon exception type. + protected static readonly Exception Exception = new DivideByZeroException(); + + protected ResourceInvoker CreateInvoker( + IFilterMetadata filter, + Exception exception = null, + List valueProviderFactories = null) + { + return CreateInvoker(new IFilterMetadata[] { filter }, exception, valueProviderFactories); + } + + protected abstract ResourceInvoker CreateInvoker( + IFilterMetadata[] filters, + Exception exception = null, + List valueProviderFactories = null); + + [Fact] + public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnException(It.IsAny())) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object, exception: null); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnException(It.IsAny()), Times.Never()); + } + + [Fact] + public async Task InvokeAction_DoesNotAsyncInvokeExceptionFilter_WhenActionDoesNotThrow() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object, exception: null); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Never()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_WhenActionThrows() + { + // Arrange + Exception exception = null; + IActionResult resultFromAction = null; + var expected = new Mock(MockBehavior.Strict); + expected + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnException(It.IsAny())) + .Verifiable(); + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + exception = context.Exception; + resultFromAction = context.Result; + + // Handle the exception + context.Result = expected.Object; + }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); + + Assert.Same(Exception, exception); + Assert.Null(resultFromAction); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_WhenActionThrows() + { + // Arrange + Exception exception = null; + IActionResult resultFromAction = null; + var expected = new Mock(MockBehavior.Strict); + expected + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + exception = context.Exception; + resultFromAction = context.Result; + + // Handle the exception + context.Result = expected.Object; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + + Assert.Same(Exception, exception); + Assert.Null(resultFromAction); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + filter2.ToString(); + context.Exception = null; + }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnException(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + context.Result = result.Object; + context.Exception = null; + }) + .Verifiable(); + + // Result filters are never used when an exception bubbles up to exception filters. + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnException(It.IsAny()), + Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + context.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnException(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + context.Result = result.Object; + context.ExceptionHandled = true; + }) + .Verifiable(); + + // Result filters are never used when an exception bubbles up to exception filters. + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnException(It.IsAny()), + Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); + var filter2 = new Mock(MockBehavior.Strict); + + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.Exception = null; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var filterMetadata = new IFilterMetadata[] { filter1.Object, filter2.Object }; + var invoker = CreateInvoker(filterMetadata, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + var filter2 = new Mock(MockBehavior.Strict); + + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.Exception = null; + context.Result = result.Object; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + // Result filters are never used when an exception bubbles up to exception filters. + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() + { + // Arrange + var filter1 = new Mock(MockBehavior.Strict); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.ExceptionHandled = true; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + var filter2 = new Mock(MockBehavior.Strict); + + filter2 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.ExceptionHandled = true; + context.Result = result.Object; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + // Result filters are never used when an exception bubbles up to exception filters. + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter2.Verify( + f => f.OnExceptionAsync(It.IsAny()), + Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncExceptionFilter_SettingResultDoesNotShortCircuit() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnExceptionAsync(It.IsAny())) + .Callback(context => + { + context.Result = result.Object; + }) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(c => { }) // Does nothing, we just want to verify that it was called. + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnExceptionAsync(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_UnhandledExceptionIsThrown() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnException(It.IsAny())) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object, exception: Exception); + + // Act + await Assert.ThrowsAsync(Exception.GetType(), async () => await invoker.InvokeAsync()); + + // Assert + filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_ResultIsExecuted_WithoutResultFilters() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnException(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter.Object, resultFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesExceptionFilter_SettingResultDoesNotShortCircuit() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnException(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnException(It.IsAny())) + .Callback(c => { }) // Does nothing, we just want to verify that it was called. + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnException(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAuthorizationFilter() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnAuthorization(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncAuthorizationFilter() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnAuthorizationAsync(It.IsAny())) + .Returns(context => Task.FromResult(true)) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify( + f => f.OnAuthorizationAsync(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAuthorizationFilter_ShortCircuit() + { + // Arrange + var challenge = new Mock(MockBehavior.Strict); + challenge + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnAuthorization(It.IsAny())) + .Callback(c => Task.FromResult(true)) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnAuthorization(It.IsAny())) + .Callback(c => c.Result = challenge.Object) + .Verifiable(); + + var filter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object, filter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + challenge.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncAuthorizationFilter_ShortCircuit() + { + // Arrange + var challenge = new Mock(MockBehavior.Strict); + challenge + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnAuthorizationAsync(It.IsAny())) + .Returns((context) => + { + return Task.FromResult(true); + }) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnAuthorizationAsync(It.IsAny())) + .Returns((context) => + { + context.Result = challenge.Object; + return Task.FromResult(true); + }); + + var filter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + challenge.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + filter1.Verify( + f => f.OnAuthorizationAsync(It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_ExceptionInAuthorizationFilter_CannotBeHandledByOtherFilters() + { + // Arrange + var expected = new InvalidCastException(); + + var exceptionFilter = new Mock(MockBehavior.Strict); + exceptionFilter + .Setup(f => f.OnException(It.IsAny())) + .Callback(context => + { + // Mark as handled + context.Result = new EmptyResult(); + }) + .Verifiable(); + + var authorizationFilter1 = new Mock(MockBehavior.Strict); + authorizationFilter1 + .Setup(f => f.OnAuthorization(It.IsAny())) + .Callback(c => { throw expected; }) + .Verifiable(); + + // None of these filters should run + var authorizationFilter2 = new Mock(MockBehavior.Strict); + var resourceFilter = new Mock(MockBehavior.Strict); + var actionFilter = new Mock(MockBehavior.Strict); + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + exceptionFilter.Object, + authorizationFilter1.Object, + authorizationFilter2.Object, + resourceFilter.Object, + actionFilter.Object, + resultFilter.Object, + }); + + // Act + var thrown = await Assert.ThrowsAsync(invoker.InvokeAsync); + + // Assert + Assert.Same(expected, thrown); + exceptionFilter.Verify(f => f.OnException(It.IsAny()), Times.Never()); + authorizationFilter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAuthorizationFilter_ChallengeNotSeenByResultFilters() + { + // Arrange + var challenge = new Mock(MockBehavior.Strict); + challenge + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var authorizationFilter = new Mock(MockBehavior.Strict); + authorizationFilter + .Setup(f => f.OnAuthorization(It.IsAny())) + .Callback(c => c.Result = challenge.Object) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + authorizationFilter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); + challenge.Verify(c => c.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter() + { + // Arrange + IActionResult result = null; + + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => result = c.Result) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + Assert.Same(Result, result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter() + { + // Arrange + IActionResult result = null; + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (context, next) => + { + var resultContext = await next(); + result = resultContext.Result; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + Assert.Same(Result, result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_ShortCircuit() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Same(context.Result, result.Object); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Notice we're not calling next + c.Result = result.Object; + return Task.FromResult(true); + }) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter1 = new Mock(MockBehavior.Strict); + resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + var resultFilter2 = new Mock(MockBehavior.Strict); + resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter1.Object, + resultFilter2.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Same(context.Result, result.Object); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() + { + // Arrange + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Notice we're not calling next + return Task.FromResult(true); + }) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() + { + // Arrange + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + c.Result = new EmptyResult(); + await next(); + }) + .Verifiable(); + + var message = + "If an IAsyncActionFilter provides a result value by setting the Result property of " + + "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + + "ActionExecutionDelegate."; + + var invoker = CreateInvoker(actionFilter.Object); + + // Act & Assert + await ExceptionAssert.ThrowsAsync( + async () => await invoker.InvokeAsync(), + message); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() + { + // Arrange + ActionExecutedContext context = null; + + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + Assert.Same(Exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() + { + // Arrange + var exception = new DataMisalignedException(); + ActionExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback(c => { throw exception; }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); + + Assert.Same(exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() + { + // Arrange + var exception = new DataMisalignedException(); + ActionExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + + // Handle the exception so the test doesn't throw. + Assert.False(context.ExceptionHandled); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter2 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => { throw exception; }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + + Assert.Same(exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_HandleException() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + + c.Result = result.Object; + }) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker( + new IFilterMetadata[] { actionFilter.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesResultFilter() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (context, next) => await next()) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify( + f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesResultFilter_ShortCircuit_WithCancel() + { + // Arrange + ResultExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + filter1 + .Setup(f => f.OnResultExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Callback(c => + { + filter2.ToString(); + c.Cancel = true; + }) + .Verifiable(); + + var filter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + filter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Never()); + + Assert.True(context.Canceled); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithCancel() + { + // Arrange + ResultExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + filter1 + .Setup(f => f.OnResultExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Not calling next here + c.Cancel = true; + return Task.FromResult(true); + }) + .Verifiable(); + + var filter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + filter2.Verify( + f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + Assert.True(context.Canceled); + Assert.IsType(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel() + { + // Arrange + ResultExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + filter1 + .Setup(f => f.OnResultExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Not calling next here + return Task.FromResult(true); + }) + .Verifiable(); + + var filter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + filter2.Verify( + f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + Assert.True(context.Canceled); + Assert.IsType(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel_CallNext() + { + // Arrange + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + // Not calling next here + c.Cancel = true; + await next(); + }) + .Verifiable(); + + var message = + "If an IAsyncResultFilter cancels execution by setting the Cancel property of " + + "ResultExecutingContext to 'true', then it cannot call the next filter by invoking " + + "ResultExecutionDelegate."; + + var invoker = CreateInvoker(filter.Object); + + // Act & Assert + await ExceptionAssert.ThrowsAsync( + async () => await invoker.InvokeAsync(), + message); + } + + [Fact] + public async Task InvokeAction_InvokesResultFilter_ExceptionGoesUnhandled() + { + // Arrange + var exception = new DataMisalignedException(); + + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Throws(exception) + .Verifiable(); + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await Assert.ThrowsAsync(exception.GetType(), async () => await invoker.InvokeAsync()); + + // Assert + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + + filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResult() + { + // Arrange + ResultExecutedContext context = null; + var exception = new DataMisalignedException(); + + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Throws(exception) + .Verifiable(); + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + filter + .Setup(f => f.OnResultExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(exception, context.Exception); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + + filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResult() + { + // Arrange + ResultExecutedContext context = null; + var exception = new DataMisalignedException(); + + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Throws(exception) + .Verifiable(); + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + c.Result = result.Object; + + context = await next(); + + // Handle the exception + Assert.False(context.ExceptionHandled); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(exception, context.Exception); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + + filter.Verify( + f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResultFilter() + { + // Arrange + ResultExecutedContext context = null; + var exception = new DataMisalignedException(); + + var resultFilter1 = new Mock(MockBehavior.Strict); + resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter1 + .Setup(f => f.OnResultExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var resultFilter2 = new Mock(MockBehavior.Strict); + resultFilter2 + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Throws(exception) + .Verifiable(); + + var resultFilter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(exception, context.Exception); + + resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResultFilter() + { + // Arrange + ResultExecutedContext context = null; + var exception = new DataMisalignedException(); + + var resultFilter1 = new Mock(MockBehavior.Strict); + resultFilter1 + .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + + // Handle the exception + Assert.False(context.ExceptionHandled); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var resultFilter2 = new Mock(MockBehavior.Strict); + resultFilter2 + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Throws(exception) + .Verifiable(); + + var resultFilter3 = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(exception, context.Exception); + + resultFilter1.Verify( + f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter() + { + // Arrange + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesResourceFilter() + { + // Arrange + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecuting(It.IsAny())) + .Verifiable(); + resourceFilter + .Setup(f => f.OnResourceExecuted(It.IsAny())) + .Verifiable(); + + var invoker = CreateInvoker(resourceFilter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + resourceFilter.Verify( + f => f.OnResourceExecuted(It.IsAny()), + Times.Once()); + resourceFilter.Verify( + f => f.OnResourceExecuted(It.IsAny()), + Times.Once()); + + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromAction() + { + // Arrange + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var invoker = CreateInvoker(resourceFilter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(Result, context.Result); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter() + { + // Arrange + var expected = Mock.Of(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback((c) => + { + c.Result = expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Result); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter() + { + // Arrange + var expected = Mock.Of(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var exceptionFilter = new Mock(MockBehavior.Strict); + exceptionFilter + .Setup(f => f.OnException(It.IsAny())) + .Callback((c) => + { + c.Result = expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Result); + Assert.Null(context.Exception); + Assert.Null(context.ExceptionDispatchInfo); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromResultFilter() + { + // Arrange + var expected = Mock.Of(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Loose); + resultFilter + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Callback((c) => + { + c.Result = expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Result); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromAction() + { + // Arrange + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(Exception, context.Exception); + Assert.Same(Exception, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback((c) => + { + throw expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var exceptionFilter = new Mock(MockBehavior.Strict); + exceptionFilter + .Setup(f => f.OnException(It.IsAny())) + .Callback((c) => + { + throw expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Loose); + resultFilter + .Setup(f => f.OnResultExecuting(It.IsAny())) + .Callback((c) => + { + throw expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_BySettingNull() + { + // Arrange + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + + Assert.Same(Exception, context.Exception); + Assert.Same(Exception, context.ExceptionDispatchInfo.SourceException); + + context.Exception = null; + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_ThrowsUnhandledException() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + throw expected; + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, exception: Exception); + + // Act + var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); + + // Assert + Assert.Same(expected, exception); + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + } + + [Fact] + public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuting_ThrowsUnhandledException() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecuting(It.IsAny())) + .Callback((c) => + { + throw expected; + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, exception: Exception); + + // Act + var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); + + // Assert + Assert.Same(expected, exception); + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + } + + [Fact] + public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuted_ThrowsUnhandledException() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Loose); + resourceFilter2 + .Setup(f => f.OnResourceExecuted(It.IsAny())) + .Callback((c) => + { + throw expected; + }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, exception: Exception); + + // Act + var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); + + // Assert + Assert.Same(expected, exception); + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit() + { + // Arrange + var expected = new Mock(MockBehavior.Strict); + expected + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + c.Result = expected.Object; + return Task.FromResult(true); + }) + .Verifiable(); + + var resourceFilter3 = new Mock(MockBehavior.Strict); + var exceptionFilter = new Mock(MockBehavior.Strict); + var actionFilter = new Mock(MockBehavior.Strict); + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter2.Object, // This filter will short circuit + resourceFilter3.Object, // This shouldn't run - it will throw if it does + exceptionFilter.Object, // This shouldn't run - it will throw if it does + actionFilter.Object, // This shouldn't run - it will throw if it does + resultFilter.Object // This shouldn't run - it will throw if it does + }, + // The action won't run + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + Assert.Same(expected.Object, context.Result); + Assert.True(context.Canceled); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit_WithoutResult() + { + // Arrange + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + return Task.FromResult(true); + }) + .Verifiable(); + + var resourceFilter3 = new Mock(MockBehavior.Strict); + var exceptionFilter = new Mock(MockBehavior.Strict); + var actionFilter = new Mock(MockBehavior.Strict); + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter2.Object, // This filter will short circuit + resourceFilter3.Object, // This shouldn't run - it will throw if it does + exceptionFilter.Object, // This shouldn't run - it will throw if it does + actionFilter.Object, // This shouldn't run - it will throw if it does + resultFilter.Object // This shouldn't run - it will throw if it does + }, + // The action won't run + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Null(context.Result); + Assert.True(context.Canceled); + } + + [Fact] + public async Task InvokeAction_InvokesResourceFilter_ShortCircuit() + { + // Arrange + var expected = new Mock(MockBehavior.Strict); + expected + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecuting(It.IsAny())) + .Callback((c) => + { + c.Result = expected.Object; + }); + + var resourceFilter3 = new Mock(MockBehavior.Strict); + var actionFilter = new Mock(MockBehavior.Strict); + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter2.Object, + resourceFilter3.Object, // This shouldn't run - it will throw if it does + actionFilter.Object, // This shouldn't run - it will throw if it does + resultFilter.Object // This shouldn't run - it will throw if it does + }, + // The action won't run + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + Assert.Same(expected.Object, context.Result); + Assert.True(context.Canceled); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_InvalidShortCircuit() + { + // Arrange + var message = + "If an IAsyncResourceFilter provides a result value by setting the Result property of " + + "ResourceExecutingContext to a non-null value, then it cannot call the next filter by invoking " + + "ResourceExecutionDelegate."; + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + // This is not valid. + c.Result = Mock.Of(); + context = await next(); + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, }); + + // Act + var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); + + // Assert + Assert.Equal(message, exception.Message); + } + + [Fact] + public async Task InvokeAction_AuthorizationFilter_ChallengePreventsResourceFiltersFromRunning() + { + // Arrange + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }) + .Verifiable(); + + var authorizationFilter = new Mock(MockBehavior.Strict); + authorizationFilter + .Setup(f => f.OnAuthorization(It.IsAny())) + .Callback((c) => + { + c.Result = Result; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resourceFilter.Object, }); + + // Act + await invoker.InvokeAsync(); + + // Assert + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Never()); + } + + [Fact] + public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter() + { + // Arrange + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + var context = await next(); + Assert.Same(Exception, context.Exception); + context.ExceptionHandled = true; + }); + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter.Object, + actionFilter1.Object, + actionFilter2.Object, + }, + // The action won't run + exception: Exception); + + // Act & Assert + await invoker.InvokeAsync(); + } + } +}