diff --git a/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs b/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs new file mode 100644 index 0000000..b2c1f24 --- /dev/null +++ b/MainProject.Tests/Core/Filters/ValidationActionFilter_Tests.cs @@ -0,0 +1,136 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Common.User; +using DatabaseSqlServer = BasicDotnetTemplate.MainProject.Models.Database.SqlServer; +using Microsoft.AspNetCore.Builder; +using BasicDotnetTemplate.MainProject.Models.Settings; +using BasicDotnetTemplate.MainProject.Utils; +using Microsoft.AspNetCore.Mvc.Filters; +using BasicDotnetTemplate.MainProject.Core.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using BasicDotnetTemplate.MainProject.Core.Filters; +using Newtonsoft.Json; +using BasicDotnetTemplate.MainProject.Models.Api.Base; + + +namespace BasicDotnetTemplate.MainProject.Tests; + +[TestClass] +public class ValidationActionFilter_Tests +{ + private readonly string _requestNotWellFormedMessage = "Request is not well formed"; + + private static ActionExecutingContext CreateContext(ModelStateDictionary modelState, object? requestBody = null) + { + var actionContext = new ActionContext( + new DefaultHttpContext(), + new Microsoft.AspNetCore.Routing.RouteData(), + new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor(), + modelState + ); + var actionArguments = new Dictionary(); + + if (requestBody != null) + { + actionArguments.Add("request", requestBody); + } + return new ActionExecutingContext( + actionContext, + [], + actionArguments, + new Mock().Object + ); + } + + [TestMethod] + public void OnActionExecutionAsync_ModelStateInvalid_ReturnsBadRequestAndDoesNotCallNext() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("MissingProperty", "MissingProperty is required"); + var context = CreateContext(modelState, new { SomeProp = "Value" }); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNotNull(context.Result); + var badRequestResult = context.Result as BadRequestObjectResult; + Assert.IsNotNull(badRequestResult); + Assert.IsNotNull(badRequestResult!.Value); + + ValidationError validationError = (ValidationError)badRequestResult.Value; + Assert.AreEqual(_requestNotWellFormedMessage, validationError?.Message); + Assert.IsNotNull(validationError?.Errors); + Assert.IsFalse(modelState.IsValid); + Assert.IsFalse(nextCalled); + } + + [TestMethod] + public void OnActionExecutionAsync_ModelStateValid_RequestBodyNull_ReturnsBadRequestAndDoesNotCallNext() + { + + var modelState = new ModelStateDictionary(); + + var context = CreateContext(modelState, null); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNotNull(context.Result); + var badRequestResult = context.Result as BadRequestObjectResult; + Assert.IsNotNull(badRequestResult); + Assert.IsNotNull(badRequestResult!.Value); + + ValidationError validationError = (ValidationError)badRequestResult.Value; + Console.WriteLine(JsonConvert.SerializeObject(validationError)); + Assert.AreEqual(_requestNotWellFormedMessage, validationError?.Message); + Assert.IsNull(validationError?.Errors); + Assert.IsTrue(modelState.IsValid); + Assert.IsFalse(nextCalled); + } + + + [TestMethod] + public void OnActionExecutionAsync_ModelStateValid_RequestBodyValid_CallsNextAndDoesNotSetResult() + { + // Arrange + var modelState = new ModelStateDictionary(); + + var requestBody = new TestRequestBody { Value = "Test" }; + var context = CreateContext(modelState, requestBody); + var nextCalled = false; + ActionExecutionDelegate next = () => { + nextCalled = true; + return Task.FromResult(new ActionExecutedContext(context, [], new Mock().Object)); + }; + var filter = new ValidationActionFilter(); + // Act + filter.OnActionExecutionAsync(context, next).GetAwaiter().GetResult(); + // Assert + Assert.IsNull(context.Result); + Assert.IsTrue(nextCalled); + } + + + private class TestRequestBody + { + public string? Value { get; set; } + } + +} diff --git a/MainProject/Core/Filters/ValidationActionFilter.cs b/MainProject/Core/Filters/ValidationActionFilter.cs index a5c6fc9..ef9ca29 100644 --- a/MainProject/Core/Filters/ValidationActionFilter.cs +++ b/MainProject/Core/Filters/ValidationActionFilter.cs @@ -1,3 +1,4 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Base; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System.Threading.Tasks; @@ -12,15 +13,27 @@ namespace BasicDotnetTemplate.MainProject.Core.Filters { if (!context.ModelState.IsValid) { - context.Result = new BadRequestObjectResult(new { message = _requestNotWellFormedMessage, errors = context.ModelState }); + context.Result = new BadRequestObjectResult(new ValidationError + { + Message = _requestNotWellFormedMessage, + Errors = context.ModelState.Where(m => + m.Value != null && m.Value.Errors.Any()) + .ToDictionary( + m => m.Key, + m => m.Value!.Errors.Select(e => e.ErrorMessage).ToList() + ) + }); return; } - var requestBody = context.ActionArguments.Values.FirstOrDefault(arg => arg != null && !arg.GetType().IsPrimitive && !(arg is string)); + var requestBody = context.ActionArguments.Values.FirstOrDefault(arg => arg != null && !arg.GetType().IsPrimitive && arg is not string); if (requestBody == null) { - context.Result = new BadRequestObjectResult(new { message = _requestNotWellFormedMessage }); + context.Result = new BadRequestObjectResult(new ValidationError + { + Message = _requestNotWellFormedMessage + }); return; } diff --git a/MainProject/Models/Api/Base/ValidationError.cs b/MainProject/Models/Api/Base/ValidationError.cs new file mode 100644 index 0000000..c0d76d6 --- /dev/null +++ b/MainProject/Models/Api/Base/ValidationError.cs @@ -0,0 +1,9 @@ +using System; + +namespace BasicDotnetTemplate.MainProject.Models.Api.Base; + +public class ValidationError +{ + public string Message { get; set; } + public Dictionary> Errors { get; set; } +}