1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -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:
Rui Tomé
2025-05-20 14:35:47 +01:00
committed by GitHub
parent 818934487f
commit 725a793863
8 changed files with 379 additions and 120 deletions

View File

@ -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>());
}
}

View File

@ -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]