Гарантирует, что Sprint 28 fix регекса для doubly-nested generics (Task<ActionResult<PagedResult<X>>>) не регрессирует. Создаёт временный controller-файл с 3 endpoint'ами разных типов, прогоняет GenerateAsync, ждёт count==3 и наличие routes в output-markdown'е. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
82 lines
3.5 KiB
C#
82 lines
3.5 KiB
C#
using System.Reflection;
|
||
using FluentAssertions;
|
||
using foodmarket.Api.Background;
|
||
using Microsoft.AspNetCore.Hosting;
|
||
using Microsoft.Extensions.Logging.Abstractions;
|
||
using Xunit;
|
||
|
||
namespace foodmarket.UnitTests;
|
||
|
||
/// <summary>Sprint 28: lock-down тест для регекса <see cref="ApiReferenceDocsJob"/>.
|
||
/// До Sprint 28 регекс return-type'a матчил только 1-level generic, поэтому
|
||
/// контроллер с <c>Task<ActionResult<PagedResult<Dto>>></c> терял endpoint'ы.
|
||
/// Этот тест ловит регрессию через scan in-memory C# source-кода и проверку
|
||
/// что нужные endpoint'ы найдены.</summary>
|
||
public class ApiReferenceDocsJobTests
|
||
{
|
||
/// <summary>Tiny test-only env для GenerateAsync. Возвращает временный
|
||
/// каталог как ContentRoot — внутри которого мы кладём наши тестовые
|
||
/// Controllers/.</summary>
|
||
private sealed class TestEnv : IWebHostEnvironment
|
||
{
|
||
public string EnvironmentName { get; set; } = "Test";
|
||
public string ApplicationName { get; set; } = "Test";
|
||
public string ContentRootPath { get; set; } = string.Empty;
|
||
public string WebRootPath { get; set; } = string.Empty;
|
||
public Microsoft.Extensions.FileProviders.IFileProvider WebRootFileProvider { get; set; } = null!;
|
||
public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!;
|
||
}
|
||
|
||
private const string DoubleNestedController = @"
|
||
using Microsoft.AspNetCore.Mvc;
|
||
|
||
namespace foodmarket.Api.Controllers.Catalog;
|
||
|
||
[Route(""api/test"")]
|
||
public class TestController : ControllerBase
|
||
{
|
||
[HttpGet]
|
||
public async Task<ActionResult<PagedResult<EmployeeDto>>> List() => Ok(null);
|
||
|
||
[HttpPost(""create""), RequiresPermission(""TestEdit"")]
|
||
public async Task<ActionResult<EmployeeDto>> Create() => Ok(null);
|
||
|
||
[HttpDelete(""{id:guid}"")]
|
||
public async Task<IActionResult> Delete(Guid id) => NoContent();
|
||
}
|
||
";
|
||
|
||
[Fact]
|
||
public async Task Scans_endpoints_with_doubly_nested_generic_return()
|
||
{
|
||
var tmp = Directory.CreateTempSubdirectory("api-ref-test-");
|
||
try
|
||
{
|
||
var ctrlDir = Path.Combine(tmp.FullName, "Controllers");
|
||
Directory.CreateDirectory(ctrlDir);
|
||
await File.WriteAllTextAsync(Path.Combine(ctrlDir, "TestController.cs"), DoubleNestedController);
|
||
|
||
var env = new TestEnv { ContentRootPath = tmp.FullName };
|
||
var job = new ApiReferenceDocsJob(env, NullLogger<ApiReferenceDocsJob>.Instance);
|
||
|
||
var count = await job.GenerateAsync();
|
||
count.Should().Be(3, "three endpoints: GET / POST create / DELETE {id}");
|
||
|
||
// Проверяем, что output-файл содержит все три route'а.
|
||
var outFile = Path.Combine(tmp.FullName, "api-reference-generated.md");
|
||
File.Exists(outFile).Should().BeTrue();
|
||
var content = await File.ReadAllTextAsync(outFile);
|
||
content.Should().Contain("/api/test/create");
|
||
content.Should().Contain("/api/test/{id:guid}");
|
||
// base-route — fallback на просто "GET /api/test".
|
||
content.Should().Contain("/api/test");
|
||
// RequiresPermission('TestEdit') должен попасть в Permission колонку.
|
||
content.Should().Contain("TestEdit");
|
||
}
|
||
finally
|
||
{
|
||
Directory.Delete(tmp.FullName, recursive: true);
|
||
}
|
||
}
|
||
}
|