diff --git a/Api.SeaHavenIndustries.Tests/TeamMemberServiceTests.cs b/Api.SeaHavenIndustries.Tests/TeamMemberServiceTests.cs index e2295ff..04024f6 100644 --- a/Api.SeaHavenIndustries.Tests/TeamMemberServiceTests.cs +++ b/Api.SeaHavenIndustries.Tests/TeamMemberServiceTests.cs @@ -159,6 +159,14 @@ public sealed class TeamMemberServiceTests userManager.Setup(manager => manager.FindByIdAsync(user.Id)).ReturnsAsync(user); userManager.Setup(manager => manager.GetRolesAsync(user)).ReturnsAsync(new List { "Dispatcher" }); userManager.Setup(manager => manager.FindByEmailAsync("new@example.com")).ReturnsAsync((ApplicationUser?)null); + userManager.Setup(manager => manager.SetEmailAsync(user, "new@example.com")) + .Callback((member, email) => member.Email = email) + .ReturnsAsync(IdentityResult.Success); + userManager.Setup(manager => manager.SetUserNameAsync(user, "new@example.com")) + .Callback((member, email) => member.UserName = email) + .ReturnsAsync(IdentityResult.Success); + userManager.Setup(manager => manager.UpdateSecurityStampAsync(user)) + .ReturnsAsync(IdentityResult.Success); roleManager.Setup(manager => manager.RoleExistsAsync("Dispatcher")).ReturnsAsync(true); areas.Setup(data => data.GetForUserAsync(user.Id, It.IsAny())) .ReturnsAsync(new[] { "Central" }); @@ -202,11 +210,60 @@ public sealed class TeamMemberServiceTests result.Member.ServiceAreas.Should().Equal("Central"); result.Member.Permissions.Should().ContainSingle(permission => permission.PermissionKey == "createSites"); userData.Verify(data => data.UpdateUserAsync(user, It.IsAny()), Times.Once); + userManager.Verify(manager => manager.UpdateSecurityStampAsync(user), Times.Once); areas.Verify(data => data.ReplaceAsync(user.Id, It.Is>(value => value.SequenceEqual(new[] { "West" })), It.IsAny()), Times.Once); overrides.Verify(data => data.ClearOverridesAsync(user.Id, It.IsAny()), Times.Once); overrides.Verify(data => data.SetOverridesAsync(user.Id, It.Is>(value => value["createSites"] == UserPermissionState.Deny), It.IsAny()), Times.Once); } + [Fact] + public async Task Update_LeavesOmittedOverridesUnchanged() + { + var service = NewService( + out var userManager, + out var roleManager, + out var areas, + out var overrides, + out var userData, + out var permissions); + var user = new ApplicationUser + { + Id = "dispatcher", + FirstName = "Taylor", + Email = "taylor@example.com", + Color = "#F59E0B", + UniqueName = "Active" + }; + userManager.Setup(manager => manager.FindByIdAsync(user.Id)).ReturnsAsync(user); + userManager.Setup(manager => manager.GetRolesAsync(user)).ReturnsAsync(new List { "Dispatcher" }); + roleManager.Setup(manager => manager.RoleExistsAsync("Dispatcher")).ReturnsAsync(true); + areas.Setup(data => data.GetForUserAsync(user.Id, It.IsAny())).ReturnsAsync(Array.Empty()); + permissions.Setup(data => data.GetProfileAsync(user.Id, It.IsAny(), It.IsAny())) + .ReturnsAsync(TeamPermissionResult.Success(new TeamPermissionProfileDTO + { + UserId = user.Id, + Permissions = Array.Empty() + })); + + var result = await service.UpdateAsync( + user.Id, + new UpdateTeamMemberRequestDTO + { + Name = "Taylor", + Role = "Dispatcher", + Color = "#F59E0B", + Email = user.Email, + ServiceAreas = new[] { "East" } + }, + Admin(), + CancellationToken.None); + + result.Success.Should().BeTrue(); + overrides.Verify(data => data.ClearOverridesAsync(It.IsAny(), It.IsAny()), Times.Never); + overrides.Verify(data => data.SetOverridesAsync(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never); + userData.Verify(data => data.UpdateUserAsync(user, It.IsAny()), Times.Once); + } + private static TeamMemberService NewService( out Mock> userManager, out Mock> roleManager, diff --git a/SeaHaven.Services/Implementation/TeamMemberService.cs b/SeaHaven.Services/Implementation/TeamMemberService.cs index 5e85e89..aeeef94 100644 --- a/SeaHaven.Services/Implementation/TeamMemberService.cs +++ b/SeaHaven.Services/Implementation/TeamMemberService.cs @@ -168,27 +168,49 @@ public sealed class TeamMemberService : ITeamMemberService return OperationFailure(roleError); user.FirstName = candidate.Name!.Trim(); - user.Email = email; - user.UserName = email; - user.NormalizedEmail = email!.ToUpperInvariant(); - user.NormalizedUserName = email.ToUpperInvariant(); user.Contact = candidate.Phone?.Trim(); user.PhoneNumber = candidate.Phone?.Trim(); user.Color = color; user.UniqueName = request.IsActive ? ActiveStatus : "Inactive"; user.IsDeleted = !request.IsActive; + if (!string.Equals(user.Email, email, StringComparison.OrdinalIgnoreCase)) + { + var emailResult = await _userManager.SetEmailAsync(user, email!); + if (!emailResult.Succeeded) + return OperationFailure(emailResult.Errors.FirstOrDefault()?.Description ?? "Unable to update email."); + + var userNameResult = await _userManager.SetUserNameAsync(user, email!); + if (!userNameResult.Succeeded) + return OperationFailure(userNameResult.Errors.FirstOrDefault()?.Description ?? "Unable to update email."); + + var securityStampResult = await _userManager.UpdateSecurityStampAsync(user); + if (!securityStampResult.Succeeded) + return OperationFailure(securityStampResult.Errors.FirstOrDefault()?.Description ?? "Unable to update email."); + } + if (roleChanged) { if (existingRoles.Count > 0) - await _userManager.RemoveFromRolesAsync(user, existingRoles); - await _userManager.AddToRoleAsync(user, role!); + { + var removeRoleResult = await _userManager.RemoveFromRolesAsync(user, existingRoles); + if (!removeRoleResult.Succeeded) + return OperationFailure(removeRoleResult.Errors.FirstOrDefault()?.Description ?? "Unable to update role."); + } + + var addRoleResult = await _userManager.AddToRoleAsync(user, role!); + if (!addRoleResult.Succeeded) + return OperationFailure(addRoleResult.Errors.FirstOrDefault()?.Description ?? "Unable to update role."); } await _userDataService.UpdateUserAsync(user, cancellationToken); await _areaDataService.ReplaceAsync(user.Id, areas!, cancellationToken); - await _permissionDataService.ClearOverridesAsync(user.Id, cancellationToken); - await _permissionDataService.SetOverridesAsync(user.Id, overrides!, cancellationToken); + if (roleChanged || request.PermissionOverrides is not null) + { + await _permissionDataService.ClearOverridesAsync(user.Id, cancellationToken); + if (request.PermissionOverrides is not null) + await _permissionDataService.SetOverridesAsync(user.Id, overrides!, cancellationToken); + } return await OperationSuccessAsync(user, caller, cancellationToken); }