File: ApiConventionAnalyzerIntegrationTest.cs
Web Access
Project: src\src\Mvc\Mvc.Api.Analyzers\test\Mvc.Api.Analyzers.Test.csproj (Mvc.Api.Analyzers.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.CodeAnalysis;
 
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers;
 
public class ApiConventionAnalyzerIntegrationTest
{
    private MvcDiagnosticAnalyzerRunner Executor { get; } = new ApiConventionWith1006DiagnosticEnabledRunner();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForNonApiController()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForRazorPageModels()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForOkResultReturningAction()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForReturnStatementsInLambdas()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public Task NoDiagnosticsAreReturned_ForApiController_WhenMethodNeverReturns()
        => RunNoDiagnosticsAreReturned();
 
    [Fact]
    public async Task DiagnosticsAreReturned_ForIncompleteActionResults()
    {
        // Arrange
        var source = @"
using Microsoft.AspNetCore.Mvc;
 
namespace Test
{
    [ApiController]
    [Route(""[controller]/[action]"")
    public class TestController : ControllerBase
    {
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return /*MM*/NotFound();
            }
 
            return;
        }
    }
}";
        var testSource = TestSource.Read(source);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(testSource.Source);
 
        // Assert
        var diagnostic = Assert.Single(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
        AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location);
    }
 
    [Fact]
    public async Task NoDiagnosticsAreReturned_WhenActionDoesNotCompile()
    {
        // Arrange
        var source = @"
namespace Test
{
    [ApiController]
    [Route(""[controller]/[action]"")
    public class TestController : ControllerBase
    {
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return NotFound();
            }
 
            return Ok();
        }
    }
}";
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(source);
 
        // Assert
        Assert.DoesNotContain(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
    }
 
    [Fact]
    public async Task DiagnosticsAreReturned_ForNotFoundNullActionResults()
    {
        // Arrange
        var source = @"
using Microsoft.AspNetCore.Mvc;
 
namespace Test
{
    [ApiController]
    [Route(""[controller]"")]
    public class TestController : ControllerBase
    {
        [HttpGet]
        public ActionResult<string> Test()
        {
            return NotFound(null);
        }
    }
}";
        var testSource = TestSource.Read(source);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(testSource.Source);
 
        // Assert
        Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
    }
 
    [Fact]
    public async Task DiagnosticsAreReturned_ForActionsReturnedFromSwitchExpression()
    {
        // Arrange
        var source = @"
using Microsoft.AspNetCore.Mvc;
 
namespace Test
{
    [ApiController]
    public class Foo : ControllerBase
    {
        public IActionResult Get(bool b)
        {
            return b switch
            {
                true => Ok(),
                false => BadRequest()
            };
        }
    }
}";
        var testSource = TestSource.Read(source);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(testSource.Source);
 
        // Assert
        Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
    }
 
        [Fact]
    public async Task DiagnosticsAreReturned_ForActionsReturnedFromSwitchStatement()
    {
        // Arrange
        var source = @"
using Microsoft.AspNetCore.Mvc;
 
namespace Test
{
    [ApiController]
    public class Foo : ControllerBase
    {
        public IActionResult Get(bool b)
        {
            switch (b)
            {
                case true:
                    return Ok();
                case false:
                    return BadRequest();
            }
        }
    }
}";
        var testSource = TestSource.Read(source);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(testSource.Source);
 
        // Assert
        Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
    }
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 200);
 
    [Fact]
    public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);
 
    [Fact]
    public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 422);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 400);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 202);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation()
        => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation()
        => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType()
        => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 400);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 404);
 
    [Fact]
    public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode()
        => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 200);
 
    private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "")
    {
        // Arrange
        var testSource = MvcTestSource.Read(GetType().Name, testMethod);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.GetDiagnosticsAsync(testSource.Source);
 
        // Assert
        Assert.Empty(result);
    }
 
    private Task RunTest(DiagnosticDescriptor descriptor, [CallerMemberName] string testMethod = "")
        => RunTest(descriptor, Array.Empty<object>(), testMethod);
 
    private Task RunTest(DiagnosticDescriptor descriptor, int statusCode, [CallerMemberName] string testMethod = "")
        => RunTest(descriptor, new[] { statusCode.ToString(CultureInfo.InvariantCulture) }, testMethod);
 
    private async Task RunTest(DiagnosticDescriptor descriptor, object[] args, [CallerMemberName] string testMethod = "")
    {
        // Arrange
        var testSource = MvcTestSource.Read(GetType().Name, testMethod);
        var expectedLocation = testSource.DefaultMarkerLocation;
 
        // Act
        var result = await Executor.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(CultureInfo.InvariantCulture, descriptor.MessageFormat.ToString(CultureInfo.InvariantCulture), args), diagnostic.GetMessage(CultureInfo.InvariantCulture));
            });
    }
    private class ApiConventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner
    {
        public ApiConventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer())
        {
        }
 
        protected override CompilationOptions ConfigureCompilationOptions(CompilationOptions options)
        {
            var compilationOptions = base.ConfigureCompilationOptions(options);
 
            // 10006 is disabled by default. Explicitly enable it so we can correctly validate no diagnostics
            // are returned scenarios.
            var specificDiagnosticOptions = compilationOptions.SpecificDiagnosticOptions.Add(
                ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode.Id,
                ReportDiagnostic.Info);
 
            return compilationOptions.WithSpecificDiagnosticOptions(specificDiagnosticOptions);
        }
    }
}