test: ApiReferenceDocsJob regex lock-down (Sprint 28)
Гарантирует, что 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>
This commit is contained in:
parent
4534f8e36c
commit
ffb8456514
81
tests/food-market.UnitTests/ApiReferenceDocsJobTests.cs
Normal file
81
tests/food-market.UnitTests/ApiReferenceDocsJobTests.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue