using System.Reflection;
using FluentAssertions;
using foodmarket.Api.Background;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace foodmarket.UnitTests;
/// Sprint 28: lock-down тест для регекса .
/// До Sprint 28 регекс return-type'a матчил только 1-level generic, поэтому
/// контроллер с Task<ActionResult<PagedResult<Dto>>> терял endpoint'ы.
/// Этот тест ловит регрессию через scan in-memory C# source-кода и проверку
/// что нужные endpoint'ы найдены.
public class ApiReferenceDocsJobTests
{
/// Tiny test-only env для GenerateAsync. Возвращает временный
/// каталог как ContentRoot — внутри которого мы кладём наши тестовые
/// Controllers/.
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>> List() => Ok(null);
[HttpPost(""create""), RequiresPermission(""TestEdit"")]
public async Task> Create() => Ok(null);
[HttpDelete(""{id:guid}"")]
public async Task 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.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);
}
}
}