1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Code Review changes.

This commit is contained in:
jrmccannon 2025-03-27 15:38:32 -05:00
parent 1384d9c58e
commit 46d36b1ef8
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
17 changed files with 220 additions and 130 deletions

View File

@ -18,6 +18,5 @@ public interface IInviteOrganizationUsersCommand
/// Contains the details for inviting a single organization user via email. /// Contains the details for inviting a single organization user via email.
/// </param> /// </param>
/// <returns>Response from InviteScimOrganiation<see cref="ScimInviteOrganizationUsersResponse"/></returns> /// <returns>Response from InviteScimOrganiation<see cref="ScimInviteOrganizationUsersResponse"/></returns>
Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync( Task<CommandResult<ScimInviteOrganizationUsersResponse>> InviteScimOrganizationUserAsync(InviteOrganizationUsersRequest request);
InviteOrganizationUsersRequest request);
} }

View File

@ -112,7 +112,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
await AdjustPasswordManagerSeatsAsync(validatedRequest, organization); await AdjustPasswordManagerSeatsAsync(validatedRequest, organization);
await AdjustSecretsManagerSeatsAsync(validatedRequest, organization); await AdjustSecretsManagerSeatsAsync(validatedRequest);
await SendAdditionalEmailsAsync(validatedRequest, organization); await SendAdditionalEmailsAsync(validatedRequest, organization);
@ -126,7 +126,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id)); await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id));
await RevertSecretsManagerChangesAsync(validatedRequest, organization); await RevertSecretsManagerChangesAsync(validatedRequest, organization, validatedRequest.Value.InviteOrganization.SmSeats);
await RevertPasswordManagerChangesAsync(validatedRequest, organization); await RevertPasswordManagerChangesAsync(validatedRequest, organization);
@ -164,16 +164,19 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
} }
} }
private async Task RevertSecretsManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization) private async Task RevertSecretsManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization, int? initialSmSeats)
{ {
if (validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd < 0) if (validatedResult.Value.InviteOrganization.UseSecretsManager && validatedResult.Value.SecretsManagerSubscriptionUpdate.SmSeatsChanged)
{ {
var updateRevert = new SecretsManagerSubscriptionUpdate(organization, validatedResult.Value.InviteOrganization.Plan, false) var smSubscriptionUpdateRevert = new SecretsManagerSubscriptionUpdate(
organization: organization,
plan: validatedResult.Value.InviteOrganization.Plan,
autoscaling: false)
{ {
SmSeats = validatedResult.Value.SecretsManagerSubscriptionUpdate.Seats SmSeats = initialSmSeats
}; };
await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(updateRevert); await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdateRevert);
} }
} }
@ -234,17 +237,14 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
.Select(u => u.Email).Distinct(); .Select(u => u.Email).Distinct();
} }
private async Task AdjustSecretsManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization) private async Task AdjustSecretsManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult)
{ {
if (validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd <= 0) if (validatedResult.Value.SecretsManagerSubscriptionUpdate?.SmSeatsChanged is not true)
{ {
return; return;
} }
var subscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, validatedResult.Value.InviteOrganization.Plan, true) await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(validatedResult.Value.SecretsManagerSubscriptionUpdate);
.AdjustSeats(validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd);
await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(subscriptionUpdate);
} }
private async Task AdjustPasswordManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization) private async Task AdjustPasswordManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization)

View File

@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager; using Bit.Core.Models.Business;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;

View File

@ -1,9 +1,8 @@
using Bit.Core.AdminConsole.Errors; using Bit.Core.AdminConsole.Errors;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
public record CannotAutoScaleOnSelfHostError(IGlobalSettings InvalidSettings) : Error<IGlobalSettings>(Code, InvalidSettings) public record CannotAutoScaleOnSelfHostError(EnvironmentRequest Invalid) : Error<EnvironmentRequest>(Code, Invalid)
{ {
public const string Code = "Cannot auto scale self-host."; public const string Code = "Cannot auto scale self-host.";
} }

View File

@ -0,0 +1,18 @@
#nullable enable
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
public class EnvironmentRequest
{
public bool IsSelfHosted { get; init; }
public PasswordManagerSubscriptionUpdate PasswordManagerSubscriptionUpdate { get; init; }
public EnvironmentRequest(IGlobalSettings globalSettings, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate)
{
IsSelfHosted = globalSettings.SelfHosted;
PasswordManagerSubscriptionUpdate = passwordManagerSubscriptionUpdate;
}
}

View File

@ -0,0 +1,13 @@
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
public interface IEnvironmentValidator : IValidator<EnvironmentRequest>;
public class EnvironmentValidator : IEnvironmentValidator
{
public async Task<ValidationResult<EnvironmentRequest>> ValidateAsync(EnvironmentRequest value) =>
value.IsSelfHosted && value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd > 0 ?
new Invalid<EnvironmentRequest>(new CannotAutoScaleOnSelfHostError(value)) :
new Valid<EnvironmentRequest>(value);
}

View File

@ -1,89 +1,64 @@
using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using SecretsManagerSubscriptionUpdate = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager.SecretsManagerSubscriptionUpdate;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
public interface IInviteUsersValidator : IValidator<InviteUserOrganizationValidationRequest>; public interface IInviteUsersValidator : IValidator<InviteUserOrganizationValidationRequest>;
public class InviteUsersValidator( public class InviteUsersValidator(
IGlobalSettings globalSettings, IOrganizationRepository organizationRepository,
IProviderRepository providerRepository, IPasswordManagerInviteUserValidator passwordManagerInviteUserValidator,
IPaymentService paymentService, IUpdateSecretsManagerSubscriptionCommand secretsManagerSubscriptionCommand) : IInviteUsersValidator
IOrganizationRepository organizationRepository) : IInviteUsersValidator
{ {
public async Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateAsync(InviteUserOrganizationValidationRequest request) public async Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateAsync(InviteUserOrganizationValidationRequest request)
{ {
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request); var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request);
var passwordManagerValidationResult = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
var passwordManagerValidationResult = await passwordManagerInviteUserValidator.ValidateAsync(subscriptionUpdate);
if (passwordManagerValidationResult is Invalid<PasswordManagerSubscriptionUpdate> invalidSubscriptionUpdate) if (passwordManagerValidationResult is Invalid<PasswordManagerSubscriptionUpdate> invalidSubscriptionUpdate)
{ {
return invalidSubscriptionUpdate.Map(request); return invalidSubscriptionUpdate.Map(request);
} }
if (ValidateEnvironment(globalSettings, subscriptionUpdate) is Invalid<IGlobalSettings> invalidEnvironment) if (request.InviteOrganization.UseSecretsManager && request.Invites.Any(x => x.AccessSecretsManager))
{ {
return invalidEnvironment.Map(request); return await ValidateSecretsManagerSubscriptionUpdateAsync(request, subscriptionUpdate);
}
var organizationValidationResult = InviteUserOrganizationValidator.Validate(request.InviteOrganization, subscriptionUpdate);
if (organizationValidationResult is Invalid<InviteOrganization> organizationValidation)
{
return organizationValidation.Map(request);
}
var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(request, subscriptionUpdate);
var secretsManagerValidationResult = SecretsManagerInviteUserValidation.Validate(smSubscriptionUpdate);
if (secretsManagerValidationResult is Invalid<SecretsManagerSubscriptionUpdate> invalidSmSubscriptionUpdate)
{
return invalidSmSubscriptionUpdate.Map(request);
}
var provider = await providerRepository.GetByOrganizationIdAsync(request.InviteOrganization.OrganizationId);
if (provider is not null)
{
var providerValidationResult = InvitingUserOrganizationProviderValidator.Validate(new InviteOrganizationProvider(provider));
if (providerValidationResult is Invalid<InviteOrganizationProvider> invalidProviderValidation)
{
return invalidProviderValidation.Map(request);
}
}
var paymentSubscription = await paymentService.GetSubscriptionAsync(
await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
var paymentValidationResult = InviteUserPaymentValidation.Validate(
new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
if (paymentValidationResult is Invalid<PaymentsSubscription> invalidPaymentValidation)
{
return invalidPaymentValidation.Map(request);
} }
return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest( return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
request, request,
subscriptionUpdate, subscriptionUpdate,
smSubscriptionUpdate)); null));
} }
public static ValidationResult<IGlobalSettings> ValidateEnvironment(IGlobalSettings globalSettings, PasswordManagerSubscriptionUpdate subscriptionUpdate) => private async Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateSecretsManagerSubscriptionUpdateAsync(
globalSettings.SelfHosted && subscriptionUpdate.SeatsRequiredToAdd > 0 InviteUserOrganizationValidationRequest request,
? new Invalid<IGlobalSettings>(new CannotAutoScaleOnSelfHostError(globalSettings)) PasswordManagerSubscriptionUpdate subscriptionUpdate)
: new Valid<IGlobalSettings>(globalSettings); {
try
{
var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(
organization: await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId),
plan: request.InviteOrganization.Plan,
autoscaling: true)
.AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager));
await secretsManagerSubscriptionCommand.ValidateUpdateAsync(smSubscriptionUpdate);
return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
request,
subscriptionUpdate,
smSubscriptionUpdate));
}
catch (Exception ex)
{
return new Invalid<InviteUserOrganizationValidationRequest>(new Error<InviteUserOrganizationValidationRequest>(ex.Message, request));
}
}
} }

View File

@ -1,15 +1,15 @@
using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
public static class InviteUserOrganizationValidator public interface IInviteUserOrganizationValidator : IValidator<InviteOrganization>;
public class InviteUserOrganizationValidator : IInviteUserOrganizationValidator
{ {
public static ValidationResult<InviteOrganization> Validate(InviteOrganization inviteOrganization, public async Task<ValidationResult<InviteOrganization>> ValidateAsync(InviteOrganization inviteOrganization)
PasswordManagerSubscriptionUpdate subscriptionUpdate)
{ {
if (inviteOrganization.Seats is null || subscriptionUpdate.SeatsRequiredToAdd is 0) if (inviteOrganization.Seats is null)
{ {
return new Valid<InviteOrganization>(inviteOrganization); return new Valid<InviteOrganization>(inviteOrganization);
} }

View File

@ -1,15 +1,33 @@
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
public static class PasswordManagerInviteUserValidator public interface IPasswordManagerInviteUserValidator : IValidator<PasswordManagerSubscriptionUpdate>;
public class PasswordManagerInviteUserValidator(
IGlobalSettings globalSettings,
IEnvironmentValidator environmentValidator,
IInviteUserOrganizationValidator inviteUserOrganizationValidator,
IProviderRepository providerRepository,
IPaymentService paymentService,
IOrganizationRepository organizationRepository
) : IPasswordManagerInviteUserValidator
{ {
/// <summary> /// <summary>
/// This is for validating if the organization can add additional users. /// This is for validating if the organization can add additional users.
/// </summary> /// </summary>
/// <param name="subscriptionUpdate"></param> /// <param name="subscriptionUpdate"></param>
/// <returns></returns> /// <returns></returns>
public static ValidationResult<PasswordManagerSubscriptionUpdate> Validate(PasswordManagerSubscriptionUpdate subscriptionUpdate) public static ValidationResult<PasswordManagerSubscriptionUpdate> ValidatePasswordManager(PasswordManagerSubscriptionUpdate subscriptionUpdate)
{ {
if (subscriptionUpdate.Seats is null) if (subscriptionUpdate.Seats is null)
{ {
@ -43,4 +61,52 @@ public static class PasswordManagerInviteUserValidator
return new Valid<PasswordManagerSubscriptionUpdate>(subscriptionUpdate); return new Valid<PasswordManagerSubscriptionUpdate>(subscriptionUpdate);
} }
public async Task<ValidationResult<PasswordManagerSubscriptionUpdate>> ValidateAsync(PasswordManagerSubscriptionUpdate request)
{
switch (ValidatePasswordManager(request))
{
case Valid<PasswordManagerSubscriptionUpdate> valid
when valid.Value.SeatsRequiredToAdd is 0:
return new Valid<PasswordManagerSubscriptionUpdate>(request);
case Invalid<PasswordManagerSubscriptionUpdate> invalid:
return invalid;
}
if (await environmentValidator.ValidateAsync(new EnvironmentRequest(globalSettings, request)) is Invalid<EnvironmentRequest> invalidEnvironment)
{
return invalidEnvironment.Map(request);
}
var organizationValidationResult = await inviteUserOrganizationValidator.ValidateAsync(request.InviteOrganization);
if (organizationValidationResult is Invalid<InviteOrganization> organizationValidation)
{
return organizationValidation.Map(request);
}
var provider = await providerRepository.GetByOrganizationIdAsync(request.InviteOrganization.OrganizationId);
if (provider is not null)
{
var providerValidationResult = InvitingUserOrganizationProviderValidator.Validate(new InviteOrganizationProvider(provider));
if (providerValidationResult is Invalid<InviteOrganizationProvider> invalidProviderValidation)
{
return invalidProviderValidation.Map(request);
}
}
var paymentSubscription = await paymentService.GetSubscriptionAsync(
await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
var paymentValidationResult = InviteUserPaymentValidation.Validate(
new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
if (paymentValidationResult is Invalid<PaymentsSubscription> invalidPaymentValidation)
{
return invalidPaymentValidation.Map(request);
}
return new Valid<PasswordManagerSubscriptionUpdate>(request);
}
} }

View File

@ -32,7 +32,7 @@ public class PasswordManagerSubscriptionUpdate
public int? AvailableSeats => Seats - OccupiedSeats; public int? AvailableSeats => Seats - OccupiedSeats;
/// <summary> /// <summary>
/// Number of seats to scale the organization to. /// Number of seats to scale the organization by.
/// ///
/// If Organization has no seat limit (Seats is null), then there are no new seats to add. /// If Organization has no seat limit (Seats is null), then there are no new seats to add.
/// </summary> /// </summary>
@ -50,17 +50,21 @@ public class PasswordManagerSubscriptionUpdate
public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; } public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; }
public InviteOrganization InviteOrganization { get; }
private PasswordManagerSubscriptionUpdate(int? organizationSeats, private PasswordManagerSubscriptionUpdate(int? organizationSeats,
int? organizationAutoScaleSeatLimit, int? organizationAutoScaleSeatLimit,
int currentSeats, int currentSeats,
int newUsersToAdd, int newUsersToAdd,
Plan.PasswordManagerPlanFeatures plan) Plan.PasswordManagerPlanFeatures plan,
InviteOrganization inviteOrganization)
{ {
Seats = organizationSeats; Seats = organizationSeats;
MaxAutoScaleSeats = organizationAutoScaleSeatLimit; MaxAutoScaleSeats = organizationAutoScaleSeatLimit;
OccupiedSeats = currentSeats; OccupiedSeats = currentSeats;
NewUsersToAdd = newUsersToAdd; NewUsersToAdd = newUsersToAdd;
PasswordManagerPlan = plan; PasswordManagerPlan = plan;
InviteOrganization = inviteOrganization;
} }
public PasswordManagerSubscriptionUpdate(InviteOrganization inviteOrganization, int occupiedSeats, int newUsersToAdd) : public PasswordManagerSubscriptionUpdate(InviteOrganization inviteOrganization, int occupiedSeats, int newUsersToAdd) :
@ -69,7 +73,8 @@ public class PasswordManagerSubscriptionUpdate
organizationAutoScaleSeatLimit: inviteOrganization.MaxAutoScaleSeats, organizationAutoScaleSeatLimit: inviteOrganization.MaxAutoScaleSeats,
currentSeats: occupiedSeats, currentSeats: occupiedSeats,
newUsersToAdd: newUsersToAdd, newUsersToAdd: newUsersToAdd,
plan: inviteOrganization.Plan.PasswordManager) plan: inviteOrganization.Plan.PasswordManager,
inviteOrganization: inviteOrganization)
{ } { }
public PasswordManagerSubscriptionUpdate(InviteUserOrganizationValidationRequest validationRequest) : public PasswordManagerSubscriptionUpdate(InviteUserOrganizationValidationRequest validationRequest) :
@ -78,6 +83,7 @@ public class PasswordManagerSubscriptionUpdate
organizationAutoScaleSeatLimit: validationRequest.InviteOrganization.MaxAutoScaleSeats, organizationAutoScaleSeatLimit: validationRequest.InviteOrganization.MaxAutoScaleSeats,
currentSeats: validationRequest.OccupiedPmSeats, currentSeats: validationRequest.OccupiedPmSeats,
newUsersToAdd: validationRequest.Invites.Length, newUsersToAdd: validationRequest.Invites.Length,
plan: validationRequest.InviteOrganization.Plan.PasswordManager) plan: validationRequest.InviteOrganization.Plan.PasswordManager,
inviteOrganization: validationRequest.InviteOrganization)
{ } { }
} }

View File

@ -15,6 +15,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.Models.Business.Tokenables; using Bit.Core.Models.Business.Tokenables;
using Bit.Core.OrganizationFeatures.OrganizationCollections; using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
@ -175,8 +178,12 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IHasConfirmedOwnersExceptQuery, HasConfirmedOwnersExceptQuery>(); services.AddScoped<IHasConfirmedOwnersExceptQuery, HasConfirmedOwnersExceptQuery>();
services.AddScoped<IInviteOrganizationUsersCommand, InviteOrganizationUsersCommand>(); services.AddScoped<IInviteOrganizationUsersCommand, InviteOrganizationUsersCommand>();
services.AddScoped<IInviteUsersValidator, InviteUsersValidator>();
services.AddScoped<ISendOrganizationInvitesCommand, SendOrganizationInvitesCommand>(); services.AddScoped<ISendOrganizationInvitesCommand, SendOrganizationInvitesCommand>();
services.AddScoped<IInviteUsersValidator, InviteUsersValidator>();
services.AddScoped<IInviteUserOrganizationValidator, InviteUserOrganizationValidator>();
services.AddScoped<IPasswordManagerInviteUserValidator, PasswordManagerInviteUserValidator>();
services.AddScoped<IEnvironmentValidator, EnvironmentValidator>();
} }
// TODO: move to OrganizationSubscriptionServiceCollectionExtensions when OrganizationUser methods are moved out of // TODO: move to OrganizationSubscriptionServiceCollectionExtensions when OrganizationUser methods are moved out of

View File

@ -5,4 +5,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
public interface IUpdateSecretsManagerSubscriptionCommand public interface IUpdateSecretsManagerSubscriptionCommand
{ {
Task UpdateSubscriptionAsync(SecretsManagerSubscriptionUpdate update); Task UpdateSubscriptionAsync(SecretsManagerSubscriptionUpdate update);
Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update);
} }

View File

@ -124,7 +124,7 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
} }
private async Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update) public async Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update)
{ {
if (_globalSettings.SelfHosted) if (_globalSettings.SelfHosted)
{ {

View File

@ -1,14 +1,15 @@
using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager; using Bit.Core.Models.Business;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers;
public static class InviteUserOrganizationValidationRequestHelpers public static class InviteUserOrganizationValidationRequestHelpers
{ {
public static InviteUserOrganizationValidationRequest GetInviteValidationRequestMock(InviteOrganizationUsersRequest request, public static InviteUserOrganizationValidationRequest GetInviteValidationRequestMock(InviteOrganizationUsersRequest request,
InviteOrganization inviteOrganization) => InviteOrganization inviteOrganization, Organization organization) =>
new() new()
{ {
Invites = request.Invites, Invites = request.Invites,
@ -18,7 +19,8 @@ public static class InviteUserOrganizationValidationRequestHelpers
OccupiedPmSeats = 0, OccupiedPmSeats = 0,
OccupiedSmSeats = 0, OccupiedSmSeats = 0,
PasswordManagerSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 0, 0), PasswordManagerSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 0, 0),
SecretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(inviteOrganization, 0, 0, 0) SecretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, inviteOrganization.Plan, true)
.AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager))
}; };
public static InviteUserOrganizationValidationRequest WithPasswordManagerUpdate(this InviteUserOrganizationValidationRequest request, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate) => public static InviteUserOrganizationValidationRequest WithPasswordManagerUpdate(this InviteUserOrganizationValidationRequest request, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate) =>

View File

@ -6,11 +6,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Business;
using Bit.Core.Models.Commands; using Bit.Core.Models.Commands;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
@ -24,6 +24,7 @@ using Microsoft.Extensions.Time.Testing;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers; using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers;
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@ -66,7 +67,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidator>() sutProvider.GetDependency<IInviteUsersValidator>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>()) .ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization))); .Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization, organization)));
// Act // Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request); var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@ -128,7 +129,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidator>() sutProvider.GetDependency<IInviteUsersValidator>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>()) .ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization))); .Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization, organization)));
// Act // Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request); var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@ -259,7 +260,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidator>() sutProvider.GetDependency<IInviteUsersValidator>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>()) .ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization) .Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithPasswordManagerUpdate(new PasswordManagerSubscriptionUpdate(inviteOrganization, organization.Seats.Value, 1)))); .WithPasswordManagerUpdate(new PasswordManagerSubscriptionUpdate(inviteOrganization, organization.Seats.Value, 1))));
// Act // Act
@ -329,7 +330,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidator>() sutProvider.GetDependency<IInviteUsersValidator>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>()) .ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization) .Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithPasswordManagerUpdate(passwordManagerUpdate))); .WithPasswordManagerUpdate(passwordManagerUpdate)));
// Act // Act
@ -364,6 +365,7 @@ public class InviteOrganizationUserCommandTests
organization.Seats = 1; organization.Seats = 1;
organization.SmSeats = 1; organization.SmSeats = 1;
organization.MaxAutoscaleSeats = 2; organization.MaxAutoscaleSeats = 2;
organization.MaxAutoscaleSmSeats = 2;
ownerDetails.Type = OrganizationUserType.Owner; ownerDetails.Type = OrganizationUserType.Owner;
var inviteOrganization = new InviteOrganization(organization, new FreePlan()); var inviteOrganization = new InviteOrganization(organization, new FreePlan());
@ -383,11 +385,8 @@ public class InviteOrganizationUserCommandTests
performedBy: Guid.Empty, performedBy: Guid.Empty,
timeProvider.GetUtcNow()); timeProvider.GetUtcNow());
var secretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate( var secretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, inviteOrganization.Plan, true)
inviteOrganization, .AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager));
organization.SmSeats.Value,
1,
organization.Seats.Value);
var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); var orgUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
@ -397,6 +396,8 @@ public class InviteOrganizationUserCommandTests
orgUserRepository orgUserRepository
.GetManyByMinimumRoleAsync(inviteOrganization.OrganizationId, OrganizationUserType.Owner) .GetManyByMinimumRoleAsync(inviteOrganization.OrganizationId, OrganizationUserType.Owner)
.Returns([ownerDetails]); .Returns([ownerDetails]);
orgUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(1);
orgUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(1);
var orgRepository = sutProvider.GetDependency<IOrganizationRepository>(); var orgRepository = sutProvider.GetDependency<IOrganizationRepository>();
@ -405,7 +406,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency<IInviteUsersValidator>() sutProvider.GetDependency<IInviteUsersValidator>()
.ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>()) .ValidateAsync(Arg.Any<InviteUserOrganizationValidationRequest>())
.Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization) .Returns(new Valid<InviteUserOrganizationValidationRequest>(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithSecretsManagerUpdate(secretsManagerSubscriptionUpdate))); .WithSecretsManagerUpdate(secretsManagerSubscriptionUpdate)));
// Act // Act
@ -416,7 +417,6 @@ public class InviteOrganizationUserCommandTests
await sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>() await sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.Received(1) .Received(1)
.UpdateSubscriptionAsync(Arg.Is<Core.Models.Business.SecretsManagerSubscriptionUpdate>(update => .UpdateSubscriptionAsync(secretsManagerSubscriptionUpdate);
update.SmSeats == secretsManagerSubscriptionUpdate.UpdatedSeatTotal));
} }
} }

