From b3e9a2eec3f8aa350162200d49f7217568930214 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Wed, 16 Sep 2026 22:30:32 -0300 Subject: [PATCH] fix(services): allow admins to reactivate services --- .../ServicesRegistryServiceTests.cs | 32 +++++++++++++++++++ .../DTOs/ServicesRegistryDTOs.cs | 1 + .../Implementation/ServicesRegistryService.cs | 11 +++++++ 3 files changed, 44 insertions(+) diff --git a/Api.SeaHavenIndustries.Tests/ServicesRegistryServiceTests.cs b/Api.SeaHavenIndustries.Tests/ServicesRegistryServiceTests.cs index 6d7086d..9eba754 100644 --- a/Api.SeaHavenIndustries.Tests/ServicesRegistryServiceTests.cs +++ b/Api.SeaHavenIndustries.Tests/ServicesRegistryServiceTests.cs @@ -260,6 +260,38 @@ public class ServicesRegistryServiceTests updated.IsActive.Should().BeFalse("update preserves the persisted active state"); } + [Fact] + public async Task Update_WhenAdminRequestsActiveState_ReactivatesService() + { + using var ctx = NewContext(); + var service = NewService(ctx); + var created = await service.CreateAsync(Admin(), Input("Inactive service"), CancellationToken.None); + await service.DeactivateAsync(Admin(), created.Id, CancellationToken.None); + + var input = Input("Inactive service"); + input.IsActive = true; + var updated = await service.UpdateAsync(Admin(), created.Id, input, CancellationToken.None); + + updated.IsActive.Should().BeTrue(); + ctx.Services.Single().IsActive.Should().BeTrue(); + } + + [Fact] + public async Task Update_WhenSchedulerRequestsActiveState_ThrowsForbidden() + { + using var ctx = NewContext(); + var service = NewService(ctx); + var created = await service.CreateAsync(Admin(), Input("Inactive service"), CancellationToken.None); + await service.DeactivateAsync(Admin(), created.Id, CancellationToken.None); + + var input = Input("Inactive service"); + input.IsActive = true; + var act = () => service.UpdateAsync(Scheduler(), created.Id, input, CancellationToken.None); + + (await act.Should().ThrowAsync()).Which.Code.Should().Be("Forbidden"); + ctx.Services.Single().IsActive.Should().BeFalse(); + } + [Fact] public async Task Update_WhenNotFound_ThrowsNotFound() { diff --git a/SeaHaven.Services/DTOs/ServicesRegistryDTOs.cs b/SeaHaven.Services/DTOs/ServicesRegistryDTOs.cs index 70fe43b..acb1bb4 100644 --- a/SeaHaven.Services/DTOs/ServicesRegistryDTOs.cs +++ b/SeaHaven.Services/DTOs/ServicesRegistryDTOs.cs @@ -28,6 +28,7 @@ namespace SeaHaven.Services.DTOs public bool RequiresCompletionDocument { get; set; } public int? CompletionDocTemplateId { get; set; } public List? SupportedWorkOrderTypes { get; set; } + public bool? IsActive { get; set; } } public class ServicesRegistryValidationErrorDto diff --git a/SeaHaven.Services/Implementation/ServicesRegistryService.cs b/SeaHaven.Services/Implementation/ServicesRegistryService.cs index b6c7dd0..1a702bb 100644 --- a/SeaHaven.Services/Implementation/ServicesRegistryService.cs +++ b/SeaHaven.Services/Implementation/ServicesRegistryService.cs @@ -108,6 +108,11 @@ namespace SeaHaven.Services.Implementation service.IconKey = iconKey; service.RequiresCompletionDocument = input.RequiresCompletionDocument; service.CompletionDocTemplateId = template?.Id; + if (input.IsActive.HasValue) + { + EnsureCanChangeActiveState(user); + service.IsActive = input.IsActive.Value; + } try { @@ -138,6 +143,12 @@ namespace SeaHaven.Services.Implementation throw new ServicesRegistryValidationException("Forbidden", "Scheduler or Admin role is required."); } + private static void EnsureCanChangeActiveState(ClaimsPrincipal user) + { + if (user is null || !user.IsInRole(AdminRole)) + throw new ServicesRegistryValidationException("Forbidden", "Only administrators can change service active state."); + } + private static ServicesRegistryValidationException DuplicateName() => new("DuplicateName", "A service with this name already exists.");