1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-25 05:21:03 -05:00

Added more tests for the updates

This commit is contained in:
jrmccannon 2025-02-21 16:08:07 -06:00
parent 1dbe37a250
commit bd5189491e
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
5 changed files with 321 additions and 53 deletions

View File

@ -78,7 +78,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
return new Success<IEnumerable<OrganizationUser>>([]);
}
// Validate we can add those seats
var validationResult = await inviteUsersValidation.ValidateAsync(new InviteUserOrganizationValidationRequest
{
Invites = invitesToSend.ToArray(),

View File

@ -23,7 +23,7 @@ public class PasswordManagerSubscriptionUpdate
public int? UpdatedSeatTotal => Seats + SeatsRequiredToAdd;
public bool MaxSeatsReached => Seats.HasValue && MaxAutoScaleSeats.HasValue && Seats.Value == MaxAutoScaleSeats.Value;
public bool MaxSeatsReached => UpdatedSeatTotal.HasValue && MaxAutoScaleSeats.HasValue && UpdatedSeatTotal.Value == MaxAutoScaleSeats.Value;
public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; }

View File

@ -0,0 +1,23 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers;
public static class InviteScimOrganizationUserRequestHelpers
{
public static InviteScimOrganizationUserRequest GetInviteScimOrganizationUserRequestDefault(string email,
OrganizationDto organizationDto, DateTimeOffset performedAt, string externalId) =>
InviteScimOrganizationUserRequest.Create(
OrganizationUserSingleEmailInvite.Create(
email,
[],
OrganizationUserType.User,
new Permissions(),
false),
organizationDto,
performedAt,
externalId
);
}

View File

@ -0,0 +1,52 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers;
public static class InviteUserOrganizationValidationRequestHelpers
{
public static InviteUserOrganizationValidationRequest GetInviteValidationRequestMock(InviteScimOrganizationUserRequest request,
OrganizationDto organizationDto) =>
new()
{
Invites =
[
OrganizationUserInviteDto.Create(request.Invite.Email,
OrganizationUserInvite.Create(request.Invite, request.ExternalId), organizationDto.OrganizationId)
],
Organization = organizationDto,
PerformedBy = Guid.Empty,
PerformedAt = request.PerformedAt,
OccupiedPmSeats = 0,
OccupiedSmSeats = 0,
PasswordManagerSubscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0),
SecretsManagerSubscriptionUpdate = SecretsManagerSubscriptionUpdate.Create(organizationDto, 0, 0, 0)
};
public static InviteUserOrganizationValidationRequest WithPasswordManagerUpdate(this InviteUserOrganizationValidationRequest request, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate) =>
new()
{
Invites = request.Invites,
Organization = request.Organization,
PerformedBy = request.PerformedBy,
PerformedAt = request.PerformedAt,
OccupiedPmSeats = request.OccupiedPmSeats,
OccupiedSmSeats = request.OccupiedSmSeats,
PasswordManagerSubscriptionUpdate = passwordManagerSubscriptionUpdate,
SecretsManagerSubscriptionUpdate = request.SecretsManagerSubscriptionUpdate
};
public static InviteUserOrganizationValidationRequest WithSecretsManagerUpdate(this InviteUserOrganizationValidationRequest request, SecretsManagerSubscriptionUpdate secretsManagerSubscriptionUpdate) =>
new()
{
Invites = request.Invites,
Organization = request.Organization,
PerformedBy = request.PerformedBy,
PerformedAt = request.PerformedAt,
OccupiedPmSeats = request.OccupiedPmSeats,
OccupiedSmSeats = request.OccupiedSmSeats,
PasswordManagerSubscriptionUpdate = request.PasswordManagerSubscriptionUpdate,
SecretsManagerSubscriptionUpdate = secretsManagerSubscriptionUpdate
};
}

View File

