mirror of
https://github.com/bitwarden/server.git
synced 2025-05-22 12:04:27 -05:00
[PM-15161] Create ProviderClientOrganizationSignUpCommand command (#5764)
* Extract OrganizationService.SignupClientAsync into new ResellerClientOrganizationSignUpCommand * Refactor ResellerClientOrganizationSignUpCommand to remove unused dependencies and simplify SignupClientAsync method signature * Add unit tests for ResellerClientOrganizationSignUpCommand * Rename SignUpProviderClientOrganizationCommand * Rename ProviderClientOrganizationSignUpCommand * Register ProviderClientOrganizationSignUpCommand for dependency injection * Refactor ProviderService to use IProviderClientOrganizationSignUpCommand for organization signup process * Refactor error handling in ProviderClientOrganizationSignUpCommand to use constants for error messages * Remove SignupClientAsync method from IOrganizationService and OrganizationService, along with associated unit tests
This commit is contained in:
parent
818934487f
commit
725a793863
@ -5,6 +5,7 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@ -53,6 +54,7 @@ public class ProviderService : IProviderService
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IProviderClientOrganizationSignUpCommand _providerClientOrganizationSignUpCommand;
|
||||
|
||||
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
|
||||
@ -61,7 +63,8 @@ public class ProviderService : IProviderService
|
||||
IOrganizationRepository organizationRepository, GlobalSettings globalSettings,
|
||||
ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService,
|
||||
IDataProtectorTokenFactory<ProviderDeleteTokenable> providerDeleteTokenDataFactory,
|
||||
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient)
|
||||
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient,
|
||||
IProviderClientOrganizationSignUpCommand providerClientOrganizationSignUpCommand)
|
||||
{
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
@ -81,6 +84,7 @@ public class ProviderService : IProviderService
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_providerBillingService = providerBillingService;
|
||||
_pricingClient = pricingClient;
|
||||
_providerClientOrganizationSignUpCommand = providerClientOrganizationSignUpCommand;
|
||||
}
|
||||
|
||||
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo, TokenizedPaymentSource tokenizedPaymentSource = null)
|
||||
@ -560,12 +564,12 @@ public class ProviderService : IProviderService
|
||||
|
||||
ThrowOnInvalidPlanType(provider.Type, organizationSignup.Plan);
|
||||
|
||||
var (organization, _, defaultCollection) = await _organizationService.SignupClientAsync(organizationSignup);
|
||||
var signUpResponse = await _providerClientOrganizationSignUpCommand.SignUpClientOrganizationAsync(organizationSignup);
|
||||
|
||||
var providerOrganization = new ProviderOrganization
|
||||
{
|
||||
ProviderId = providerId,
|
||||
OrganizationId = organization.Id,
|
||||
OrganizationId = signUpResponse.Organization.Id,
|
||||
Key = organizationSignup.OwnerKey,
|
||||
};
|
||||
|
||||
@ -574,12 +578,12 @@ public class ProviderService : IProviderService
|
||||
|
||||
// Give the owner Can Manage access over the default collection
|
||||
// The orgUser is not available when the org is created so we have to do it here as part of the invite
|
||||
var defaultOwnerAccess = defaultCollection != null
|
||||
var defaultOwnerAccess = signUpResponse.DefaultCollection != null
|
||||
?
|
||||
[
|
||||
new CollectionAccessSelection
|
||||
{
|
||||
Id = defaultCollection.Id,
|
||||
Id = signUpResponse.DefaultCollection.Id,
|
||||
HidePasswords = false,
|
||||
ReadOnly = false,
|
||||
Manage = true
|
||||
@ -587,7 +591,7 @@ public class ProviderService : IProviderService
|
||||
]
|
||||
: Array.Empty<CollectionAccessSelection>();
|
||||
|
||||
await _organizationService.InviteUsersAsync(organization.Id, user.Id, systemUser: null,
|
||||
await _organizationService.InviteUsersAsync(signUpResponse.Organization.Id, user.Id, systemUser: null,
|
||||
new (OrganizationUserInvite, string)[]
|
||||
{
|
||||
(
|
||||
|
@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models;
|
||||
@ -717,8 +718,8 @@ public class ProviderServiceTests
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
||||
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||
|
||||
var providerOrganization =
|
||||
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||
@ -755,8 +756,8 @@ public class ProviderServiceTests
|
||||
|
||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
||||
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user));
|
||||
@ -782,8 +783,8 @@ public class ProviderServiceTests
|
||||
|
||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
||||
.Returns((organization, null as OrganizationUser, new Collection()));
|
||||
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||
.Returns(new ProviderClientOrganizationSignUpResponse(organization, new Collection()));
|
||||
|
||||
var providerOrganization = await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||
|
||||
@ -821,8 +822,8 @@ public class ProviderServiceTests
|
||||
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
|
||||
var providerOrganizationRepository = sutProvider.GetDependency<IProviderOrganizationRepository>();
|
||||
sutProvider.GetDependency<IOrganizationService>().SignupClientAsync(organizationSignup)
|
||||
.Returns((organization, null as OrganizationUser, defaultCollection));
|
||||
sutProvider.GetDependency<IProviderClientOrganizationSignUpCommand>().SignUpClientOrganizationAsync(organizationSignup)
|
||||
.Returns(new ProviderClientOrganizationSignUpResponse(organization, defaultCollection));
|
||||
|
||||
var providerOrganization =
|
||||
await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user);
|
||||
|
@ -0,0 +1,187 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
public record ProviderClientOrganizationSignUpResponse(
|
||||
Organization Organization,
|
||||
Collection DefaultCollection);
|
||||
|
||||
public interface IProviderClientOrganizationSignUpCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Sign up a new client organization for a provider.
|
||||
/// </summary>
|
||||
/// <param name="signup">The signup information.</param>
|
||||
/// <returns>A tuple containing the new organization and its default collection.</returns>
|
||||
Task<ProviderClientOrganizationSignUpResponse> SignUpClientOrganizationAsync(OrganizationSignup signup);
|
||||
}
|
||||
|
||||
public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizationSignUpCommand
|
||||
{
|
||||
public const string PlanNullErrorMessage = "Password Manager Plan was null.";
|
||||
public const string PlanDisabledErrorMessage = "Password Manager Plan is disabled.";
|
||||
public const string AdditionalSeatsNegativeErrorMessage = "You can't subtract Password Manager seats!";
|
||||
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
|
||||
public ProviderClientOrganizationSignUpCommand(
|
||||
ICurrentContext currentContext,
|
||||
IPricingClient pricingClient,
|
||||
IReferenceEventService referenceEventService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
ICollectionRepository collectionRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_pricingClient = pricingClient;
|
||||
_referenceEventService = referenceEventService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_collectionRepository = collectionRepository;
|
||||
}
|
||||
|
||||
public async Task<ProviderClientOrganizationSignUpResponse> SignUpClientOrganizationAsync(OrganizationSignup signup)
|
||||
{
|
||||
var plan = await _pricingClient.GetPlanOrThrow(signup.Plan);
|
||||
|
||||
ValidatePlan(plan, signup.AdditionalSeats);
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
// Pre-generate the org id so that we can save it with the Stripe subscription.
|
||||
Id = CoreHelpers.GenerateComb(),
|
||||
Name = signup.Name,
|
||||
BillingEmail = signup.BillingEmail,
|
||||
PlanType = plan!.Type,
|
||||
Seats = signup.AdditionalSeats,
|
||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||
MaxStorageGb = 1,
|
||||
UsePolicies = plan.HasPolicies,
|
||||
UseSso = plan.HasSso,
|
||||
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||
UseGroups = plan.HasGroups,
|
||||
UseEvents = plan.HasEvents,
|
||||
UseDirectory = plan.HasDirectory,
|
||||
UseTotp = plan.HasTotp,
|
||||
Use2fa = plan.Has2fa,
|
||||
UseApi = plan.HasApi,
|
||||
UseResetPassword = plan.HasResetPassword,
|
||||
SelfHost = plan.HasSelfHost,
|
||||
UsersGetPremium = plan.UsersGetPremium,
|
||||
UseCustomPermissions = plan.HasCustomPermissions,
|
||||
UseScim = plan.HasScim,
|
||||
Plan = plan.Name,
|
||||
Gateway = GatewayType.Stripe,
|
||||
ReferenceData = signup.Owner.ReferenceData,
|
||||
Enabled = true,
|
||||
LicenseKey = CoreHelpers.SecureRandomString(20),
|
||||
PublicKey = signup.PublicKey,
|
||||
PrivateKey = signup.PrivateKey,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Status = OrganizationStatusType.Created,
|
||||
UsePasswordManager = true,
|
||||
// Secrets Manager not available for purchase with Consolidated Billing.
|
||||
UseSecretsManager = false,
|
||||
};
|
||||
|
||||
var returnValue = await SignUpAsync(organization, signup.CollectionName);
|
||||
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
||||
{
|
||||
PlanName = plan.Name,
|
||||
PlanType = plan.Type,
|
||||
Seats = returnValue.Organization.Seats,
|
||||
SignupInitiationPath = signup.InitiationPath,
|
||||
Storage = returnValue.Organization.MaxStorageGb,
|
||||
});
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private static void ValidatePlan(Plan plan, int additionalSeats)
|
||||
{
|
||||
if (plan is null)
|
||||
{
|
||||
throw new BadRequestException(PlanNullErrorMessage);
|
||||
}
|
||||
|
||||
if (plan.Disabled)
|
||||
{
|
||||
throw new BadRequestException(PlanDisabledErrorMessage);
|
||||
}
|
||||
|
||||
if (additionalSeats < 0)
|
||||
{
|
||||
throw new BadRequestException(AdditionalSeatsNegativeErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private helper method to create a new organization.
|
||||
/// </summary>
|
||||
private async Task<ProviderClientOrganizationSignUpResponse> SignUpAsync(
|
||||
Organization organization, string collectionName)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _organizationRepository.CreateAsync(organization);
|
||||
await _organizationApiKeyRepository.CreateAsync(new OrganizationApiKey
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
ApiKey = CoreHelpers.SecureRandomString(30),
|
||||
Type = OrganizationApiKeyType.Default,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
});
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
Collection defaultCollection = null;
|
||||
if (!string.IsNullOrWhiteSpace(collectionName))
|
||||
{
|
||||
defaultCollection = new Collection
|
||||
{
|
||||
Name = collectionName,
|
||||
OrganizationId = organization.Id,
|
||||
CreationDate = organization.CreationDate,
|
||||
RevisionDate = organization.CreationDate
|
||||
};
|
||||
|
||||
await _collectionRepository.CreateAsync(defaultCollection, null, null);
|
||||
}
|
||||
|
||||
return new ProviderClientOrganizationSignUpResponse(organization, defaultCollection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (organization.Id != default)
|
||||
{
|
||||
await _organizationRepository.DeleteAsync(organization);
|
||||
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,9 +18,6 @@ public interface IOrganizationService
|
||||
Task AutoAddSeatsAsync(Organization organization, int seatsToAdd);
|
||||
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
||||
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
||||
#nullable enable
|
||||
Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup);
|
||||
#nullable disable
|
||||
/// <summary>
|
||||
/// Create a new organization on a self-hosted instance
|
||||
/// </summary>
|
||||
|
@ -410,66 +410,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup)
|
||||
{
|
||||
var plan = await _pricingClient.GetPlanOrThrow(signup.Plan);
|
||||
|
||||
ValidatePlan(plan, signup.AdditionalSeats, "Password Manager");
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
// Pre-generate the org id so that we can save it with the Stripe subscription.
|
||||
Id = CoreHelpers.GenerateComb(),
|
||||
Name = signup.Name,
|
||||
BillingEmail = signup.BillingEmail,
|
||||
PlanType = plan!.Type,
|
||||
Seats = signup.AdditionalSeats,
|
||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||
MaxStorageGb = 1,
|
||||
UsePolicies = plan.HasPolicies,
|
||||
UseSso = plan.HasSso,
|
||||
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||
UseGroups = plan.HasGroups,
|
||||
UseEvents = plan.HasEvents,
|
||||
UseDirectory = plan.HasDirectory,
|
||||
UseTotp = plan.HasTotp,
|
||||
Use2fa = plan.Has2fa,
|
||||
UseApi = plan.HasApi,
|
||||
UseResetPassword = plan.HasResetPassword,
|
||||
SelfHost = plan.HasSelfHost,
|
||||
UsersGetPremium = plan.UsersGetPremium,
|
||||
UseCustomPermissions = plan.HasCustomPermissions,
|
||||
UseScim = plan.HasScim,
|
||||
Plan = plan.Name,
|
||||
Gateway = GatewayType.Stripe,
|
||||
ReferenceData = signup.Owner.ReferenceData,
|
||||
Enabled = true,
|
||||
LicenseKey = CoreHelpers.SecureRandomString(20),
|
||||
PublicKey = signup.PublicKey,
|
||||
PrivateKey = signup.PrivateKey,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
RevisionDate = DateTime.UtcNow,
|
||||
Status = OrganizationStatusType.Created,
|
||||
UsePasswordManager = true,
|
||||
// Secrets Manager not available for purchase with Consolidated Billing.
|
||||
UseSecretsManager = false,
|
||||
};
|
||||
|
||||
var returnValue = await SignUpAsync(organization, default, signup.OwnerKey, signup.CollectionName, false);
|
||||
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
||||
{
|
||||
PlanName = plan.Name,
|
||||
PlanType = plan.Type,
|
||||
Seats = returnValue.Item1.Seats,
|
||||
SignupInitiationPath = signup.InitiationPath,
|
||||
Storage = returnValue.Item1.MaxStorageGb,
|
||||
});
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||
{
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||
|
@ -69,8 +69,11 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
||||
}
|
||||
|
||||
private static IServiceCollection AddOrganizationSignUpCommands(this IServiceCollection services) =>
|
||||
private static void AddOrganizationSignUpCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
||||
services.AddScoped<IProviderClientOrganizationSignUpCommand, ProviderClientOrganizationSignUpCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
||||
{
|
||||
|
@ -0,0 +1,169 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations.OrganizationSignUp;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class ProviderClientOrganizationSignUpCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
public async Task SignupClientAsync_ValidParameters_CreatesOrganizationSuccessfully(
|
||||
PlanType planType,
|
||||
OrganizationSignup signup,
|
||||
string collectionName,
|
||||
SutProvider<ProviderClientOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.CollectionName = collectionName;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
|
||||
var result = await sutProvider.Sut.SignUpClientOrganizationAsync(signup);
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.DefaultCollection);
|
||||
Assert.Equal(collectionName, result.DefaultCollection.Name);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Name == signup.Name &&
|
||||
o.BillingEmail == signup.BillingEmail &&
|
||||
o.PlanType == plan.Type &&
|
||||
o.Seats == signup.AdditionalSeats &&
|
||||
o.MaxCollections == plan.PasswordManager.MaxCollections &&
|
||||
o.UsePasswordManager == true &&
|
||||
o.UseSecretsManager == false &&
|
||||
o.Status == OrganizationStatusType.Created
|
||||
)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>()
|
||||
.Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Organization.Seats &&
|
||||
referenceEvent.Storage == result.Organization.MaxStorageGb &&
|
||||
referenceEvent.SignupInitiationPath == signup.InitiationPath
|
||||
));
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(
|
||||
Arg.Is<Collection>(c =>
|
||||
c.Name == collectionName &&
|
||||
c.OrganizationId == result.Organization.Id
|
||||
),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>(),
|
||||
Arg.Any<IEnumerable<CollectionAccessSelection>>()
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(
|
||||
Arg.Is<OrganizationApiKey>(k =>
|
||||
k.OrganizationId == result.Organization.Id &&
|
||||
k.Type == OrganizationApiKeyType.Default
|
||||
)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.Received(1)
|
||||
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(o => o.Id == result.Organization.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignupClientAsync_NullPlan_ThrowsBadRequestException(
|
||||
OrganizationSignup signup,
|
||||
SutProvider<ProviderClientOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns((Plan)null);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpClientOrganizationAsync(signup));
|
||||
|
||||
Assert.Contains(ProviderClientOrganizationSignUpCommand.PlanNullErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignupClientAsync_NegativeAdditionalSeats_ThrowsBadRequestException(
|
||||
OrganizationSignup signup,
|
||||
SutProvider<ProviderClientOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = PlanType.TeamsMonthly;
|
||||
signup.AdditionalSeats = -5;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpClientOrganizationAsync(signup));
|
||||
|
||||
Assert.Contains(ProviderClientOrganizationSignUpCommand.AdditionalSeatsNegativeErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
public async Task SignupClientAsync_WhenExceptionIsThrown_CleanupIsPerformed(
|
||||
PlanType planType,
|
||||
OrganizationSignup signup,
|
||||
SutProvider<ProviderClientOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(signup.Plan)
|
||||
.Returns(plan);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.When(x => x.CreateAsync(Arg.Any<OrganizationApiKey>()))
|
||||
.Do(_ => throw new Exception());
|
||||
|
||||
var thrownException = await Assert.ThrowsAsync<Exception>(
|
||||
() => sutProvider.Sut.SignUpClientOrganizationAsync(signup));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(Arg.Is<Organization>(o => o.Name == signup.Name));
|
||||
|
||||
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.Received(1)
|
||||
.DeleteOrganizationAbilityAsync(Arg.Any<Guid>());
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
@ -177,47 +176,6 @@ public class OrganizationServiceTests
|
||||
referenceEvent.Users == expectedNewUsersCount));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SignupClientAsync_Succeeds(
|
||||
OrganizationSignup signup,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = PlanType.TeamsMonthly;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
sutProvider.GetDependency<IPricingClient>().GetPlanOrThrow(signup.Plan).Returns(plan);
|
||||
|
||||
var (organization, _, _) = await sutProvider.Sut.SignupClientAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(Arg.Is<Organization>(org =>
|
||||
org.Id == organization.Id &&
|
||||
org.Name == signup.Name &&
|
||||
org.Plan == plan.Name &&
|
||||
org.PlanType == plan.Type &&
|
||||
org.UsePolicies == plan.HasPolicies &&
|
||||
org.PublicKey == signup.PublicKey &&
|
||||
org.PrivateKey == signup.PrivateKey &&
|
||||
org.UseSecretsManager == false));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationApiKeyRepository>().Received(1)
|
||||
.CreateAsync(Arg.Is<OrganizationApiKey>(orgApiKey =>
|
||||
orgApiKey.OrganizationId == organization.Id));
|
||||
|
||||
await sutProvider.GetDependency<IApplicationCacheService>().Received(1)
|
||||
.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1)
|
||||
.CreateAsync(Arg.Is<Collection>(c => c.Name == signup.CollectionName && c.OrganizationId == organization.Id), null, null);
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1).RaiseEventAsync(Arg.Is<ReferenceEvent>(
|
||||
re =>
|
||||
re.Type == ReferenceEventType.Signup &&
|
||||
re.PlanType == plan.Type));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User,
|
||||
InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize, BitAutoData]
|
||||
|
Loading…
x
Reference in New Issue
Block a user