food-market/tests/food-market.UnitTests/ApiReferenceDocsJobTests.cs
nns ffb8456514
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
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>
2026-06-09 03:38:29 +05:00

82 lines
3.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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&lt;ActionResult&lt;PagedResult&lt;Dto&gt;&gt;&gt;</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);
}
}
}