@ -8,7 +8,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Commands;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
@ -18,6 +18,8 @@ using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteScimOrganizationUserRequestHelpers;
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@ -39,37 +41,18 @@ public class InviteOrganizationUserCommandTests
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = InviteScimOrganizationUserRequest.Create(
OrganizationUserSingleEmailInvite.Create(
user.Email,
[],
OrganizationUserType.User,
new Permissions(),
false),
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId
);
externalId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
.Returns([user.Email]);
var validationRequest = new InviteUserOrganizationValidationRequest
{
Invites = [],
Organization = organizationDto,
PerformedBy = Guid.Empty,
PerformedAt = request.PerformedAt,
OccupiedPmSeats = 0,
OccupiedSmSeats = 0,
PasswordManagerSubscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0),
SecretsManagerSubscriptionUpdate = SecretsManagerSubscriptionUpdate.Create(organizationDto, 0, 0, 0)
};
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(validationRequest));
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, organizationDto)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@ -93,29 +76,22 @@ public class InviteOrganizationUserCommandTests
[Theory]
[BitAutoData]
public async Task InviteScimOrganizationUserAsync_WhenEmailDoesNotExistAndRequestIsValid_ThenUserIsSavedAndInviteIsSent(
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
{
// Arrange
user.Email = address.Address;
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = InviteScimOrganizationUserRequest.Create(
OrganizationUserSingleEmailInvite.Create(
user.Email,
[],
OrganizationUserType.User,
new Permissions(),
false),
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId
);
externalId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
@ -125,21 +101,9 @@ public class InviteOrganizationUserCommandTests
.GetByIdAsync(organization.Id)
.Returns(organization);
var validationRequest = new InviteUserOrganizationValidationRequest
{
Invites = [OrganizationUserInviteDto.Create(request.Invite.Email, OrganizationUserInvite.Create(request.Invite, request.ExternalId), organization.Id)],
Organization = organizationDto,
PerformedBy = Guid.Empty,
PerformedAt = request.PerformedAt,
OccupiedPmSeats = 0,
OccupiedSmSeats = 0,
PasswordManagerSubscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, 0, 0),
SecretsManagerSubscriptionUpdate = SecretsManagerSubscriptionUpdate.Create(organizationDto, 0, 0, 0)
};
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(validationRequest));
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, organizationDto)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@ -158,4 +122,234 @@ public class InviteOrganizationUserCommandTests
invite.Organization == organization &&
invite.Users.Count(x => x.Email == user.Email) == 1));
}
[Theory]
[BitAutoData]
public async Task InviteScimOrganizationUserAsync_WhenEmailIsNewAndRequestIsInvalid_ThenFailureIsReturnedWithValidationFailureReason(
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
{
// Arrange
const string errorMessage = "Org cannot add user for some given reason";
user.Email = address.Address;
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.SelectKnownEmailsAsync(organization.Id, Arg.Any<IEnumerable<string>>(), false)
.Returns([]);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Invalid<InviteUserOrganizationValidationRequest>(errorMessage));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
// Assert
Assert.IsType<Failure<OrganizationUser>>(result);
var failure = result as Failure<OrganizationUser>;
Assert.Equal(errorMessage, failure.ErrorMessage);
sutProvider.GetDependency<IOrganizationUserRepository>()
.DidNotReceive()
.CreateManyAsync(Arg.Any<IEnumerable<CreateOrganizationUser>>());
sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
.DidNotReceive()
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
}
[Theory]
[BitAutoData]
public async Task InviteScimOrganizationUserAsync_WhenValidInviteCausesOrganizationToReachMaxSeats_ThenOrganizationOwnersShouldBeNotified(
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
OrganizationUserUserDetails ownerDetails,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
{
// Arrange
user.Email = address.Address;
organization.Seats = 1;
organization.MaxAutoscaleSeats = 2;
ownerDetails.Type = OrganizationUserType.Owner;
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId);
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUserRepository
.SelectKnownEmailsAsync(organizationDto.OrganizationId, Arg.Any<IEnumerable<string>>(), false)
.Returns([]);
orgUserRepository
.GetManyByMinimumRoleAsync(organizationDto.OrganizationId, OrganizationUserType.Owner)
.Returns([ownerDetails]);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, organizationDto)
.WithPasswordManagerUpdate(PasswordManagerSubscriptionUpdate.Create(organizationDto, organization.Seats.Value, 1))));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
// Assert
Assert.IsType<Success<OrganizationUser>>(result);
sutProvider.GetDependency<IMailService>()
.Received(1)
.SendOrganizationMaxSeatLimitReachedEmailAsync(organization,
organizationDto.MaxAutoScaleSeats.Value,
Arg.Is<IEnumerable<string>>(emails => emails.Any(email => email == ownerDetails.Email)));
}
[Theory]
[BitAutoData]
public async Task InviteScimOrganizationUserAsync_WhenValidInviteIncreasesSeats_ThenSeatTotalShouldBeUpdated(
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
OrganizationUserUserDetails ownerDetails,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
{
// Arrange
user.Email = address.Address;
organization.Seats = 1;
organization.MaxAutoscaleSeats = 2;
ownerDetails.Type = OrganizationUserType.Owner;
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId);
var passwordManagerUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, organization.Seats.Value, 1);
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUserRepository
.SelectKnownEmailsAsync(organizationDto.OrganizationId, Arg.Any<IEnumerable<string>>(), false)
.Returns([]);
orgUserRepository
.GetManyByMinimumRoleAsync(organizationDto.OrganizationId, OrganizationUserType.Owner)
.Returns([ownerDetails]);
var orgRepository = sutProvider.GetDependency<IOrganizationRepository>();
orgRepository.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, organizationDto)
.WithPasswordManagerUpdate(passwordManagerUpdate)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
// Assert
Assert.IsType<Success<OrganizationUser>>(result);
sutProvider.GetDependency<IPaymentService>()
.AdjustSeatsAsync(organization, organizationDto.Plan, passwordManagerUpdate.SeatsRequiredToAdd);
orgRepository.Received(1).ReplaceAsync(Arg.Is<Organization>(x => x.Seats == passwordManagerUpdate.UpdatedSeatTotal));
sutProvider.GetDependency<IApplicationCacheService>()
.Received(1)
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(x => x.Seats == passwordManagerUpdate.UpdatedSeatTotal));
}
[Theory]
[BitAutoData]
public async Task InviteScimOrganizationUserAsync_WhenValidInviteIncreasesSecretsManagerSeats_ThenSecretsManagerShouldBeUpdated(
MailAddress address,
Organization organization,
OrganizationUser user,
FakeTimeProvider timeProvider,
string externalId,
OrganizationUserUserDetails ownerDetails,
SutProvider<InviteOrganizationUsersCommand> sutProvider)
{
// Arrange
user.Email = address.Address;
organization.Seats = 1;
organization.SmSeats = 1;
organization.MaxAutoscaleSeats = 2;
ownerDetails.Type = OrganizationUserType.Owner;
var organizationDto = OrganizationDto.FromOrganization(organization);
var request = GetInviteScimOrganizationUserRequestDefault(user.Email,
organizationDto,
timeProvider.GetUtcNow(),
externalId);
var secretsManagerSubscriptionUpdate = SecretsManagerSubscriptionUpdate.Create(
organizationDto,
organization.SmSeats.Value,
1,
organization.Seats.Value);
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUserRepository
.SelectKnownEmailsAsync(organizationDto.OrganizationId, Arg.Any<IEnumerable<string>>(), false)
.Returns([]);
orgUserRepository
.GetManyByMinimumRoleAsync(organizationDto.OrganizationId, OrganizationUserType.Owner)
.Returns([ownerDetails]);
var orgRepository = sutProvider.GetDependency<IOrganizationRepository>();
orgRepository.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IInviteUsersValidation>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, organizationDto)
.WithSecretsManagerUpdate(secretsManagerSubscriptionUpdate)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
// Assert
Assert.IsType<Success<OrganizationUser>>(result);
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.Received(1)
.UpdateSubscriptionAsync(Arg.Is<Core.Models.Business.SecretsManagerSubscriptionUpdate>(update =>
update.SmSeats == secretsManagerSubscriptionUpdate.UpdatedSeatTotal));
}
}