From 623810da45cbb1d31ed992731f30d55e9a8cdd9b Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Tue, 30 Jun 2026 13:20:41 -0300 Subject: [PATCH] =?UTF-8?q?feat(phase-0):=20finalize=20foundation=20?= =?UTF-8?q?=E2=80=94=20authorize,=20concurrency=20filter,=20isolated=20bui?= =?UTF-8?q?ld?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/WorkOrderController.cs | 2 +- .../Filters/ConcurrencyExceptionFilter.cs | 22 ++++++++ Api.SeaHavenIndustries/Program.cs | 5 +- .../ConcurrencyExceptionFilterTests.cs | 50 +++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 Api.SeaHavenIndustries/Filters/ConcurrencyExceptionFilter.cs create mode 100644 SeaHavenIndustries.Tests/ConcurrencyExceptionFilterTests.cs diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs index 57eb4b8..9c3b181 100644 --- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs +++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs @@ -21,7 +21,7 @@ using SeaHaven.DataServices.Interfaces; namespace Api.SeaHavenIndustries.Controllers { - // [Authorize] + [Authorize] [ApiController] [Route("api/WorkOrder")] [Route("api/workorders")] diff --git a/Api.SeaHavenIndustries/Filters/ConcurrencyExceptionFilter.cs b/Api.SeaHavenIndustries/Filters/ConcurrencyExceptionFilter.cs new file mode 100644 index 0000000..6b84ee6 --- /dev/null +++ b/Api.SeaHavenIndustries/Filters/ConcurrencyExceptionFilter.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.EntityFrameworkCore; + +namespace Api.SeaHavenIndustries.Filters +{ + public class ConcurrencyExceptionFilter : IExceptionFilter + { + public void OnException(ExceptionContext context) + { + if (context.Exception is not DbUpdateConcurrencyException) return; + + context.Result = new ConflictObjectResult(new + { + status = "Conflict", + message = "The record was modified by another user. Refresh and retry.", + code = 409 + }); + context.ExceptionHandled = true; + } + } +} diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs index a826847..6dc0e95 100644 --- a/Api.SeaHavenIndustries/Program.cs +++ b/Api.SeaHavenIndustries/Program.cs @@ -22,7 +22,10 @@ builder.Services.AddIdentity(options => .AddEntityFrameworkStores() .AddDefaultTokenProviders(); -builder.Services.AddControllers().AddJsonOptions(options => +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}).AddJsonOptions(options => { options.JsonSerializerOptions.MaxDepth = 264; // or any other desired value }); diff --git a/SeaHavenIndustries.Tests/ConcurrencyExceptionFilterTests.cs b/SeaHavenIndustries.Tests/ConcurrencyExceptionFilterTests.cs new file mode 100644 index 0000000..6ccfaf1 --- /dev/null +++ b/SeaHavenIndustries.Tests/ConcurrencyExceptionFilterTests.cs @@ -0,0 +1,50 @@ +using Api.SeaHavenIndustries.Filters; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; + +namespace SeaHavenIndustries.Tests; + +public class ConcurrencyExceptionFilterTests +{ + [Fact] + public void OnException_DbUpdateConcurrencyException_Returns409Conflict() + { + var filter = new ConcurrencyExceptionFilter(); + var context = CreateExceptionContext(new DbUpdateConcurrencyException()); + + filter.OnException(context); + + Assert.True(context.ExceptionHandled); + var result = Assert.IsType(context.Result); + Assert.Equal(StatusCodes.Status409Conflict, result.StatusCode); + } + + [Fact] + public void OnException_OtherException_DoesNotHandle() + { + var filter = new ConcurrencyExceptionFilter(); + var context = CreateExceptionContext(new InvalidOperationException("other")); + + filter.OnException(context); + + Assert.False(context.ExceptionHandled); + Assert.Null(context.Result); + } + + private static ExceptionContext CreateExceptionContext(Exception exception) + { + var actionContext = new ActionContext( + new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor()); + + return new ExceptionContext(actionContext, []) + { + Exception = exception + }; + } +}