mirror of
https://github.com/bitwarden/server.git
synced 2026-06-01 01:55:55 -05:00
* feat(admin-console): Add bulk confirmation and pending auto-confirmation methods for organization users - Implemented ConfirmManyOrganizationUsersAsync to confirm multiple users in a single operation. - Added GetManyPendingAutoConfirmAsync to retrieve users pending automatic confirmation. - Created stored procedures for bulk confirmation and fetching pending users. - Updated relevant repository interfaces and implementations across Dapper and Entity Framework. * refactor(admin-console): Change parameter type for ConfirmManyOrganizationUsersAsync to IReadOnlyCollection - Updated the ConfirmManyOrganizationUsersAsync method signature in the IOrganizationUserRepository and its implementations to use IReadOnlyCollection instead of IEnumerable for better performance and clarity. - Adjusted related repository methods in both Dapper and Entity Framework implementations to reflect this change. - Added unit tests to ensure the new implementation behaves as expected, including scenarios for mixed batches and idempotency. * Remove OrganizationUser_ReadByOrganizationIdStatus stored procedure as part of database cleanup. * Add integration tests for ConfirmManyOrganizationUsers and GetManyPendingAutoConfirm methods - Introduced ConfirmManyOrganizationUsersTests to validate the confirmation of multiple organization users, ensuring only accepted users are confirmed and idempotency is maintained. - Added GetManyPendingAutoConfirmTests to verify retrieval of pending auto-confirm users, ensuring only eligible users are returned based on specific criteria. - Removed duplicate test implementations from OrganizationUserRepositoryTests to maintain clarity and organization in the test suite. * Implement OrganizationUser_UpdateStatusKey stored procedure and update related repository method - Added OrganizationUser_UpdateStatusKey stored procedure to handle updating the status and key of organization users based on a JSON input. - Updated OrganizationUserRepository to call the new stored procedure instead of the previous confirmation procedure. - Modified OrganizationUser_ReadByPendingAutoConfirm stored procedure to filter users by a new type value. - Enhanced integration tests to verify the correct behavior of the updated confirmation logic, ensuring revision dates are accurately tracked. * Refactor OrganizationUser_UpdateStatusKey to OrganizationUser_UpdateManyStatusKey - Renamed the stored procedure to OrganizationUser_UpdateManyStatusKey to better reflect its functionality of updating multiple organization users' statuses. - Updated the OrganizationUserRepository to call the new stored procedure. - Adjusted the migration script to create or alter the procedure accordingly. * Update data type for Key column in AddOrganizationUserUpdateStatusKey migration script - Changed the data type of the Key column from NVARCHAR(MAX) to VARCHAR(MAX) in the UsersToUpdate table and the corresponding JSON parsing logic to improve compatibility and performance. * Updated spacing * Add stored procedures for organization user status updates and retrieval - Created OrganizationUser_UpdateManyStatusKey to update multiple organization users' statuses based on a JSON input, including handling revision dates and tracking updated IDs for idempotency. - Added OrganizationUser_ReadByPendingAutoConfirm to retrieve organization users pending auto-confirmation based on organization ID and specific status and type filters. --------- Co-authored-by: mkincaid-bw <mkincaid@bitwarden.com>
83 lines
3.8 KiB
C#
83 lines
3.8 KiB
C#
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Repositories;
|
|
using Xunit;
|
|
|
|
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.OrganizationUserRepository;
|
|
|
|
public class ConfirmManyOrganizationUsersTests
|
|
{
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmManyOrganizationUsersAsync_MixedBatch_ReturnsOnlyAcceptedIds(
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository)
|
|
{
|
|
// Arrange
|
|
var org = await organizationRepository.CreateTestOrganizationAsync();
|
|
var acceptedUser = await userRepository.CreateTestUserAsync("accepted");
|
|
var confirmedUser = await userRepository.CreateTestUserAsync("confirmed");
|
|
|
|
var acceptedOrgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(org, acceptedUser);
|
|
var confirmedOrgUser = await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(org, confirmedUser);
|
|
|
|
var usersToConfirm = new[]
|
|
{
|
|
new AcceptedOrganizationUserToConfirm { OrganizationUserId = acceptedOrgUser.Id, UserId = acceptedUser.Id, Key = "key-accepted" },
|
|
new AcceptedOrganizationUserToConfirm { OrganizationUserId = confirmedOrgUser.Id, UserId = confirmedUser.Id, Key = "key-already-confirmed" },
|
|
new AcceptedOrganizationUserToConfirm { OrganizationUserId = Guid.NewGuid(), UserId = Guid.NewGuid(), Key = "key-nonexistent" },
|
|
};
|
|
|
|
// Act
|
|
var before = DateTime.UtcNow;
|
|
var confirmedIds = await organizationUserRepository.ConfirmManyOrganizationUsersAsync(usersToConfirm);
|
|
|
|
// Assert — only the Accepted user's ID is returned
|
|
Assert.Single(confirmedIds);
|
|
Assert.Contains(acceptedOrgUser.Id, confirmedIds);
|
|
|
|
// The previously-accepted user is now Confirmed
|
|
var updated = await organizationUserRepository.GetByIdAsync(acceptedOrgUser.Id);
|
|
Assert.NotNull(updated);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, updated.Status);
|
|
Assert.Equal("key-accepted", updated.Key);
|
|
Assert.True(updated.RevisionDate >= before);
|
|
|
|
// The already-confirmed user's status is unchanged
|
|
var unchanged = await organizationUserRepository.GetByIdAsync(confirmedOrgUser.Id);
|
|
Assert.NotNull(unchanged);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, unchanged.Status);
|
|
Assert.Null(unchanged.Key);
|
|
}
|
|
|
|
[Theory, DatabaseData]
|
|
public async Task ConfirmManyOrganizationUsersAsync_Idempotent_SecondCallWithSameBatchReturnsEmpty(
|
|
IOrganizationUserRepository organizationUserRepository,
|
|
IOrganizationRepository organizationRepository,
|
|
IUserRepository userRepository)
|
|
{
|
|
// Arrange
|
|
var org = await organizationRepository.CreateTestOrganizationAsync();
|
|
var user = await userRepository.CreateTestUserAsync();
|
|
var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(org, user);
|
|
|
|
var batch = new[]
|
|
{
|
|
new AcceptedOrganizationUserToConfirm { OrganizationUserId = orgUser.Id, UserId = user.Id, Key = "key" }
|
|
};
|
|
|
|
// Act
|
|
var firstResult = await organizationUserRepository.ConfirmManyOrganizationUsersAsync(batch);
|
|
var secondResult = await organizationUserRepository.ConfirmManyOrganizationUsersAsync(batch);
|
|
|
|
// Assert — first call confirms, second call is a no-op (user is no longer Accepted)
|
|
Assert.Single(firstResult);
|
|
Assert.Contains(orgUser.Id, firstResult);
|
|
Assert.Empty(secondResult);
|
|
|
|
var finalState = await organizationUserRepository.GetByIdAsync(orgUser.Id);
|
|
Assert.NotNull(finalState);
|
|
Assert.Equal(OrganizationUserStatusType.Confirmed, finalState.Status);
|
|
}
|
|
}
|