1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-16 07:50:49 -05:00

[PM-19703] Fix admin count logic to exclude current organization (#5918)

This commit is contained in:
Jimmy Vo 2025-06-13 16:27:48 -04:00 committed by GitHub
parent db77201ca4
commit 4a12120950
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 126 additions and 40 deletions

View File

@ -521,7 +521,9 @@ public class OrganizationUsersController : Controller
.Concat(readonlyCollectionAccess) .Concat(readonlyCollectionAccess)
.ToList(); .ToList();
await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), userId, var existingUserType = organizationUser.Type;
await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), existingUserType, userId,
collectionsToSave, groupsToSave); collectionsToSave, groupsToSave);
} }

View File

@ -177,9 +177,10 @@ public class MembersController : Controller
{ {
return new NotFoundResult(); return new NotFoundResult();
} }
var existingUserType = existingUser.Type;
var updatedUser = model.ToOrganizationUser(existingUser); var updatedUser = model.ToOrganizationUser(existingUser);
var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection()).ToList(); var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection()).ToList();
await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, null, associations, model.Groups); await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, existingUserType, null, associations, model.Groups);
MemberResponseModel response = null; MemberResponseModel response = null;
if (existingUser.UserId.HasValue) if (existingUser.UserId.HasValue)
{ {

View File

@ -1,11 +1,12 @@
#nullable enable #nullable enable
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
public interface IUpdateOrganizationUserCommand public interface IUpdateOrganizationUserCommand
{ {
Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId, Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType, Guid? savingUserId,
List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess); List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess);
} }

View File

