mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
* Add feature flag * Disable provider and schedule cancellation when subscription goes unpaid * Run dotnet format * Only set provider subscription cancel_at when subscription is going from paid to unpaid * Update tests
667 lines
24 KiB
C#
667 lines
24 KiB
C#
using Bit.Billing.Constants;
|
|
using Bit.Billing.Services;
|
|
using Bit.Billing.Services.Implementations;
|
|
using Bit.Core;
|
|
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.AdminConsole.Entities.Provider;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
|
using Bit.Core.AdminConsole.Repositories;
|
|
using Bit.Core.AdminConsole.Services;
|
|
using Bit.Core.Billing.Enums;
|
|
using Bit.Core.Billing.Models.StaticStore.Plans;
|
|
using Bit.Core.Billing.Pricing;
|
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
|
using Bit.Core.Platform.Push;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json.Linq;
|
|
using NSubstitute;
|
|
using Quartz;
|
|
using Stripe;
|
|
using Stripe.TestHelpers;
|
|
using Xunit;
|
|
using Event = Stripe.Event;
|
|
|
|
namespace Bit.Billing.Test.Services;
|
|
|
|
public class SubscriptionUpdatedHandlerTests
|
|
{
|
|
private readonly IStripeEventService _stripeEventService;
|
|
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
|
private readonly IOrganizationService _organizationService;
|
|
private readonly IStripeFacade _stripeFacade;
|
|
private readonly IOrganizationSponsorshipRenewCommand _organizationSponsorshipRenewCommand;
|
|
private readonly IUserService _userService;
|
|
private readonly IPushNotificationService _pushNotificationService;
|
|
private readonly IOrganizationRepository _organizationRepository;
|
|
private readonly ISchedulerFactory _schedulerFactory;
|
|
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
|
private readonly IOrganizationDisableCommand _organizationDisableCommand;
|
|
private readonly IPricingClient _pricingClient;
|
|
private readonly IFeatureService _featureService;
|
|
private readonly IProviderRepository _providerRepository;
|
|
private readonly IProviderService _providerService;
|
|
private readonly ILogger<SubscriptionUpdatedHandler> _logger;
|
|
private readonly IScheduler _scheduler;
|
|
private readonly SubscriptionUpdatedHandler _sut;
|
|
|
|
public SubscriptionUpdatedHandlerTests()
|
|
{
|
|
_stripeEventService = Substitute.For<IStripeEventService>();
|
|
_stripeEventUtilityService = Substitute.For<IStripeEventUtilityService>();
|
|
_organizationService = Substitute.For<IOrganizationService>();
|
|
_stripeFacade = Substitute.For<IStripeFacade>();
|
|
_organizationSponsorshipRenewCommand = Substitute.For<IOrganizationSponsorshipRenewCommand>();
|
|
_userService = Substitute.For<IUserService>();
|
|
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
|
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
|
_schedulerFactory = Substitute.For<ISchedulerFactory>();
|
|
_organizationEnableCommand = Substitute.For<IOrganizationEnableCommand>();
|
|
_organizationDisableCommand = Substitute.For<IOrganizationDisableCommand>();
|
|
_pricingClient = Substitute.For<IPricingClient>();
|
|
_featureService = Substitute.For<IFeatureService>();
|
|
_providerRepository = Substitute.For<IProviderRepository>();
|
|
_providerService = Substitute.For<IProviderService>();
|
|
_logger = Substitute.For<ILogger<SubscriptionUpdatedHandler>>();
|
|
_scheduler = Substitute.For<IScheduler>();
|
|
|
|
_schedulerFactory.GetScheduler().Returns(_scheduler);
|
|
|
|
_sut = new SubscriptionUpdatedHandler(
|
|
_stripeEventService,
|
|
_stripeEventUtilityService,
|
|
_organizationService,
|
|
_stripeFacade,
|
|
_organizationSponsorshipRenewCommand,
|
|
_userService,
|
|
_pushNotificationService,
|
|
_organizationRepository,
|
|
_schedulerFactory,
|
|
_organizationEnableCommand,
|
|
_organizationDisableCommand,
|
|
_pricingClient,
|
|
_featureService,
|
|
_providerRepository,
|
|
_providerService,
|
|
_logger);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidOrganizationSubscription_DisablesOrganizationAndSchedulesCancellation()
|
|
{
|
|
// Arrange
|
|
var organizationId = Guid.NewGuid();
|
|
var subscriptionId = "sub_123";
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(organizationId, null, null));
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _organizationDisableCommand.Received(1)
|
|
.DisableAsync(organizationId, currentPeriodEnd);
|
|
await _scheduler.Received(1).ScheduleJob(
|
|
Arg.Is<IJobDetail>(j => j.Key.Name == $"cancel-sub-{subscriptionId}"),
|
|
Arg.Is<ITrigger>(t => t.Key.Name == $"cancel-trigger-{subscriptionId}"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WithValidTransition_DisablesProviderAndSchedulesCancellation()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
const string subscriptionId = "sub_123";
|
|
var frozenTime = DateTime.UtcNow;
|
|
|
|
var testClock = new TestClock
|
|
{
|
|
Id = "clock_123",
|
|
Status = "ready",
|
|
FrozenTime = frozenTime
|
|
};
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" },
|
|
TestClock = testClock
|
|
};
|
|
|
|
var provider = new Provider
|
|
{
|
|
Id = providerId,
|
|
Name = "Test Provider",
|
|
Enabled = true
|
|
};
|
|
|
|
var parsedEvent = new Event
|
|
{
|
|
Data = new EventData
|
|
{
|
|
PreviousAttributes = JObject.FromObject(new
|
|
{
|
|
status = "active"
|
|
})
|
|
}
|
|
};
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(true);
|
|
|
|
_providerRepository.GetByIdAsync(providerId)
|
|
.Returns(provider);
|
|
|
|
_stripeFacade.GetTestClock(testClock.Id)
|
|
.Returns(testClock);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
Assert.False(provider.Enabled);
|
|
await _providerService.Received(1).UpdateAsync(provider);
|
|
await _stripeFacade.Received(1).UpdateSubscription(subscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(o => o.CancelAt == frozenTime.AddDays(7)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WithoutValidTransition_DisablesProviderOnly()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
const string subscriptionId = "sub_123";
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
|
};
|
|
|
|
var provider = new Provider
|
|
{
|
|
Id = providerId,
|
|
Name = "Test Provider",
|
|
Enabled = true
|
|
};
|
|
|
|
var parsedEvent = new Event
|
|
{
|
|
Data = new EventData
|
|
{
|
|
PreviousAttributes = JObject.FromObject(new
|
|
{
|
|
status = "unpaid" // No valid transition
|
|
})
|
|
}
|
|
};
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(true);
|
|
|
|
_providerRepository.GetByIdAsync(providerId)
|
|
.Returns(provider);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
Assert.False(provider.Enabled);
|
|
await _providerService.Received(1).UpdateAsync(provider);
|
|
await _stripeFacade.DidNotReceive().UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WithNoPreviousAttributes_DisablesProviderOnly()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
const string subscriptionId = "sub_123";
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
|
};
|
|
|
|
var provider = new Provider
|
|
{
|
|
Id = providerId,
|
|
Name = "Test Provider",
|
|
Enabled = true
|
|
};
|
|
|
|
var parsedEvent = new Event
|
|
{
|
|
Data = new EventData
|
|
{
|
|
PreviousAttributes = null
|
|
}
|
|
};
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(true);
|
|
|
|
_providerRepository.GetByIdAsync(providerId)
|
|
.Returns(provider);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
Assert.False(provider.Enabled);
|
|
await _providerService.Received(1).UpdateAsync(provider);
|
|
await _stripeFacade.DidNotReceive().UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WithIncompleteExpiredStatus_DisablesProvider()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
var subscriptionId = "sub_123";
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.IncompleteExpired,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "renewal" }
|
|
};
|
|
|
|
var provider = new Provider
|
|
{
|
|
Id = providerId,
|
|
Name = "Test Provider",
|
|
Enabled = true
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(true);
|
|
|
|
_providerRepository.GetByIdAsync(providerId)
|
|
.Returns(provider);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
Assert.False(provider.Enabled);
|
|
await _providerService.Received(1).UpdateAsync(provider);
|
|
await _stripeFacade.DidNotReceive().UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WhenFeatureFlagDisabled_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
var subscriptionId = "sub_123";
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(false);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _providerRepository.DidNotReceive().GetByIdAsync(Arg.Any<Guid>());
|
|
await _providerService.DidNotReceive().UpdateAsync(Arg.Any<Provider>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidProviderSubscription_WhenProviderNotFound_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var providerId = Guid.NewGuid();
|
|
var subscriptionId = "sub_123";
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "providerId", providerId.ToString() } },
|
|
LatestInvoice = new Invoice { BillingReason = "subscription_cycle" }
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, null, providerId));
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.PM21821_ProviderPortalTakeover)
|
|
.Returns(true);
|
|
|
|
_providerRepository.GetByIdAsync(providerId)
|
|
.Returns((Provider)null);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _providerService.DidNotReceive().UpdateAsync(Arg.Any<Provider>());
|
|
await _stripeFacade.DidNotReceive().UpdateSubscription(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_UnpaidUserSubscription_DisablesPremiumAndCancelsSubscription()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var subscriptionId = "sub_123";
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
var subscription = new Subscription
|
|
{
|
|
Id = subscriptionId,
|
|
Status = StripeSubscriptionStatus.Unpaid,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "userId", userId.ToString() } },
|
|
Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = new List<SubscriptionItem>
|
|
{
|
|
new() { Price = new Price { Id = IStripeEventUtilityService.PremiumPlanId } }
|
|
}
|
|
}
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, userId, null));
|
|
|
|
_stripeFacade.ListInvoices(Arg.Any<InvoiceListOptions>())
|
|
.Returns(new StripeList<Invoice> { Data = new List<Invoice>() });
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _userService.Received(1)
|
|
.DisablePremiumAsync(userId, currentPeriodEnd);
|
|
await _stripeFacade.Received(1)
|
|
.CancelSubscription(subscriptionId, Arg.Any<SubscriptionCancelOptions>());
|
|
await _stripeFacade.Received(1)
|
|
.ListInvoices(Arg.Is<InvoiceListOptions>(o =>
|
|
o.Status == StripeInvoiceStatus.Open && o.Subscription == subscriptionId));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ActiveOrganizationSubscription_EnablesOrganizationAndUpdatesExpiration()
|
|
{
|
|
// Arrange
|
|
var organizationId = Guid.NewGuid();
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
var subscription = new Subscription
|
|
{
|
|
Status = StripeSubscriptionStatus.Active,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } }
|
|
};
|
|
|
|
var organization = new Organization
|
|
{
|
|
Id = organizationId,
|
|
PlanType = PlanType.EnterpriseAnnually2023
|
|
};
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(organizationId, null, null));
|
|
|
|
_organizationRepository.GetByIdAsync(organizationId)
|
|
.Returns(organization);
|
|
|
|
_stripeFacade.ListInvoices(Arg.Any<InvoiceListOptions>())
|
|
.Returns(new StripeList<Invoice> { Data = new List<Invoice> { new Invoice { Id = "inv_123" } } });
|
|
|
|
var plan = new Enterprise2023Plan(true);
|
|
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
|
.Returns(plan);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _organizationEnableCommand.Received(1)
|
|
.EnableAsync(organizationId);
|
|
await _organizationService.Received(1)
|
|
.UpdateExpirationDateAsync(organizationId, currentPeriodEnd);
|
|
await _pushNotificationService.Received(1)
|
|
.PushSyncOrganizationStatusAsync(organization);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_ActiveUserSubscription_EnablesPremiumAndUpdatesExpiration()
|
|
{
|
|
// Arrange
|
|
var userId = Guid.NewGuid();
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
var subscription = new Subscription
|
|
{
|
|
Status = StripeSubscriptionStatus.Active,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "userId", userId.ToString() } }
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(null, userId, null));
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _userService.Received(1)
|
|
.EnablePremiumAsync(userId, currentPeriodEnd);
|
|
await _userService.Received(1)
|
|
.UpdatePremiumExpirationAsync(userId, currentPeriodEnd);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_SponsoredSubscription_RenewsSponsorship()
|
|
{
|
|
// Arrange
|
|
var organizationId = Guid.NewGuid();
|
|
var currentPeriodEnd = DateTime.UtcNow.AddDays(30);
|
|
var subscription = new Subscription
|
|
{
|
|
Status = StripeSubscriptionStatus.Active,
|
|
CurrentPeriodEnd = currentPeriodEnd,
|
|
Metadata = new Dictionary<string, string> { { "organizationId", organizationId.ToString() } }
|
|
};
|
|
|
|
var parsedEvent = new Event { Data = new EventData() };
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(organizationId, null, null));
|
|
|
|
_stripeEventUtilityService.IsSponsoredSubscription(subscription)
|
|
.Returns(true);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _organizationSponsorshipRenewCommand.Received(1)
|
|
.UpdateExpirationDateAsync(organizationId, currentPeriodEnd);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandleAsync_WhenSubscriptionIsActive_AndOrganizationHasSecretsManagerTrial_AndRemovingSecretsManagerTrial_RemovesPasswordManagerCoupon()
|
|
{
|
|
// Arrange
|
|
var organizationId = Guid.NewGuid();
|
|
var subscription = new Subscription
|
|
{
|
|
Id = "sub_123",
|
|
Status = StripeSubscriptionStatus.Active,
|
|
CurrentPeriodEnd = DateTime.UtcNow.AddDays(10),
|
|
CustomerId = "cus_123",
|
|
Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = new List<SubscriptionItem>
|
|
{
|
|
new() { Plan = new Stripe.Plan { Id = "2023-enterprise-org-seat-annually" } }
|
|
}
|
|
},
|
|
Customer = new Customer
|
|
{
|
|
Balance = 0,
|
|
Discount = new Discount
|
|
{
|
|
Coupon = new Coupon { Id = "sm-standalone" }
|
|
}
|
|
},
|
|
Discount = new Discount
|
|
{
|
|
Coupon = new Coupon { Id = "sm-standalone" }
|
|
},
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
{ "organizationId", organizationId.ToString() }
|
|
}
|
|
};
|
|
|
|
var organization = new Organization
|
|
{
|
|
Id = organizationId,
|
|
PlanType = PlanType.EnterpriseAnnually2023
|
|
};
|
|
|
|
var plan = new Enterprise2023Plan(true);
|
|
_pricingClient.GetPlanOrThrow(organization.PlanType)
|
|
.Returns(plan);
|
|
|
|
var parsedEvent = new Event
|
|
{
|
|
Data = new EventData
|
|
{
|
|
Object = subscription,
|
|
PreviousAttributes = JObject.FromObject(new
|
|
{
|
|
items = new
|
|
{
|
|
data = new[]
|
|
{
|
|
new { plan = new { id = "secrets-manager-enterprise-seat-annually" } }
|
|
}
|
|
},
|
|
Items = new StripeList<SubscriptionItem>
|
|
{
|
|
Data = new List<SubscriptionItem>
|
|
{
|
|
new SubscriptionItem
|
|
{
|
|
Plan = new Stripe.Plan { Id = "secrets-manager-enterprise-seat-annually" }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
};
|
|
|
|
_stripeEventService.GetSubscription(Arg.Any<Event>(), Arg.Any<bool>(), Arg.Any<List<string>>())
|
|
.Returns(subscription);
|
|
|
|
_stripeEventUtilityService.GetIdsFromMetadata(Arg.Any<Dictionary<string, string>>())
|
|
.Returns(Tuple.Create<Guid?, Guid?, Guid?>(organizationId, null, null));
|
|
|
|
_organizationRepository.GetByIdAsync(organizationId)
|
|
.Returns(organization);
|
|
|
|
// Act
|
|
await _sut.HandleAsync(parsedEvent);
|
|
|
|
// Assert
|
|
await _stripeFacade.Received(1).DeleteCustomerDiscount(subscription.CustomerId);
|
|
await _stripeFacade.Received(1).DeleteSubscriptionDiscount(subscription.Id);
|
|
}
|
|
}
|