using System.Text.Json; using Deal.Api.Middleware; using Deal.SharedKernel.Errors; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NSubstitute; namespace Deal.Tests.Unit.Api; /// /// Тесты обработчика необработанных исключений HTTP. /// public sealed class DealExceptionHandlerTests { [Fact] public async Task NotFound_MapsTo404WithCodeAndRussianDetail() { DefaultHttpContext context = CreateContext(); DealExceptionHandler handler = new(Substitute.For>()); bool handled = await handler.TryHandleAsync( context, new NotFoundException("Карточка", "c_1"), CancellationToken.None); Assert.True(handled); Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode); (string detail, string code) = await ReadBodyAsync(context); Assert.Equal(DealErrorCodes.NotFound, code); Assert.Contains("Карточка", detail); } [Fact] public async Task Unavailable_MapsTo503() { DefaultHttpContext context = CreateContext(); DealExceptionHandler handler = new(Substitute.For>()); await handler.TryHandleAsync( context, new ServiceUnavailableException("ИИ"), CancellationToken.None); Assert.Equal(StatusCodes.Status503ServiceUnavailable, context.Response.StatusCode); } [Fact] public async Task UnexpectedException_MapsToGeneric500WithoutStackOrDetails() { DefaultHttpContext context = CreateContext(); DealExceptionHandler handler = new(Substitute.For>()); bool handled = await handler.TryHandleAsync( context, new InvalidOperationException("секретная внутренняя деталь"), CancellationToken.None); Assert.True(handled); Assert.Equal(StatusCodes.Status500InternalServerError, context.Response.StatusCode); (string detail, string code) = await ReadBodyAsync(context); Assert.Equal(DealErrorCodes.Internal, code); Assert.DoesNotContain("секретная внутренняя деталь", detail); Assert.DoesNotContain("at ", detail); } private static DefaultHttpContext CreateContext() { var context = new DefaultHttpContext(); context.Response.Body = new MemoryStream(); return context; } private static async Task<(string Detail, string Code)> ReadBodyAsync(HttpContext context) { context.Response.Body.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(context.Response.Body); string json = await reader.ReadToEndAsync(); using JsonDocument document = JsonDocument.Parse(json); return ( document.RootElement.GetProperty("detail").GetString() ?? string.Empty, document.RootElement.GetProperty("code").GetString() ?? string.Empty); } }