diff --git a/src/Squidex.Web/ApiExceptionFilterAttribute.cs b/src/Squidex.Web/ApiExceptionFilterAttribute.cs index 65c6e8ac0..3e195c0be 100644 --- a/src/Squidex.Web/ApiExceptionFilterAttribute.cs +++ b/src/Squidex.Web/ApiExceptionFilterAttribute.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -60,7 +61,7 @@ namespace Squidex.Web private static IActionResult OnValidationException(ValidationException ex) { - return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ex.Errors?.ToArray(e => e.Message) }); + return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ToDetails(ex) }); } private static IActionResult ErrorResult(int statusCode, ErrorDto error) @@ -89,5 +90,20 @@ namespace Squidex.Web context.Result = result; } } + + private static string[] ToDetails(ValidationException ex) + { + return ex.Errors?.ToArray(e => + { + if (e.PropertyNames?.Any() == true) + { + return $"{string.Join(", ", e.PropertyNames)}: {e.Message}"; + } + else + { + return e.Message; + } + }); + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs index 81170e36f..ea9c72a64 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; using Squidex.Domain.Apps.Core.HandleRules; namespace Squidex.Areas.Api.Controllers.Rules.Models diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index 30f1b27bf..0b1f7cfce 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -25,6 +25,8 @@ namespace Squidex.Config.Domain { private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) { + settings.Converters.Add(new StringEnumConverter()); + settings.ContractResolver = new ConverterContractResolver( new AppClientsConverter(), new AppContributorsConverter(), diff --git a/tests/Squidex.Web.Tests/ApiCostsAttributeTests.cs b/tests/Squidex.Web.Tests/ApiCostsAttributeTests.cs new file mode 100644 index 000000000..91f9c4a16 --- /dev/null +++ b/tests/Squidex.Web.Tests/ApiCostsAttributeTests.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Xunit; + +namespace Squidex.Web +{ + public class ApiCostsAttributeTests + { + [Fact] + public void Should_assign_weight() + { + var sut = new ApiCostsAttribute(10.5); + + Assert.Equal(10.5, sut.Weight); + } + } +} diff --git a/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs b/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs new file mode 100644 index 000000000..51a15b1d5 --- /dev/null +++ b/tests/Squidex.Web.Tests/ApiExceptionFilterAttributeTests.cs @@ -0,0 +1,122 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Security; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Web +{ + public class ApiExceptionFilterAttributeTests + { + private readonly ApiExceptionFilterAttribute sut = new ApiExceptionFilterAttribute(); + + [Fact] + public void Should_generate_404_for_DomainObjectNotFoundException() + { + var context = E(new DomainObjectNotFoundException("1", typeof(object))); + + sut.OnException(context); + + Assert.IsType(context.Result); + } + + [Fact] + public void Should_generate_400_for_ValidationException() + { + var ex = new ValidationException("NotAllowed", + new ValidationError("Error1"), + new ValidationError("Error2", "P"), + new ValidationError("Error3", "P1", "P2")); + + var context = E(ex); + + sut.OnException(context); + + var result = context.Result as ObjectResult; + + Assert.Equal(400, result.StatusCode); + Assert.Equal(400, (result.Value as ErrorDto)?.StatusCode); + + Assert.Equal(ex.Summary, (result.Value as ErrorDto).Message); + + Assert.Equal(new string[] { "Error1", "P: Error2", "P1, P2: Error3" }, (result.Value as ErrorDto).Details); + } + + [Fact] + public void Should_generate_400_for_DomainException() + { + var context = E(new DomainException("NotAllowed")); + + sut.OnException(context); + + Validate(400, context); + } + + [Fact] + public void Should_generate_412_for_DomainObjectVersionException() + { + var context = E(new DomainObjectVersionException("1", typeof(object), 1, 2)); + + sut.OnException(context); + + Validate(412, context); + } + + [Fact] + public void Should_generate_403_for_DomainForbiddenException() + { + var context = E(new DomainForbiddenException("Forbidden")); + + sut.OnException(context); + + Validate(403, context); + } + + [Fact] + public void Should_generate_403_for_SecurityException() + { + var context = E(new SecurityException("Forbidden")); + + sut.OnException(context); + + Validate(403, context); + } + + private static ExceptionContext E(Exception exception) + { + var httpContext = new DefaultHttpContext(); + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor + { + FilterDescriptors = new List() + }); + + return new ExceptionContext(actionContext, new List()) + { + Exception = exception + }; + } + + private static void Validate(int statusCode, ExceptionContext context) + { + var result = context.Result as ObjectResult; + + Assert.Equal(statusCode, result.StatusCode); + Assert.Equal(statusCode, (result.Value as ErrorDto)?.StatusCode); + + Assert.Equal(context.Exception.Message, (result.Value as ErrorDto).Message); + } + } +}