mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
fix(team-members): preserve update semantics and validate identity results (SH-326)
This commit is contained in:
parent
b9c916cb22
commit
a0b9fabb69
2 changed files with 87 additions and 8 deletions
|
|
@ -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<string> { "Dispatcher" });
|
||||
userManager.Setup(manager => manager.FindByEmailAsync("new@example.com")).ReturnsAsync((ApplicationUser?)null);
|
||||
userManager.Setup(manager => manager.SetEmailAsync(user, "new@example.com"))
|
||||
.Callback<ApplicationUser, string>((member, email) => member.Email = email)
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
userManager.Setup(manager => manager.SetUserNameAsync(user, "new@example.com"))
|
||||
.Callback<ApplicationUser, string>((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<CancellationToken>()))
|
||||
.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<CancellationToken>()), Times.Once);
|
||||
userManager.Verify(manager => manager.UpdateSecurityStampAsync(user), Times.Once);
|
||||
areas.Verify(data => data.ReplaceAsync(user.Id, It.Is<IReadOnlyCollection<string>>(value => value.SequenceEqual(new[] { "West" })), It.IsAny<CancellationToken>()), Times.Once);
|
||||
overrides.Verify(data => data.ClearOverridesAsync(user.Id, It.IsAny<CancellationToken>()), Times.Once);
|
||||
overrides.Verify(data => data.SetOverridesAsync(user.Id, It.Is<IReadOnlyDictionary<string, UserPermissionState>>(value => value["createSites"] == UserPermissionState.Deny), It.IsAny<CancellationToken>()), 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<string> { "Dispatcher" });
|
||||
roleManager.Setup(manager => manager.RoleExistsAsync("Dispatcher")).ReturnsAsync(true);
|
||||
areas.Setup(data => data.GetForUserAsync(user.Id, It.IsAny<CancellationToken>())).ReturnsAsync(Array.Empty<string>());
|
||||
permissions.Setup(data => data.GetProfileAsync(user.Id, It.IsAny<ClaimsPrincipal>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(TeamPermissionResult<TeamPermissionProfileDTO>.Success(new TeamPermissionProfileDTO
|
||||
{
|
||||
UserId = user.Id,
|
||||
Permissions = Array.Empty<TeamPermissionValueDTO>()
|
||||
}));
|
||||
|
||||
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<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
overrides.Verify(data => data.SetOverridesAsync(It.IsAny<string>(), It.IsAny<IReadOnlyDictionary<string, UserPermissionState>>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
userData.Verify(data => data.UpdateUserAsync(user, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
private static TeamMemberService NewService(
|
||||
out Mock<UserManager<ApplicationUser>> userManager,
|
||||
out Mock<RoleManager<IdentityRole>> roleManager,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue