131 lines
5.0 KiB
C#
131 lines
5.0 KiB
C#
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");
|
|
}
|
|
|
|
}
|