Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-12491] Create Organization disable command #5348

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
using Bit.Billing.Constants;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.Services;
using Event = Stripe.Event;
namespace Bit.Billing.Services.Implementations;

public class SubscriptionDeletedHandler : ISubscriptionDeletedHandler
{
private readonly IStripeEventService _stripeEventService;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly IStripeEventUtilityService _stripeEventUtilityService;
private readonly IOrganizationDisableCommand _organizationDisableCommand;

public SubscriptionDeletedHandler(
IStripeEventService stripeEventService,
IOrganizationService organizationService,
IUserService userService,
IStripeEventUtilityService stripeEventUtilityService)
IStripeEventUtilityService stripeEventUtilityService,
IOrganizationDisableCommand organizationDisableCommand)

Check warning on line 18 in src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs#L17-L18

Added lines #L17 - L18 were not covered by tests
{
_stripeEventService = stripeEventService;
_organizationService = organizationService;
_userService = userService;
_stripeEventUtilityService = stripeEventUtilityService;
_organizationDisableCommand = organizationDisableCommand;

Check warning on line 23 in src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs#L23

Added line #L23 was not covered by tests
}

/// <summary>
Expand All @@ -41,7 +42,7 @@

if (organizationId.HasValue && subscription is not { CancellationDetails.Comment: providerMigrationCancellationComment })
{
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);

Check warning on line 45 in src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs#L45

Added line #L45 was not covered by tests
}
else if (userId.HasValue)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Bit.Billing.Constants;
using Bit.Billing.Jobs;
using Bit.Core;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
Expand All @@ -24,6 +25,7 @@
private readonly IOrganizationRepository _organizationRepository;
private readonly ISchedulerFactory _schedulerFactory;
private readonly IFeatureService _featureService;
private readonly IOrganizationDisableCommand _organizationDisableCommand;

public SubscriptionUpdatedHandler(
IStripeEventService stripeEventService,
Expand All @@ -35,7 +37,8 @@
IPushNotificationService pushNotificationService,
IOrganizationRepository organizationRepository,
ISchedulerFactory schedulerFactory,
IFeatureService featureService)
IFeatureService featureService,
IOrganizationDisableCommand organizationDisableCommand)

Check warning on line 41 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L40-L41

Added lines #L40 - L41 were not covered by tests
{
_stripeEventService = stripeEventService;
_stripeEventUtilityService = stripeEventUtilityService;
Expand All @@ -47,6 +50,7 @@
_organizationRepository = organizationRepository;
_schedulerFactory = schedulerFactory;
_featureService = featureService;
_organizationDisableCommand = organizationDisableCommand;

Check warning on line 53 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L53

Added line #L53 was not covered by tests
}

/// <summary>
Expand All @@ -63,7 +67,7 @@
case StripeSubscriptionStatus.Unpaid or StripeSubscriptionStatus.IncompleteExpired
when organizationId.HasValue:
{
await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
await _organizationDisableCommand.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd);

Check warning on line 70 in src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs

View check run for this annotation

Codecov / codecov/patch

src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs#L70