@ -55,11 +55,13 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
/// Update an organization user. /// Update an organization user.
/// </summary> /// </summary>
/// <param name="organizationUser">The modified organization user to save.</param> /// <param name="organizationUser">The modified organization user to save.</param>
/// <param name="existingUserType">The current type (member role) of the user.</param>
/// <param name="savingUserId">The userId of the currently logged in user who is making the change.</param> /// <param name="savingUserId">The userId of the currently logged in user who is making the change.</param>
/// <param name="collectionAccess">The user's updated collection access. If set to null, this removes all collection access.</param> /// <param name="collectionAccess">The user's updated collection access. If set to null, this removes all collection access.</param>
/// <param name="groupAccess">The user's updated group access. If set to null, groups are not updated.</param> /// <param name="groupAccess">The user's updated group access. If set to null, groups are not updated.</param>
/// <exception cref="BadRequestException"></exception> /// <exception cref="BadRequestException"></exception>
public async Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId, public async Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType,
Guid? savingUserId,
List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess) List<CollectionAccessSelection>? collectionAccess, IEnumerable<Guid>? groupAccess)
{ {
// Avoid multiple enumeration // Avoid multiple enumeration
@ -83,15 +85,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
throw new NotFoundException(); throw new NotFoundException();
} }
if (organizationUser.UserId.HasValue && organization.PlanType == PlanType.Free && organizationUser.Type is OrganizationUserType.Admin or OrganizationUserType.Owner) await EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(organizationUser, existingUserType, organization);
{
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this.
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(organizationUser.UserId.Value);
if (adminCount > 0)
{
throw new BadRequestException("User can only be an admin of one free organization.");
}
}
if (collectionAccessList.Count != 0) if (collectionAccessList.Count != 0)
{ {
@ -151,6 +145,40 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated);
} }
private async Task EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(OrganizationUser updatedOrgUser, OrganizationUserType existingUserType, Entities.Organization organization)
{
if (organization.PlanType != PlanType.Free)
{
return;
}
if (!updatedOrgUser.UserId.HasValue)
{
return;
}
if (updatedOrgUser.Type is not (OrganizationUserType.Admin or OrganizationUserType.Owner))
{
return;
}
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this.
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(updatedOrgUser.UserId!.Value);
var isCurrentAdminOrOwner = existingUserType is OrganizationUserType.Admin or OrganizationUserType.Owner;
if (isCurrentAdminOrOwner && adminCount <= 1)
{
return;
}
if (!isCurrentAdminOrOwner && adminCount == 0)
{
return;
}
throw new BadRequestException("User can only be an admin of one free organization.");
}
private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser, private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser,
ICollection<CollectionAccessSelection> collectionAccess) ICollection<CollectionAccessSelection> collectionAccess)
{ {

View File

@ -30,6 +30,7 @@ public class OrganizationUserControllerPutTests
OrganizationUser organizationUser, OrganizationAbility organizationAbility, OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId) SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{ {
// Arrange
Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []);
// Authorize all changes for basic happy path test // Authorize all changes for basic happy path test
@ -41,15 +42,18 @@ public class OrganizationUserControllerPutTests
// Save these for later - organizationUser object will be mutated // Save these for later - organizationUser object will be mutated
var orgUserId = organizationUser.Id; var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email; var orgUserEmail = organizationUser.Email;
var existingUserType = organizationUser.Type;
// Act
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
// Assert
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou => await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type && ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager && ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId && ou.Id == orgUserId &&
ou.Email == orgUserEmail), ou.Email == orgUserEmail), existingUserType,
savingUserId, savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas => Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))), cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
@ -77,6 +81,7 @@ public class OrganizationUserControllerPutTests
OrganizationUser organizationUser, OrganizationAbility organizationAbility, OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId) SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{ {
// Arrange
// Updating self // Updating self
organizationUser.UserId = savingUserId; organizationUser.UserId = savingUserId;
organizationAbility.AllowAdminAccessToAllCollectionItems = false; organizationAbility.AllowAdminAccessToAllCollectionItems = false;
@ -88,15 +93,18 @@ public class OrganizationUserControllerPutTests
var orgUserId = organizationUser.Id; var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email; var orgUserEmail = organizationUser.Email;
var existingUserType = organizationUser.Type;
// Act
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
// Assert
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou => await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type && ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager && ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId && ou.Id == orgUserId &&
ou.Email == orgUserEmail), ou.Email == orgUserEmail), existingUserType,
savingUserId, savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas => Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))), cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
@ -110,6 +118,7 @@ public class OrganizationUserControllerPutTests
OrganizationUser organizationUser, OrganizationAbility organizationAbility, OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId) SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{ {
// Arrange
// Updating self // Updating self
organizationUser.UserId = savingUserId; organizationUser.UserId = savingUserId;
organizationAbility.AllowAdminAccessToAllCollectionItems = true; organizationAbility.AllowAdminAccessToAllCollectionItems = true;
@ -121,15 +130,18 @@ public class OrganizationUserControllerPutTests
var orgUserId = organizationUser.Id; var orgUserId = organizationUser.Id;
var orgUserEmail = organizationUser.Email; var orgUserEmail = organizationUser.Email;
var existingUserType = organizationUser.Type;
// Act
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
// Assert
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou => await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type && ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager && ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId && ou.Id == orgUserId &&
ou.Email == orgUserEmail), ou.Email == orgUserEmail), existingUserType,
savingUserId, savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas => Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.All(c => model.Collections.Any(m => m.Id == c.Id))), cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
@ -142,6 +154,7 @@ public class OrganizationUserControllerPutTests
OrganizationUser organizationUser, OrganizationAbility organizationAbility, OrganizationUser organizationUser, OrganizationAbility organizationAbility,
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId) SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
{ {
// Arrange
var editedCollectionId = CoreHelpers.GenerateComb(); var editedCollectionId = CoreHelpers.GenerateComb();
var readonlyCollectionId1 = CoreHelpers.GenerateComb(); var readonlyCollectionId1 = CoreHelpers.GenerateComb();
var readonlyCollectionId2 = CoreHelpers.GenerateComb(); var readonlyCollectionId2 = CoreHelpers.GenerateComb();
@ -194,16 +207,19 @@ public class OrganizationUserControllerPutTests
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), .AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2),
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
.Returns(AuthorizationResult.Failed()); .Returns(AuthorizationResult.Failed());
var existingUserType = organizationUser.Type;
// Act
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
// Assert
// Expect all collection access (modified and unmodified) to be saved // Expect all collection access (modified and unmodified) to be saved
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou => await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
ou.Type == model.Type && ou.Type == model.Type &&
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
ou.AccessSecretsManager == model.AccessSecretsManager && ou.AccessSecretsManager == model.AccessSecretsManager &&
ou.Id == orgUserId && ou.Id == orgUserId &&
ou.Email == orgUserEmail), ou.Email == orgUserEmail), existingUserType,
savingUserId, savingUserId,
Arg.Is<List<CollectionAccessSelection>>(cas => Arg.Is<List<CollectionAccessSelection>>(cas =>
cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) &&

View File

@ -27,8 +27,10 @@ public class UpdateOrganizationUserCommandTests
List<CollectionAccessSelection> collections, List<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider) List<CollectionAccessSelection> collections, List<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider)
{ {
user.Id = default(Guid); user.Id = default(Guid);
var existingUserType = OrganizationUserType.User;
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collections, groups)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collections, groups));
Assert.Contains("invite the user first", exception.Message.ToLowerInvariant()); Assert.Contains("invite the user first", exception.Message.ToLowerInvariant());
} }
@ -37,9 +39,10 @@ public class UpdateOrganizationUserCommandTests
Guid? savingUserId, SutProvider<UpdateOrganizationUserCommand> sutProvider) Guid? savingUserId, SutProvider<UpdateOrganizationUserCommand> sutProvider)
{ {
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(user.Id).Returns(originalUser); sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(user.Id).Returns(originalUser);
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>( await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, null)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, null));
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@ -55,8 +58,10 @@ public class UpdateOrganizationUserCommandTests
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>() .Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
.Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); .Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>( await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@ -76,9 +81,9 @@ public class UpdateOrganizationUserCommandTests
result.RemoveAt(0); result.RemoveAt(0);
return result; return result;
}); });
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>( await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@ -94,8 +99,10 @@ public class UpdateOrganizationUserCommandTests
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>() .Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
.Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); .Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>( await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@ -115,9 +122,9 @@ public class UpdateOrganizationUserCommandTests
result.RemoveAt(0); result.RemoveAt(0);
return result; return result;
}); });
var existingUserType = OrganizationUserType.User;
await Assert.ThrowsAsync<NotFoundException>( await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@ -165,7 +172,9 @@ public class UpdateOrganizationUserCommandTests
.GetCountByFreeOrganizationAdminUserAsync(newUserData.Id) .GetCountByFreeOrganizationAdminUserAsync(newUserData.Id)
.Returns(0); .Returns(0);
await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups); var existingUserType = OrganizationUserType.User;
await sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, savingUser.UserId, collections, groups);
var organizationService = sutProvider.GetDependency<IOrganizationService>(); var organizationService = sutProvider.GetDependency<IOrganizationService>();
await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions( await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions(
@ -184,7 +193,7 @@ public class UpdateOrganizationUserCommandTests
[Theory] [Theory]
[BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Owner)]
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws( public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsNotAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
OrganizationUserType userType, OrganizationUserType userType,
OrganizationUser oldUserData, OrganizationUser oldUserData,
OrganizationUser newUserData, OrganizationUser newUserData,
@ -199,10 +208,39 @@ public class UpdateOrganizationUserCommandTests
sutProvider.GetDependency<IOrganizationUserRepository>() sutProvider.GetDependency<IOrganizationUserRepository>()
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value) .GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
.Returns(1); .Returns(1);
var existingUserType = OrganizationUserType.User;
// Assert // Assert
var exception = await Assert.ThrowsAsync<BadRequestException>( var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(newUserData, null, null, null)); () => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Owner)]
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
OrganizationUserType newUserType,
OrganizationUserType existingUserType,
OrganizationUser oldUserData,
OrganizationUser newUserData,
Organization organization,
SutProvider<UpdateOrganizationUserCommand> sutProvider)
{
organization.PlanType = PlanType.Free;
newUserData.Type = newUserType;
Setup(sutProvider, organization, newUserData, oldUserData);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
.Returns(2);
// Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
Assert.Contains("User can only be an admin of one free organization.", exception.Message); Assert.Contains("User can only be an admin of one free organization.", exception.Message);
} }