// 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.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles; using Microsoft.CodeAnalysis; using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers { public class TopLevelParameterNameAnalyzerTest { private MvcDiagnosticAnalyzerRunner Runner { get; } = new MvcDiagnosticAnalyzerRunner(new TopLevelParameterNameAnalyzer()); [Fact] public Task DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties() => RunTest(nameof(DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel), "model"); [Fact] public Task DiagnosticsAreReturned_ForModelBoundParameters() => RunTest(nameof(DiagnosticsAreReturned_ForModelBoundParametersModel), "value"); [Fact] public Task DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName() => RunTest(nameof(DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel), "parameter"); [Fact] public Task NoDiagnosticsAreReturnedForApiControllers() => RunNoDiagnosticsAreReturned(); [Fact] public Task NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute() => RunNoDiagnosticsAreReturned(); [Fact] public Task NoDiagnosticsAreReturnedForNonActions() => RunNoDiagnosticsAreReturned(); [Fact] public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty() { var result = await IsProblematicParameterTest(); Assert.True(result); } [Fact] public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty() { var result = await IsProblematicParameterTest(); Assert.True(result); } [Fact] public async Task IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter() { var result = await IsProblematicParameterTest(); Assert.True(result); } [Fact] public async Task IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter() { var result = await IsProblematicParameterTest(); Assert.True(result); } [Fact] public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_ReturnsFalse_ForFromBodyParameter() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_IgnoresStaticProperties() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_IgnoresFields() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_IgnoresMethods() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] public async Task IsProblematicParameter_IgnoresNonPublicProperties() { var result = await IsProblematicParameterTest(); Assert.False(result); } private async Task IsProblematicParameterTest([CallerMemberName] string testMethod = "") { var testSource = MvcTestSource.Read(GetType().Name, testMethod); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); var compilation = await project.GetCompilationAsync(); var modelType = compilation.GetTypeByMetadataName($"Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles.{testMethod}"); var method = (IMethodSymbol)modelType.GetMembers("ActionMethod").First(); var parameter = method.Parameters[0]; var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var result = TopLevelParameterNameAnalyzer.IsProblematicParameter(symbolCache, parameter); return result; } [Fact] public async Task GetName_ReturnsValueFromFirstAttributeWithValue() { var methodName = nameof(GetNameTests.SingleAttribute); var compilation = await GetCompilationForGetName(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); var method = (IMethodSymbol)type.GetMembers(methodName).First(); var parameter = method.Parameters[0]; var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); Assert.Equal("testModelName", name); } [Fact] public async Task GetName_ReturnsName_IfNoAttributesAreSpecified() { var methodName = nameof(GetNameTests.NoAttribute); var compilation = await GetCompilationForGetName(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); var method = (IMethodSymbol)type.GetMembers(methodName).First(); var parameter = method.Parameters[0]; var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); Assert.Equal("param", name); } [Fact] public async Task GetName_ReturnsName_IfAttributeDoesNotSpecifyName() { var methodName = nameof(GetNameTests.SingleAttributeWithoutName); var compilation = await GetCompilationForGetName(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); var method = (IMethodSymbol)type.GetMembers(methodName).First(); var parameter = method.Parameters[0]; var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); Assert.Equal("param", name); } [Fact] public async Task GetName_ReturnsFirstName_IfMultipleAttributesAreSpecified() { var methodName = nameof(GetNameTests.MultipleAttributes); var compilation = await GetCompilationForGetName(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); var method = (IMethodSymbol)type.GetMembers(methodName).First(); var parameter = method.Parameters[0]; var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); Assert.Equal("name1", name); } private async Task GetCompilationForGetName() { var testSource = MvcTestSource.Read(GetType().Name, "GetNameTests"); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); var compilation = await project.GetCompilationAsync(); return compilation; } [Fact] public async Task SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType() { var testMethod = nameof(SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType); var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); var compilation = await project.GetCompilationAsync(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); var method = (IMethodSymbol)type.GetMembers(testMethod).First(); var parameter = method.Parameters[0]; var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); Assert.False(result); } [Fact] public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor() { var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor); var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); var compilation = await project.GetCompilationAsync(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); var method = (IMethodSymbol)type.GetMembers(testMethod).First(); var parameter = method.Parameters[0]; var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); Assert.True(result); } [Fact] public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty() { var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty); var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); var compilation = await project.GetCompilationAsync(); var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); var method = (IMethodSymbol)type.GetMembers(testMethod).First(); var parameter = method.Parameters[0]; var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); Assert.True(result); } private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") { // Arrange var testSource = MvcTestSource.Read(GetType().Name, testMethod); var expectedLocation = testSource.DefaultMarkerLocation; // Act var result = await Runner.GetDiagnosticsAsync(testSource.Source); // Assert Assert.Empty(result); } private async Task RunTest(string typeName, string parameterName, [CallerMemberName] string testMethod = "") { // Arrange var descriptor = DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty; var testSource = MvcTestSource.Read(GetType().Name, testMethod); var expectedLocation = testSource.DefaultMarkerLocation; // Act var result = await Runner.GetDiagnosticsAsync(testSource.Source); // Assert Assert.Collection( result, diagnostic => { Assert.Equal(descriptor.Id, diagnostic.Id); Assert.Same(descriptor, diagnostic.Descriptor); AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), typeName, parameterName), diagnostic.GetMessage()); }); } } }