Added line #L70 was not covered by tests
if (subscription.Status == StripeSubscriptionStatus.Unpaid)
{
await ScheduleCancellationJobAsync(subscription.Id, organizationId.Value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;

/// <summary>
/// Command interface for disabling organizations.
/// </summary>
public interface IOrganizationDisableCommand
{
/// <summary>
/// Disables an organization with an optional expiration date.
/// </summary>
/// <param name="organizationId">The unique identifier of the organization to disable.</param>
/// <param name="expirationDate">Optional date when the disable status should expire.</param>
Task DisableAsync(Guid organizationId, DateTime? expirationDate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;

public class OrganizationDisableCommand : IOrganizationDisableCommand
{
private readonly IOrganizationRepository _organizationRepository;
private readonly IApplicationCacheService _applicationCacheService;

public OrganizationDisableCommand(
IOrganizationRepository organizationRepository,
IApplicationCacheService applicationCacheService)
{
_organizationRepository = organizationRepository;
_applicationCacheService = applicationCacheService;
}

public async Task DisableAsync(Guid organizationId, DateTime? expirationDate)
{
var organization = await _organizationRepository.GetByIdAsync(organizationId);
if (organization is { Enabled: true })
{
organization.Enabled = false;
organization.ExpirationDate = expirationDate;
organization.RevisionDate = DateTime.UtcNow;

await _organizationRepository.ReplaceAsync(organization);
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
}
}
}
1 change: 0 additions & 1 deletion src/Core/AdminConsole/Services/IOrganizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, Payment
Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(OrganizationLicense license, User owner,
string ownerKey, string collectionName, string publicKey, string privateKey);
Task EnableAsync(Guid organizationId, DateTime? expirationDate);
Task DisableAsync(Guid organizationId, DateTime? expirationDate);
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
Task EnableAsync(Guid organizationId);
Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,20 +698,6 @@ public async Task EnableAsync(Guid organizationId, DateTime? expirationDate)
}
}

public async Task DisableAsync(Guid organizationId, DateTime? expirationDate)
{
var org = await GetOrgById(organizationId);
if (org != null && org.Enabled)
{
org.Enabled = false;
org.ExpirationDate = expirationDate;
org.RevisionDate = DateTime.UtcNow;
await ReplaceAndUpdateCacheAsync(org);

// TODO: send email to owners?
}
}

public async Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate)
{
var org = await GetOrgById(organizationId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static void AddOrganizationServices(this IServiceCollection services, IGl
services.AddOrganizationDomainCommandsQueries();
services.AddOrganizationSignUpCommands();
services.AddOrganizationDeleteCommands();
services.AddOrganizationDisableCommands();
services.AddOrganizationAuthCommands();
services.AddOrganizationUserCommands();
services.AddOrganizationUserCommandsQueries();
Expand All @@ -69,6 +70,9 @@ private static void AddOrganizationDeleteCommands(this IServiceCollection servic
services.AddScoped<IOrganizationInitiateDeleteCommand, OrganizationInitiateDeleteCommand>();
}

private static void AddOrganizationDisableCommands(this IServiceCollection services) =>
services.AddScoped<IOrganizationDisableCommand, OrganizationDisableCommand>();

private static void AddOrganizationConnectionCommands(this IServiceCollection services)
{
services.AddScoped<ICreateOrganizationConnectionCommand, CreateOrganizationConnectionCommand>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
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;

[SutProviderCustomize]
public class OrganizationDisableCommandTests
{
[Theory, BitAutoData]
public async Task DisableAsync_WhenOrganizationEnabled_DisablesSuccessfully(
Organization organization,
DateTime expirationDate,
SutProvider<OrganizationDisableCommand> sutProvider)
{
organization.Enabled = true;
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);

await sutProvider.Sut.DisableAsync(organization.Id, expirationDate);

Assert.False(organization.Enabled);
Assert.Equal(expirationDate, organization.ExpirationDate);

await sutProvider.GetDependency<IOrganizationRepository>()
.Received(1)
.ReplaceAsync(organization);
await sutProvider.GetDependency<IApplicationCacheService>()
.Received(1)
.UpsertOrganizationAbilityAsync(organization);
}

[Theory, BitAutoData]
public async Task DisableAsync_WhenOrganizationNotFound_DoesNothing(
Guid organizationId,
DateTime expirationDate,
SutProvider<OrganizationDisableCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organizationId)
.Returns((Organization)null);

await sutProvider.Sut.DisableAsync(organizationId, expirationDate);

await sutProvider.GetDependency<IOrganizationRepository>()
.DidNotReceive()
.ReplaceAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IApplicationCacheService>()
.DidNotReceive()
.UpsertOrganizationAbilityAsync(Arg.Any<Organization>());
}

[Theory, BitAutoData]
public async Task DisableAsync_WhenOrganizationAlreadyDisabled_DoesNothing(
Organization organization,
DateTime expirationDate,
SutProvider<OrganizationDisableCommand> sutProvider)
{
organization.Enabled = false;
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);

await sutProvider.Sut.DisableAsync(organization.Id, expirationDate);

await sutProvider.GetDependency<IOrganizationRepository>()
.DidNotReceive()
.ReplaceAsync(Arg.Any<Organization>());
await sutProvider.GetDependency<IApplicationCacheService>()
.DidNotReceive()
.UpsertOrganizationAbilityAsync(Arg.Any<Organization>());
}
}
Loading