mirror of
https://github.com/bitwarden/server.git
synced 2025-06-25 13:18:48 -05:00
[PM-15160] Create ResellerClientOrganizationSignUpCommand (#5981)
* Implement ResellerClientOrganizationSignUpCommand for signing up reseller client organizations with email invitations and error handling * Refactor ProvidersController to replace IOrganizationService with IResellerClientOrganizationSignUpCommand for organization sign-up process * Remove CreatePendingOrganization method from IOrganizationService and its implementation in OrganizationService * Add IResellerClientOrganizationSignUpCommand to service collection for organization sign-up * Add comment to clarify organization deletion process in ResellerClientOrganizationSignUpCommand
This commit is contained in:
parent
cc07f13c7c
commit
494c41e3b1
@ -6,6 +6,7 @@ using Bit.Admin.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
@ -34,14 +35,13 @@ namespace Bit.Admin.AdminConsole.Controllers;
|
|||||||
public class ProvidersController : Controller
|
public class ProvidersController : Controller
|
||||||
{
|
{
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IResellerClientOrganizationSignUpCommand _resellerClientOrganizationSignUpCommand;
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderUserRepository _providerUserRepository;
|
private readonly IProviderUserRepository _providerUserRepository;
|
||||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
private readonly IProviderService _providerService;
|
private readonly IProviderService _providerService;
|
||||||
private readonly IUserService _userService;
|
|
||||||
private readonly ICreateProviderCommand _createProviderCommand;
|
private readonly ICreateProviderCommand _createProviderCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IProviderPlanRepository _providerPlanRepository;
|
private readonly IProviderPlanRepository _providerPlanRepository;
|
||||||
@ -54,14 +54,13 @@ public class ProvidersController : Controller
|
|||||||
|
|
||||||
public ProvidersController(
|
public ProvidersController(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationService organizationService,
|
IResellerClientOrganizationSignUpCommand resellerClientOrganizationSignUpCommand,
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
IProviderUserRepository providerUserRepository,
|
IProviderUserRepository providerUserRepository,
|
||||||
IProviderOrganizationRepository providerOrganizationRepository,
|
IProviderOrganizationRepository providerOrganizationRepository,
|
||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
IUserService userService,
|
|
||||||
ICreateProviderCommand createProviderCommand,
|
ICreateProviderCommand createProviderCommand,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IProviderPlanRepository providerPlanRepository,
|
IProviderPlanRepository providerPlanRepository,
|
||||||
@ -71,14 +70,13 @@ public class ProvidersController : Controller
|
|||||||
IStripeAdapter stripeAdapter)
|
IStripeAdapter stripeAdapter)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationService = organizationService;
|
_resellerClientOrganizationSignUpCommand = resellerClientOrganizationSignUpCommand;
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_providerUserRepository = providerUserRepository;
|
_providerUserRepository = providerUserRepository;
|
||||||
_providerOrganizationRepository = providerOrganizationRepository;
|
_providerOrganizationRepository = providerOrganizationRepository;
|
||||||
_providerService = providerService;
|
_providerService = providerService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_userService = userService;
|
|
||||||
_createProviderCommand = createProviderCommand;
|
_createProviderCommand = createProviderCommand;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_providerPlanRepository = providerPlanRepository;
|
_providerPlanRepository = providerPlanRepository;
|
||||||
@ -459,7 +457,7 @@ public class ProvidersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var organization = model.CreateOrganization(provider);
|
var organization = model.CreateOrganization(provider);
|
||||||
await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted);
|
await _resellerClientOrganizationSignUpCommand.SignUpResellerClientAsync(organization, model.Owners);
|
||||||
await _providerService.AddOrganization(providerId, organization.Id, null);
|
await _providerService.AddOrganization(providerId, organization.Id, null);
|
||||||
|
|
||||||
return RedirectToAction("Edit", "Providers", new { id = providerId });
|
return RedirectToAction("Edit", "Providers", new { id = providerId });
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
|
public record ResellerClientOrganizationSignUpResponse(
|
||||||
|
Organization Organization,
|
||||||
|
OrganizationUser OwnerOrganizationUser);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command for signing up reseller client organizations in a pending state.
|
||||||
|
/// </summary>
|
||||||
|
public interface IResellerClientOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sign up a reseller client organization. The organization will be created in a pending state
|
||||||
|
/// (disabled and with Pending status) and the owner will be invited via email. The organization
|
||||||
|
/// will become active once the owner accepts the invitation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organization">The organization to create.</param>
|
||||||
|
/// <param name="ownerEmail">The email of the organization owner who will be invited.</param>
|
||||||
|
/// <returns>A response containing the created pending organization and invited owner user.</returns>
|
||||||
|
Task<ResellerClientOrganizationSignUpResponse> SignUpResellerClientAsync(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResellerClientOrganizationSignUpCommand : IResellerClientOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly IEventService _eventService;
|
||||||
|
private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand;
|
||||||
|
private readonly IPaymentService _paymentService;
|
||||||
|
|
||||||
|
public ResellerClientOrganizationSignUpCommand(
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
|
IApplicationCacheService applicationCacheService,
|
||||||
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IEventService eventService,
|
||||||
|
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand,
|
||||||
|
IPaymentService paymentService)
|
||||||
|
{
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
|
_applicationCacheService = applicationCacheService;
|
||||||
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
_eventService = eventService;
|
||||||
|
_sendOrganizationInvitesCommand = sendOrganizationInvitesCommand;
|
||||||
|
_paymentService = paymentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ResellerClientOrganizationSignUpResponse> SignUpResellerClientAsync(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var createdOrganization = await CreateOrganizationAsync(organization);
|
||||||
|
var ownerOrganizationUser = await CreateAndInviteOwnerAsync(createdOrganization, ownerEmail);
|
||||||
|
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
|
||||||
|
|
||||||
|
return new ResellerClientOrganizationSignUpResponse(organization, ownerOrganizationUser);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await _paymentService.CancelAndRecoverChargesAsync(organization);
|
||||||
|
|
||||||
|
if (organization.Id != default)
|
||||||
|
{
|
||||||
|
// Deletes the organization and all related data, including its owner user
|
||||||
|
await _organizationRepository.DeleteAsync(organization);
|
||||||
|
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Organization> CreateOrganizationAsync(Organization organization)
|
||||||
|
{
|
||||||
|
organization.Id = CoreHelpers.GenerateComb();
|
||||||
|
organization.Enabled = false;
|
||||||
|
organization.Status = OrganizationStatusType.Pending;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OrganizationUser> CreateAndInviteOwnerAsync(Organization organization, string ownerEmail)
|
||||||
|
{
|
||||||
|
var ownerOrganizationUser = new OrganizationUser
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
UserId = null,
|
||||||
|
Email = ownerEmail,
|
||||||
|
Key = null,
|
||||||
|
Type = OrganizationUserType.Owner,
|
||||||
|
Status = OrganizationUserStatusType.Invited,
|
||||||
|
};
|
||||||
|
|
||||||
|
await _organizationUserRepository.CreateAsync(ownerOrganizationUser);
|
||||||
|
|
||||||
|
await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest(
|
||||||
|
users: [ownerOrganizationUser],
|
||||||
|
organization: organization,
|
||||||
|
initOrganization: true));
|
||||||
|
|
||||||
|
return ownerOrganizationUser;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.Security.Claims;
|
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.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@ -42,7 +41,6 @@ public interface IOrganizationService
|
|||||||
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
|
Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser);
|
||||||
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
Task<List<Tuple<OrganizationUser, string>>> RevokeUsersAsync(Guid organizationId,
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId);
|
IEnumerable<Guid> organizationUserIds, Guid? revokingUserId);
|
||||||
Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted);
|
|
||||||
Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null);
|
Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null);
|
||||||
Task<(bool canScale, string failureReason)> CanScaleAsync(Organization organization, int seatsToAdd);
|
Task<(bool canScale, string failureReason)> CanScaleAsync(Organization organization, int seatsToAdd);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Security.Claims;
|
using System.Text.Json;
|
||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
@ -1705,27 +1704,4 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
|
|
||||||
{
|
|
||||||
organization.Id = CoreHelpers.GenerateComb();
|
|
||||||
organization.Enabled = false;
|
|
||||||
organization.Status = OrganizationStatusType.Pending;
|
|
||||||
|
|
||||||
await SignUpAsync(organization, default, null, null, true);
|
|
||||||
|
|
||||||
var ownerOrganizationUser = new OrganizationUser
|
|
||||||
{
|
|
||||||
OrganizationId = organization.Id,
|
|
||||||
UserId = null,
|
|
||||||
Email = ownerEmail,
|
|
||||||
Key = null,
|
|
||||||
Type = OrganizationUserType.Owner,
|
|
||||||
Status = OrganizationUserStatusType.Invited,
|
|
||||||
};
|
|
||||||
await _organizationUserRepository.CreateAsync(ownerOrganizationUser);
|
|
||||||
|
|
||||||
await SendInviteAsync(ownerOrganizationUser, organization, true);
|
|
||||||
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
||||||
services.AddScoped<IProviderClientOrganizationSignUpCommand, ProviderClientOrganizationSignUpCommand>();
|
services.AddScoped<IProviderClientOrganizationSignUpCommand, ProviderClientOrganizationSignUpCommand>();
|
||||||
|
services.AddScoped<IResellerClientOrganizationSignUpCommand, ResellerClientOrganizationSignUpCommand>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
||||||
|
@ -0,0 +1,185 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
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 ResellerClientOrganizationSignUpCommandTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpResellerClientAsync_WithValidParameters_CreatesOrganizationSuccessfully(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail,
|
||||||
|
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
var result = await sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail);
|
||||||
|
|
||||||
|
Assert.NotNull(result.Organization);
|
||||||
|
Assert.False(result.Organization.Enabled);
|
||||||
|
Assert.Equal(OrganizationStatusType.Pending, result.Organization.Status);
|
||||||
|
Assert.NotNull(result.OwnerOrganizationUser);
|
||||||
|
Assert.Equal(ownerEmail, result.OwnerOrganizationUser.Email);
|
||||||
|
Assert.Equal(OrganizationUserType.Owner, result.OwnerOrganizationUser.Type);
|
||||||
|
Assert.Equal(OrganizationUserStatusType.Invited, result.OwnerOrganizationUser.Status);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(
|
||||||
|
Arg.Is<Organization>(o =>
|
||||||
|
o.Id != default &&
|
||||||
|
o.Name == organization.Name &&
|
||||||
|
o.Enabled == false &&
|
||||||
|
o.Status == OrganizationStatusType.Pending
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(
|
||||||
|
Arg.Is<OrganizationApiKey>(k =>
|
||||||
|
k.OrganizationId == result.Organization.Id &&
|
||||||
|
k.Type == OrganizationApiKeyType.Default &&
|
||||||
|
!string.IsNullOrEmpty(k.ApiKey)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||||
|
.Received(1)
|
||||||
|
.UpsertOrganizationAbilityAsync(Arg.Is<Organization>(o => o.Id == result.Organization.Id));
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(
|
||||||
|
Arg.Is<OrganizationUser>(u =>
|
||||||
|
u.OrganizationId == result.Organization.Id &&
|
||||||
|
u.Email == ownerEmail &&
|
||||||
|
u.Type == OrganizationUserType.Owner &&
|
||||||
|
u.Status == OrganizationUserStatusType.Invited &&
|
||||||
|
u.UserId == null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.SendInvitesAsync(
|
||||||
|
Arg.Is<SendInvitesRequest>(r =>
|
||||||
|
r.Users.Count() == 1 &&
|
||||||
|
r.Users.First().Email == ownerEmail &&
|
||||||
|
r.Organization.Id == result.Organization.Id &&
|
||||||
|
r.InitOrganization == true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(
|
||||||
|
Arg.Is<OrganizationUser>(u => u.Email == ownerEmail),
|
||||||
|
EventType.OrganizationUser_Invited
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpResellerClientAsync_WhenOrganizationRepositoryThrows_PerformsCleanup(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail,
|
||||||
|
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.When(x => x.CreateAsync(Arg.Any<Organization>()))
|
||||||
|
.Do(_ => throw new Exception());
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<Exception>(
|
||||||
|
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
|
||||||
|
|
||||||
|
await AssertCleanupIsPerformed(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpResellerClientAsync_WhenOrganizationUserCreationFails_PerformsCleanup(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail,
|
||||||
|
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.When(x => x.CreateAsync(Arg.Any<OrganizationUser>()))
|
||||||
|
.Do(_ => throw new Exception());
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<Exception>(
|
||||||
|
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Any<Organization>());
|
||||||
|
await AssertCleanupIsPerformed(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpResellerClientAsync_WhenInvitationSendingFails_PerformsCleanup(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail,
|
||||||
|
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
|
||||||
|
.When(x => x.SendInvitesAsync(Arg.Any<SendInvitesRequest>()))
|
||||||
|
.Do(_ => throw new Exception());
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<Exception>(
|
||||||
|
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Any<Organization>());
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Any<OrganizationUser>());
|
||||||
|
await AssertCleanupIsPerformed(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpResellerClientAsync_WhenEventLoggingFails_PerformsCleanup(
|
||||||
|
Organization organization,
|
||||||
|
string ownerEmail,
|
||||||
|
SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IEventService>()
|
||||||
|
.When(x => x.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>()))
|
||||||
|
.Do(_ => throw new Exception());
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<Exception>(
|
||||||
|
() => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Any<Organization>());
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Any<OrganizationUser>());
|
||||||
|
await sutProvider.GetDependency<ISendOrganizationInvitesCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.SendInvitesAsync(Arg.Any<SendInvitesRequest>());
|
||||||
|
await AssertCleanupIsPerformed(sutProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task AssertCleanupIsPerformed(SutProvider<ResellerClientOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
await sutProvider.GetDependency<IPaymentService>()
|
||||||
|
.Received(1)
|
||||||
|
.CancelAndRecoverChargesAsync(Arg.Any<Organization>());
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(Arg.Any<Organization>());
|
||||||
|
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteOrganizationAbilityAsync(Arg.Any<Guid>());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user