View File

@ -1,40 +1,39 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Xunit; using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
[SutProviderCustomize]
public class InviteUserOrganizationValidationTests public class InviteUserOrganizationValidationTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_WhenOrganizationIsFreeTier_ShouldReturnValidResponse(Organization organization) public async Task Validate_WhenOrganizationIsFreeTier_ShouldReturnValidResponse(Organization organization, SutProvider<InviteUserOrganizationValidator> sutProvider)
{ {
var inviteOrganization = new InviteOrganization(organization, new FreePlan()); var inviteOrganization = new InviteOrganization(organization, new FreePlan());
var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 0, 0);
var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType<Valid<InviteOrganization>>(result); Assert.IsType<Valid<InviteOrganization>>(result);
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_WhenOrganizationDoesNotHavePaymentMethod_ShouldReturnInvalidResponseWithPaymentMethodMessage( public async Task Validate_WhenOrganizationDoesNotHavePaymentMethod_ShouldReturnInvalidResponseWithPaymentMethodMessage(
Organization organization) Organization organization, SutProvider<InviteUserOrganizationValidator> sutProvider)
{ {
organization.GatewayCustomerId = string.Empty; organization.GatewayCustomerId = string.Empty;
organization.Seats = 3; organization.Seats = 3;
var inviteOrganization = new InviteOrganization(organization, new FreePlan()); var inviteOrganization = new InviteOrganization(organization, new FreePlan());
var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 3, 1);
var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType<Invalid<InviteOrganization>>(result); Assert.IsType<Invalid<InviteOrganization>>(result);
Assert.Equal(OrganizationNoPaymentMethodFoundError.Code, (result as Invalid<InviteOrganization>)!.ErrorMessageString); Assert.Equal(OrganizationNoPaymentMethodFoundError.Code, (result as Invalid<InviteOrganization>)!.ErrorMessageString);
@ -42,17 +41,16 @@ public class InviteUserOrganizationValidationTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_WhenOrganizationDoesNotHaveSubscription_ShouldReturnInvalidResponseWithSubscriptionMessage( public async Task Validate_WhenOrganizationDoesNotHaveSubscription_ShouldReturnInvalidResponseWithSubscriptionMessage(
Organization organization) Organization organization, SutProvider<InviteUserOrganizationValidator> sutProvider)
{ {
organization.GatewaySubscriptionId = string.Empty; organization.GatewaySubscriptionId = string.Empty;
organization.Seats = 3; organization.Seats = 3;
organization.MaxAutoscaleSeats = 4; organization.MaxAutoscaleSeats = 4;
var inviteOrganization = new InviteOrganization(organization, new FreePlan()); var inviteOrganization = new InviteOrganization(organization, new FreePlan());
var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 3, 1);
var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType<Invalid<InviteOrganization>>(result); Assert.IsType<Invalid<InviteOrganization>>(result);
Assert.Equal(OrganizationNoSubscriptionFoundError.Code, (result as Invalid<InviteOrganization>)!.ErrorMessageString); Assert.Equal(OrganizationNoSubscriptionFoundError.Code, (result as Invalid<InviteOrganization>)!.ErrorMessageString);

View File

@ -4,17 +4,20 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V
using Bit.Core.AdminConsole.Shared.Validation; using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Enums; using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Xunit; using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
[SutProviderCustomize]
public class PasswordManagerInviteUserValidatorTests public class PasswordManagerInviteUserValidatorTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_OrganizationDoesNotHaveSeatsLimit_ShouldReturnValidResult(Organization organization) public async Task Validate_OrganizationDoesNotHaveSeatsLimit_ShouldReturnValidResult(Organization organization,
SutProvider<PasswordManagerInviteUserValidator> sutProvider)
{ {
organization.Seats = null; organization.Seats = null;
@ -22,14 +25,15 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, 0, 0); var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, 0, 0);
var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType<Valid<PasswordManagerSubscriptionUpdate>>(result); Assert.IsType<Valid<PasswordManagerSubscriptionUpdate>>(result);
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_NumberOfSeatsToAddMatchesSeatsAvailable_ShouldReturnValidResult(Organization organization) public async Task Validate_NumberOfSeatsToAddMatchesSeatsAvailable_ShouldReturnValidResult(Organization organization,
SutProvider<PasswordManagerInviteUserValidator> sutProvider)
{ {
organization.Seats = 8; organization.Seats = 8;
organization.PlanType = PlanType.EnterpriseAnnually; organization.PlanType = PlanType.EnterpriseAnnually;
@ -40,14 +44,15 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats); var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType<Valid<PasswordManagerSubscriptionUpdate>>(result); Assert.IsType<Valid<PasswordManagerSubscriptionUpdate>>(result);
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_NumberOfSeatsToAddIsGreaterThanMaxSeatsAllowed_ShouldBeInvalidWithSeatLimitMessage(Organization organization) public async Task Validate_NumberOfSeatsToAddIsGreaterThanMaxSeatsAllowed_ShouldBeInvalidWithSeatLimitMessage(Organization organization,
SutProvider<PasswordManagerInviteUserValidator> sutProvider)
{ {
organization.Seats = 4; organization.Seats = 4;
organization.MaxAutoscaleSeats = 4; organization.MaxAutoscaleSeats = 4;
@ -59,7 +64,7 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats); var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result); Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result);
Assert.Equal(PasswordManagerSeatLimitHasBeenReachedError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>)!.ErrorMessageString); Assert.Equal(PasswordManagerSeatLimitHasBeenReachedError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>)!.ErrorMessageString);
@ -67,7 +72,8 @@ public class PasswordManagerInviteUserValidatorTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public void Validate_GivenThePlanDoesNotAllowAdditionalSeats_ShouldBeInvalidMessageOfPlanNotAllowingSeats(Organization organization) public async Task Validate_GivenThePlanDoesNotAllowAdditionalSeats_ShouldBeInvalidMessageOfPlanNotAllowingSeats(Organization organization,
SutProvider<PasswordManagerInviteUserValidator> sutProvider)
{ {
organization.Seats = 8; organization.Seats = 8;
organization.MaxAutoscaleSeats = 9; organization.MaxAutoscaleSeats = 9;
@ -79,7 +85,7 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats); var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate); var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result); Assert.IsType<Invalid<PasswordManagerSubscriptionUpdate>>(result);
Assert.Equal(PasswordManagerPlanDoesNotAllowAdditionalSeatsError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>)!.ErrorMessageString); Assert.Equal(PasswordManagerPlanDoesNotAllowAdditionalSeatsError.Code, (result as Invalid<PasswordManagerSubscriptionUpdate>)!.ErrorMessageString);