// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNet.Mvc.Core.Test { public class ActionExecutorTests { private TestController _controller = new TestController(); private delegate void MethodWithVoidReturnType(); private delegate string SyncMethod(string s); private delegate Task MethodWithTaskReturnType(int i, string s); private delegate Task MethodWithTaskOfIntReturnType(int i, string s); private delegate Task> MethodWithTaskOfTaskOfIntReturnType(int i, string s); public delegate TestController.TaskDerivedType MethodWithCustomTaskReturnType(int i, string s); private delegate TestController.TaskOfTDerivedType MethodWithCustomTaskOfTReturnType(int i, string s); private delegate dynamic ReturnTaskAsDynamicValue(int i, string s); [Fact] public async Task AsyncAction_WithVoidReturnType() { var methodWithVoidReturnType = new MethodWithVoidReturnType(TestController.VoidAction); var result = await ReflectedActionExecutor.ExecuteAsync( methodWithVoidReturnType.GetMethodInfo(), null, null); Assert.Same(null, result); } [Fact] public async Task AsyncAction_TaskReturnType() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskReturnType = new MethodWithTaskReturnType(_controller.TaskAction); var result = await ReflectedActionExecutor.ExecuteAsync( methodWithTaskReturnType.GetMethodInfo(), _controller, actionParameters); Assert.Same(null, result); } [Fact] public async Task AsyncAction_TaskOfValueReturnType() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction); var result = await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters); Assert.Equal(inputParam1, result); } [Fact] public async Task AsyncAction_TaskOfTaskOfValueReturnType() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskOfTaskOfIntReturnType = new MethodWithTaskOfTaskOfIntReturnType(_controller.TaskOfTaskAction); var result = await (Task)(await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters)); Assert.Equal(inputParam1, result); } [Fact] public async Task AsyncAction_WithAsyncKeywordThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithException); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters), "Not Implemented Exception"); } [Fact] public async Task AsyncAction_WithoutAsyncThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionWithExceptionWithoutAsync); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters), "Not Implemented Exception"); } [Fact] public async Task AsyncAction_WithExceptionsAfterAwait() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskActionThrowAfterAwait); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters), "Argument Exception"); } [Fact] public async Task SyncAction() { string inputString = "hello"; var syncMethod = new SyncMethod(_controller.Echo); var result = await ReflectedActionExecutor.ExecuteAsync( syncMethod.GetMethodInfo(), _controller, new Dictionary() { { "input", inputString } }); Assert.Equal(inputString, result); } [Fact] public async Task SyncAction_WithException() { string inputString = "hello"; var syncMethod = new SyncMethod(_controller.EchoWithException); var expectedException = "The method or operation is not implemented."; await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( syncMethod.GetMethodInfo(), _controller, new Dictionary() { { "input", inputString } }), expectedException); } [Fact] public async Task AsyncAction_WithCustomTaskReturnTypeThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; // If it is an unrecognized derived type we throw an InvalidOperationException. var methodWithCutomTaskReturnType = new MethodWithCustomTaskReturnType(_controller.TaskActionWithCustomTaskReturnType); string expectedException = string.Format( CultureInfo.CurrentCulture, "The method 'TaskActionWithCustomTaskReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.", typeof(TestController)); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithCutomTaskReturnType.GetMethodInfo(), _controller, actionParameters), expectedException); } [Fact] public async Task AsyncAction_WithCustomTaskOfTReturnTypeThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithCutomTaskOfTReturnType = new MethodWithCustomTaskOfTReturnType(_controller.TaskActionWithCustomTaskOfTReturnType); string expectedException = string.Format( CultureInfo.CurrentCulture, "The method 'TaskActionWithCustomTaskOfTReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.", typeof(TestController)); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithCutomTaskOfTReturnType.GetMethodInfo(), _controller, actionParameters), expectedException); } [Fact] public async Task AsyncAction_ReturningUnwrappedTaskThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var methodWithUnwrappedTask = new MethodWithTaskReturnType(_controller.UnwrappedTask); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithUnwrappedTask.GetMethodInfo(), _controller, actionParameters), string.Format(CultureInfo.CurrentCulture, "The method 'UnwrappedTask' on type '{0}' returned an instance of '{1}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task.", typeof(TestController), typeof(Task).FullName )); } [Fact] public async Task AsyncAction_WithDynamicReturnTypeThrows() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; var dynamicTaskMethod = new ReturnTaskAsDynamicValue(_controller.ReturnTaskAsDynamicValue); string expectedException = string.Format( CultureInfo.CurrentCulture, "The method 'ReturnTaskAsDynamicValue' on type '{0}' returned a Task instance even though it is not an asynchronous method.", typeof(TestController)); await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( dynamicTaskMethod.GetMethodInfo(), _controller, actionParameters), expectedException); } [Fact] public async Task ParametersInRandomOrder() { int inputParam1 = 1; string inputParam2 = "Second Parameter"; // Note that the order of parameters is reversed var actionParameters = new Dictionary { { "s", inputParam2 }, { "i", inputParam1 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction); var result = await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters); Assert.Equal(inputParam1, result); } [Fact] public async Task InvalidParameterValueThrows() { string inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", "Some Invalid Value" }, { "s", inputParam2 } }; var methodWithTaskOfIntReturnType = new MethodWithTaskOfIntReturnType(_controller.TaskValueTypeAction); var expectedException = string.Format( CultureInfo.CurrentCulture, "Object of type '{0}' cannot be converted to type '{1}'.", typeof (string), typeof (int)); // If it is an unrecognized derived type we throw an InvalidOperationException. await AssertThrowsAsync( async () => await ReflectedActionExecutor.ExecuteAsync( methodWithTaskOfIntReturnType.GetMethodInfo(), _controller, actionParameters), expectedException); } // TODO: XUnit Assert.Throw is not async-aware. Check if the latest version supports it. private static async Task AssertThrowsAsync(Func> func, string expectedExceptionMessage = "") { var expected = typeof(TException); Type actual = null; string actualExceptionMessage = string.Empty; try { var result = await func(); } catch (Exception e) { actual = e.GetType(); actualExceptionMessage = e.Message; } Assert.Equal(expected, actual); Assert.Equal(expectedExceptionMessage, actualExceptionMessage); } } }