This commit is contained in:
2025-10-27 17:39:18 -04:00
commit 31f723bea4
1579 changed files with 642409 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Prefab.Handler;
using Prefab.Catalog.Domain.Exceptions;
using Prefab.Web.Shared;
using Shouldly;
using System.Text.Json;
namespace Prefab.Tests.Unit.Web.Shared;
public sealed class ExceptionHandlerShould
{
[Fact]
public async Task ReturnBadRequestForValidationFailures()
{
var failure = new ValidationFailure("productId", "Either productId or sku must be provided.");
var exception = new ValidationException(new[] { failure });
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
var services = new ServiceCollection();
services.AddSingleton<IHandlerContextAccessor, HandlerContextAccessor>();
using var provider = services.BuildServiceProvider();
context.RequestServices = provider;
var middleware = new ExceptionHandler(
_ => throw exception,
NullLogger<ExceptionHandler>.Instance,
Options.Create(new JsonOptions()));
await middleware.InvokeAsync(context);
context.Response.StatusCode.ShouldBe(StatusCodes.Status400BadRequest);
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var payload = await reader.ReadToEndAsync(TestContext.Current.CancellationToken);
using var document = JsonDocument.Parse(payload);
var root = document.RootElement;
var messages = new List<string>();
if (root.TryGetProperty("errors", out var errorsElement))
{
messages.AddRange(
errorsElement
.EnumerateObject()
.SelectMany(property => property.Value.EnumerateArray())
.Select(element => element.GetString())
.Where(message => !string.IsNullOrWhiteSpace(message))!
.Cast<string>());
}
if (messages.Count == 0 && root.TryGetProperty("detail", out var detailElement))
{
messages.Add(detailElement.GetString() ?? string.Empty);
}
messages.ShouldNotBeEmpty(payload);
messages.ShouldContain(message => message.Equals("Either productId or sku must be provided.", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public async Task ReturnBadRequestForDomainExceptions()
{
var exception = new DomainValidationException("Demo domain failure.");
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
var services = new ServiceCollection();
services.AddSingleton<IHandlerContextAccessor, HandlerContextAccessor>();
using var provider = services.BuildServiceProvider();
context.RequestServices = provider;
var middleware = new ExceptionHandler(
_ => throw exception,
NullLogger<ExceptionHandler>.Instance,
Options.Create(new JsonOptions()));
await middleware.InvokeAsync(context);
context.Response.StatusCode.ShouldBe(StatusCodes.Status400BadRequest);
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var payload = await reader.ReadToEndAsync(TestContext.Current.CancellationToken);
using var document = JsonDocument.Parse(payload);
var detail = document.RootElement.GetProperty("detail").GetString();
document.RootElement.GetProperty("type").GetString().ShouldBe("https://prefab.dev/problems/domain-error");
detail.ShouldNotBeNull();
detail!.ShouldContain("Demo domain failure.");
}
[Fact]
public async Task ReturnConflictForConcurrencyExceptions()
{
var exception = new DbUpdateConcurrencyException("Concurrency conflict");
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
var services = new ServiceCollection();
services.AddSingleton<IHandlerContextAccessor, HandlerContextAccessor>();
using var provider = services.BuildServiceProvider();
context.RequestServices = provider;
var middleware = new ExceptionHandler(
_ => throw exception,
NullLogger<ExceptionHandler>.Instance,
Options.Create(new JsonOptions()));
await middleware.InvokeAsync(context);
context.Response.StatusCode.ShouldBe(StatusCodes.Status409Conflict);
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var payload = await reader.ReadToEndAsync(TestContext.Current.CancellationToken);
using var document = JsonDocument.Parse(payload);
document.RootElement.GetProperty("type").GetString().ShouldBe("https://prefab.dev/problems/concurrency-conflict");
}
}