From 1bd21807456d5425f644b7c90a4e87ec1b62e52b Mon Sep 17 00:00:00 2001 From: Rex Lorenzo Date: Fri, 28 Aug 2026 11:04:39 -0700 Subject: [PATCH] refactor(raps): derive MembersController from ApiController MembersController was the only RAPS API controller on ControllerBase, so it missed [ApiResponse], [ApiExceptionFilter], and [ApiSessionUpdateFilter]: it returned bare arrays where every sibling returns the { success, result } envelope, its exceptions skipped the standard error shape and correlation id, and calls to it did not refresh the session timeout. - viperFetch already unwraps both shapes, so the consuming pages need no change; the envelope is transparent to them - Pin the base class in RapsControllerAuthorizationTests so the drift cannot recur --- test/RAPS/RapsControllerAuthorizationTests.cs | 21 +++++++++++++++++++ .../RAPS/Controllers/MembersController.cs | 4 ++-- .../RAPS/Views/Permissions/Members.cshtml | 5 ++--- web/Areas/RAPS/Views/Roles/Members.cshtml | 5 ++--- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/test/RAPS/RapsControllerAuthorizationTests.cs b/test/RAPS/RapsControllerAuthorizationTests.cs index 5fc92de44..b075c57fe 100644 --- a/test/RAPS/RapsControllerAuthorizationTests.cs +++ b/test/RAPS/RapsControllerAuthorizationTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using Microsoft.AspNetCore.Authorization; using Viper.Areas.RAPS.Controllers; +using Viper.Classes; namespace Viper.test.RAPS { @@ -38,6 +39,26 @@ public void EveryRapsController_RequiresARapsRole(Type controller) Assert.False(string.IsNullOrEmpty(roles), $"{controller.Name} is missing an [Authorize(Roles = ...)] restriction"); } + /// + /// MembersController drifted onto ControllerBase and so missed the ApiController filters: + /// it returned bare arrays where every sibling returned the { success, result } envelope, + /// and its exceptions skipped the standard error shape and correlation id. + /// + [Theory] + [MemberData(nameof(RapsControllers))] + public void EveryRapsApiController_DerivesFromApiController(Type controller) + { + // The page controller renders views, so it is an AreaController, not an API controller. + if (typeof(AreaController).IsAssignableFrom(controller)) + { + return; + } + + Assert.True(typeof(ApiController).IsAssignableFrom(controller), + $"{controller.Name} does not derive from ApiController, so it misses [ApiResponse], " + + "[ApiExceptionFilter] and [ApiSessionUpdateFilter]"); + } + [Fact] public void RapsControllersAreDiscovered() { diff --git a/web/Areas/RAPS/Controllers/MembersController.cs b/web/Areas/RAPS/Controllers/MembersController.cs index b97610e2d..2faef3df3 100644 --- a/web/Areas/RAPS/Controllers/MembersController.cs +++ b/web/Areas/RAPS/Controllers/MembersController.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Viper.Areas.RAPS.Models; using Viper.Areas.RAPS.Services; +using Viper.Classes; using Viper.Classes.SQLContext; using Web.Authorization; @@ -11,9 +12,8 @@ namespace Viper.Areas.RAPS.Controllers { [Route("raps/{Instance=VIPER}/[controller]")] - [ApiController] [Authorize(Roles = "VMDO SVM-IT,RAPS Users", Policy = "2faAuthentication")] - public class MembersController : ControllerBase + public class MembersController : ApiController { private readonly RAPSContext _context; private readonly RAPSSecurityService _securityService; diff --git a/web/Areas/RAPS/Views/Permissions/Members.cshtml b/web/Areas/RAPS/Views/Permissions/Members.cshtml index 9cef4ba72..8ac60170c 100644 --- a/web/Areas/RAPS/Views/Permissions/Members.cshtml +++ b/web/Areas/RAPS/Views/Permissions/Members.cshtml @@ -179,10 +179,9 @@ } update(() => { - fetch("Members?search=" + val) - .then(r => r.json()) + viperFetch(this, "Members?search=" + val) .then(data => - this.memberSearchResults = data + this.memberSearchResults = (data ?? []) .map(m => ({ label: m.displayLastName + ", " + m.displayFirstName, value: m.memberId })) ) }) diff --git a/web/Areas/RAPS/Views/Roles/Members.cshtml b/web/Areas/RAPS/Views/Roles/Members.cshtml index 7d6162e79..57fcc1b8d 100644 --- a/web/Areas/RAPS/Views/Roles/Members.cshtml +++ b/web/Areas/RAPS/Views/Roles/Members.cshtml @@ -172,10 +172,9 @@ } update(() => { - fetch("Members?search=" + val) - .then(r => r.json()) + viperFetch(this, "Members?search=" + val) .then(data => - this.memberSearchResults = data + this.memberSearchResults = (data ?? []) .map(m => ({ label: m.displayLastName + ", " + m.displayFirstName, value: m.memberId })) ) })