// 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.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers { public class MvcFactsTest { private static readonly string ControllerAttribute = typeof(ControllerAttribute).FullName; private static readonly string NonControllerAttribute = typeof(NonControllerAttribute).FullName; private static readonly string NonActionAttribute = typeof(NonActionAttribute).FullName; private static readonly Type TestIsControllerActionType = typeof(TestIsControllerAction); #region IsController [Fact] public Task IsController_ReturnsFalseForInterfaces() => IsControllerReturnsFalse(typeof(ITestController)); [Fact] public Task IsController_ReturnsFalseForAbstractTypes() => IsControllerReturnsFalse(typeof(AbstractController)); [Fact] public Task IsController_ReturnsFalseForValueType() => IsControllerReturnsFalse(typeof(ValueTypeController)); [Fact] public Task IsController_ReturnsFalseForGenericType() => IsControllerReturnsFalse(typeof(OpenGenericController<>)); [Fact] public Task IsController_ReturnsFalseForPocoType() => IsControllerReturnsFalse(typeof(PocoType)); [Fact] public Task IsController_ReturnsFalseForTypeDerivedFromPocoType() => IsControllerReturnsFalse(typeof(DerivedPocoType)); [Fact] public Task IsController_ReturnsTrueForTypeDerivingFromController() => IsControllerReturnsTrue(typeof(TypeDerivingFromController)); [Fact] public Task IsController_ReturnsTrueForTypeDerivingFromControllerBase() => IsControllerReturnsTrue(typeof(TypeDerivingFromControllerBase)); [Fact] public Task IsController_ReturnsTrueForTypeDerivingFromController_WithoutSuffix() => IsControllerReturnsTrue(typeof(NoSuffix)); [Fact] public Task IsController_ReturnsTrueForTypeWithSuffix_ThatIsNotDerivedFromController() => IsControllerReturnsTrue(typeof(PocoController)); [Fact] public Task IsController_ReturnsTrueForTypeWithoutSuffix_WithControllerAttribute() => IsControllerReturnsTrue(typeof(CustomBase)); [Fact] public Task IsController_ReturnsTrueForTypeDerivingFromCustomBaseThatHasControllerAttribute() => IsControllerReturnsTrue(typeof(ChildOfCustomBase)); [Fact] public Task IsController_ReturnsFalseForTypeWithNonControllerAttribute() => IsControllerReturnsFalse(typeof(BaseNonController)); [Fact] public Task IsController_ReturnsFalseForTypesDerivingFromTypeWithNonControllerAttribute() => IsControllerReturnsFalse(typeof(BasePocoNonControllerChildController)); [Fact] public Task IsController_ReturnsFalseForTypesDerivingFromTypeWithNonControllerAttributeWithControllerAttribute() => IsControllerReturnsFalse(typeof(ControllerAttributeDerivingFromNonController)); private async Task IsControllerReturnsFalse(Type type) { var compilation = await GetIsControllerCompilation(); var controllerAttribute = compilation.GetTypeByMetadataName(ControllerAttribute); var nonControllerAttribute = compilation.GetTypeByMetadataName(NonControllerAttribute); var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); // Act var isController = MvcFacts.IsController(typeSymbol, controllerAttribute, nonControllerAttribute); // Assert Assert.False(isController); } private async Task IsControllerReturnsTrue(Type type) { var compilation = await GetIsControllerCompilation(); var controllerAttribute = compilation.GetTypeByMetadataName(ControllerAttribute); var nonControllerAttribute = compilation.GetTypeByMetadataName(NonControllerAttribute); var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); // Act var isController = MvcFacts.IsController(typeSymbol, controllerAttribute, nonControllerAttribute); // Assert Assert.True(isController); } #endregion #region IsControllerAction [Fact] public Task IsAction_ReturnsFalseForConstructor() => IsActionReturnsFalse(TestIsControllerActionType, ".ctor"); [Fact] public Task IsAction_ReturnsFalseForStaticConstructor() => IsActionReturnsFalse(TestIsControllerActionType, ".cctor"); [Fact] public Task IsAction_ReturnsFalseForPrivateMethod() => IsActionReturnsFalse(TestIsControllerActionType, "PrivateMethod"); [Fact] public Task IsAction_ReturnsFalseForProtectedMethod() => IsActionReturnsFalse(TestIsControllerActionType, "ProtectedMethod"); [Fact] public Task IsAction_ReturnsFalseForInternalMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.InternalMethod)); [Fact] public Task IsAction_ReturnsFalseForGenericMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.GenericMethod)); [Fact] public Task IsAction_ReturnsFalseForStaticMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.StaticMethod)); [Fact] public Task IsAction_ReturnsFalseForNonActionMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.NonAction)); [Fact] public Task IsAction_ReturnsFalseForOverriddenNonActionMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.NonActionBase)); [Fact] public Task IsAction_ReturnsFalseForDisposableDispose() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.Dispose)); [Fact] public Task IsAction_ReturnsFalseForExplicitDisposableDispose() => IsActionReturnsFalse(typeof(ExplicitIDisposable), "System.IDisposable.Dispose"); [Fact] public Task IsAction_ReturnsFalseForAbstractMethods() => IsActionReturnsFalse(typeof(TestIsControllerActionBase), nameof(TestIsControllerActionBase.AbstractMethod)); [Fact] public Task IsAction_ReturnsFalseForObjectEquals() => IsActionReturnsFalse(typeof(object), nameof(object.Equals)); [Fact] public Task IsAction_ReturnsFalseForObjectHashCode() => IsActionReturnsFalse(typeof(object), nameof(object.GetHashCode)); [Fact] public Task IsAction_ReturnsFalseForObjectToString() => IsActionReturnsFalse(typeof(object), nameof(object.ToString)); [Fact] public Task IsAction_ReturnsFalseForOverriddenObjectEquals() => IsActionReturnsFalse(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.Equals)); [Fact] public Task IsAction_ReturnsFalseForOverriddenObjectHashCode() => IsActionReturnsFalse(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.GetHashCode)); private async Task IsActionReturnsFalse(Type type, string methodName) { var compilation = await GetIsControllerActionCompilation(); var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); var disposableDispose = GetDisposableDispose(compilation); var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); var method = (IMethodSymbol)typeSymbol.GetMembers(methodName).First(); // Act var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); // Assert Assert.False(isControllerAction); } [Fact] public Task IsAction_ReturnsTrueForNewMethodsOfObject() => IsActionReturnsTrue(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.ToString)); [Fact] public Task IsAction_ReturnsTrueForNotDisposableDispose() => IsActionReturnsTrue(typeof(NotDisposable), nameof(NotDisposable.Dispose)); [Fact] public Task IsAction_ReturnsTrueForNotDisposableDisposeOnTypeWithExplicitImplementation() => IsActionReturnsTrue(typeof(NotDisposableWithExplicitImplementation), nameof(NotDisposableWithExplicitImplementation.Dispose)); [Fact] public Task IsAction_ReturnsTrueForOrdinaryAction() => IsActionReturnsTrue(TestIsControllerActionType, nameof(TestIsControllerAction.Ordinary)); [Fact] public Task IsAction_ReturnsTrueForOverriddenMethod() => IsActionReturnsTrue(TestIsControllerActionType, nameof(TestIsControllerAction.AbstractMethod)); [Fact] public async Task IsAction_ReturnsTrueForNotDisposableDisposeOnTypeWithImplicitImplementation() { var compilation = await GetIsControllerActionCompilation(); var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); var disposableDispose = GetDisposableDispose(compilation); var typeSymbol = compilation.GetTypeByMetadataName(typeof(NotDisposableWithDisposeThatIsNotInterfaceContract).FullName); var method = typeSymbol.GetMembers(nameof(IDisposable.Dispose)).OfType().First(f => !f.ReturnsVoid); // Act var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); // Assert Assert.True(isControllerAction); } private async Task IsActionReturnsTrue(Type type, string methodName) { var compilation = await GetIsControllerActionCompilation(); var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); var disposableDispose = GetDisposableDispose(compilation); var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); var method = (IMethodSymbol)typeSymbol.GetMembers(methodName).First(); // Act var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); // Assert Assert.True(isControllerAction); } private IMethodSymbol GetDisposableDispose(Compilation compilation) { var type = compilation.GetSpecialType(SpecialType.System_IDisposable); return (IMethodSymbol)type.GetMembers(nameof(IDisposable.Dispose)).First(); } #endregion private Task GetIsControllerCompilation() => GetCompilation("IsControllerTests"); private Task GetIsControllerActionCompilation() => GetCompilation("IsControllerActionTests"); private Task GetCompilation(string test) { var testSource = MvcTestSource.Read(GetType().Name, test); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); return project.GetCompilationAsync(); } } }