1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-03 14:03:33 +01:00
bitwarden-server/test/Core.Test/Billing/Commands/RemovePaymentMethodCommandTests.cs
Alex Morask ffd988eeda
[AC-1904] Implement endpoint to retrieve Provider subscription (#3921)
* Refactor Core.Billing prior to adding new logic

* Add ProviderBillingQueries.GetSubscriptionData

* Add ProviderBillingController.GetSubscriptionAsync
2024-03-28 08:46:12 -04:00

359 lines
13 KiB
C#

using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Commands.Implementations;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
using static Bit.Core.Test.Billing.Utilities;
using BT = Braintree;
using S = Stripe;
namespace Bit.Core.Test.Billing.Commands;
[SutProviderCustomize]
public class RemovePaymentMethodCommandTests
{
[Theory, BitAutoData]
public async Task RemovePaymentMethod_NullOrganization_ArgumentNullException(
SutProvider<RemovePaymentMethodCommand> sutProvider) =>
await Assert.ThrowsAsync<ArgumentNullException>(() => sutProvider.Sut.RemovePaymentMethod(null));
[Theory, BitAutoData]
public async Task RemovePaymentMethod_NonStripeGateway_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.BitPay;
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_NoGatewayCustomerId_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
organization.GatewayCustomerId = null;
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_NoStripeCustomer_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
sutProvider.GetDependency<IStripeAdapter>()
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.ReturnsNull();
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Braintree_NoCustomer_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string braintreeCustomerId = "1";
var stripeCustomer = new S.Customer
{
Metadata = new Dictionary<string, string>
{
{ "btCustomerId", braintreeCustomerId }
}
};
sutProvider.GetDependency<IStripeAdapter>()
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
var (braintreeGateway, customerGateway, paymentMethodGateway) = Setup(sutProvider.GetDependency<BT.IBraintreeGateway>());
customerGateway.FindAsync(braintreeCustomerId).ReturnsNull();
braintreeGateway.Customer.Returns(customerGateway);
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
await customerGateway.Received(1).FindAsync(braintreeCustomerId);
await customerGateway.DidNotReceiveWithAnyArgs()
.UpdateAsync(Arg.Any<string>(), Arg.Any<BT.CustomerRequest>());
await paymentMethodGateway.DidNotReceiveWithAnyArgs().DeleteAsync(Arg.Any<string>());
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Braintree_NoPaymentMethod_NoOp(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string braintreeCustomerId = "1";
var stripeCustomer = new S.Customer
{
Metadata = new Dictionary<string, string>
{
{ "btCustomerId", braintreeCustomerId }
}
};
sutProvider.GetDependency<IStripeAdapter>()
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
var (_, customerGateway, paymentMethodGateway) = Setup(sutProvider.GetDependency<BT.IBraintreeGateway>());
var braintreeCustomer = Substitute.For<BT.Customer>();
braintreeCustomer.PaymentMethods.Returns(Array.Empty<BT.PaymentMethod>());
customerGateway.FindAsync(braintreeCustomerId).Returns(braintreeCustomer);
await sutProvider.Sut.RemovePaymentMethod(organization);
await customerGateway.Received(1).FindAsync(braintreeCustomerId);
await customerGateway.DidNotReceiveWithAnyArgs().UpdateAsync(Arg.Any<string>(), Arg.Any<BT.CustomerRequest>());
await paymentMethodGateway.DidNotReceiveWithAnyArgs().DeleteAsync(Arg.Any<string>());
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Braintree_CustomerUpdateFails_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string braintreeCustomerId = "1";
const string braintreePaymentMethodToken = "TOKEN";
var stripeCustomer = new S.Customer
{
Metadata = new Dictionary<string, string>
{
{ "btCustomerId", braintreeCustomerId }
}
};
sutProvider.GetDependency<IStripeAdapter>()
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
var (_, customerGateway, paymentMethodGateway) = Setup(sutProvider.GetDependency<BT.IBraintreeGateway>());
var braintreeCustomer = Substitute.For<BT.Customer>();
var paymentMethod = Substitute.For<BT.PaymentMethod>();
paymentMethod.Token.Returns(braintreePaymentMethodToken);
paymentMethod.IsDefault.Returns(true);
braintreeCustomer.PaymentMethods.Returns(new[]
{
paymentMethod
});
customerGateway.FindAsync(braintreeCustomerId).Returns(braintreeCustomer);
var updateBraintreeCustomerResult = Substitute.For<BT.Result<BT.Customer>>();
updateBraintreeCustomerResult.IsSuccess().Returns(false);
customerGateway.UpdateAsync(
braintreeCustomerId,
Arg.Is<BT.CustomerRequest>(request => request.DefaultPaymentMethodToken == null))
.Returns(updateBraintreeCustomerResult);
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
await customerGateway.Received(1).FindAsync(braintreeCustomerId);
await customerGateway.Received(1).UpdateAsync(braintreeCustomerId, Arg.Is<BT.CustomerRequest>(request =>
request.DefaultPaymentMethodToken == null));
await paymentMethodGateway.DidNotReceiveWithAnyArgs().DeleteAsync(paymentMethod.Token);
await customerGateway.DidNotReceive().UpdateAsync(braintreeCustomerId, Arg.Is<BT.CustomerRequest>(request =>
request.DefaultPaymentMethodToken == paymentMethod.Token));
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Braintree_PaymentMethodDeleteFails_RollBack_ContactSupport(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string braintreeCustomerId = "1";
const string braintreePaymentMethodToken = "TOKEN";
var stripeCustomer = new S.Customer
{
Metadata = new Dictionary<string, string>
{
{ "btCustomerId", braintreeCustomerId }
}
};
sutProvider.GetDependency<IStripeAdapter>()
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
var (_, customerGateway, paymentMethodGateway) = Setup(sutProvider.GetDependency<BT.IBraintreeGateway>());
var braintreeCustomer = Substitute.For<BT.Customer>();
var paymentMethod = Substitute.For<BT.PaymentMethod>();
paymentMethod.Token.Returns(braintreePaymentMethodToken);
paymentMethod.IsDefault.Returns(true);
braintreeCustomer.PaymentMethods.Returns(new[]
{
paymentMethod
});
customerGateway.FindAsync(braintreeCustomerId).Returns(braintreeCustomer);
var updateBraintreeCustomerResult = Substitute.For<BT.Result<BT.Customer>>();
updateBraintreeCustomerResult.IsSuccess().Returns(true);
customerGateway.UpdateAsync(braintreeCustomerId, Arg.Any<BT.CustomerRequest>())
.Returns(updateBraintreeCustomerResult);
var deleteBraintreePaymentMethodResult = Substitute.For<BT.Result<BT.PaymentMethod>>();
deleteBraintreePaymentMethodResult.IsSuccess().Returns(false);
paymentMethodGateway.DeleteAsync(paymentMethod.Token).Returns(deleteBraintreePaymentMethodResult);
await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization));
await customerGateway.Received(1).FindAsync(braintreeCustomerId);
await customerGateway.Received(1).UpdateAsync(braintreeCustomerId, Arg.Is<BT.CustomerRequest>(request =>
request.DefaultPaymentMethodToken == null));
await paymentMethodGateway.Received(1).DeleteAsync(paymentMethod.Token);
await customerGateway.Received(1).UpdateAsync(braintreeCustomerId, Arg.Is<BT.CustomerRequest>(request =>
request.DefaultPaymentMethodToken == paymentMethod.Token));
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Stripe_Legacy_RemovesSources(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string bankAccountId = "bank_account_id";
const string cardId = "card_id";
var sources = new List<S.IPaymentSource>
{
new S.BankAccount { Id = bankAccountId }, new S.Card { Id = cardId }
};
var stripeCustomer = new S.Customer { Sources = new S.StripeList<S.IPaymentSource> { Data = sources } };
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
stripeAdapter
.PaymentMethodListAutoPagingAsync(Arg.Any<S.PaymentMethodListOptions>())
.Returns(GetPaymentMethodsAsync(new List<S.PaymentMethod>()));
await sutProvider.Sut.RemovePaymentMethod(organization);
await stripeAdapter.Received(1).BankAccountDeleteAsync(stripeCustomer.Id, bankAccountId);
await stripeAdapter.Received(1).CardDeleteAsync(stripeCustomer.Id, cardId);
await stripeAdapter.DidNotReceiveWithAnyArgs()
.PaymentMethodDetachAsync(Arg.Any<string>(), Arg.Any<S.PaymentMethodDetachOptions>());
}
[Theory, BitAutoData]
public async Task RemovePaymentMethod_Stripe_DetachesPaymentMethods(
Organization organization,
SutProvider<RemovePaymentMethodCommand> sutProvider)
{
organization.Gateway = GatewayType.Stripe;
const string bankAccountId = "bank_account_id";
const string cardId = "card_id";
var sources = new List<S.IPaymentSource>();
var stripeCustomer = new S.Customer { Sources = new S.StripeList<S.IPaymentSource> { Data = sources } };
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter
.CustomerGetAsync(organization.GatewayCustomerId, Arg.Any<S.CustomerGetOptions>())
.Returns(stripeCustomer);
stripeAdapter
.PaymentMethodListAutoPagingAsync(Arg.Any<S.PaymentMethodListOptions>())
.Returns(GetPaymentMethodsAsync(new List<S.PaymentMethod>
{
new ()
{
Id = bankAccountId
},
new ()
{
Id = cardId
}
}));
await sutProvider.Sut.RemovePaymentMethod(organization);
await stripeAdapter.DidNotReceiveWithAnyArgs().BankAccountDeleteAsync(Arg.Any<string>(), Arg.Any<string>());
await stripeAdapter.DidNotReceiveWithAnyArgs().CardDeleteAsync(Arg.Any<string>(), Arg.Any<string>());
await stripeAdapter.Received(1)
.PaymentMethodDetachAsync(bankAccountId, Arg.Any<S.PaymentMethodDetachOptions>());
await stripeAdapter.Received(1)
.PaymentMethodDetachAsync(cardId, Arg.Any<S.PaymentMethodDetachOptions>());
}
private static async IAsyncEnumerable<S.PaymentMethod> GetPaymentMethodsAsync(
IEnumerable<S.PaymentMethod> paymentMethods)
{
foreach (var paymentMethod in paymentMethods)
{
yield return paymentMethod;
}
await Task.CompletedTask;
}
private static (BT.IBraintreeGateway, BT.ICustomerGateway, BT.IPaymentMethodGateway) Setup(
BT.IBraintreeGateway braintreeGateway)
{
var customerGateway = Substitute.For<BT.ICustomerGateway>();
var paymentMethodGateway = Substitute.For<BT.IPaymentMethodGateway>();
braintreeGateway.Customer.Returns(customerGateway);
braintreeGateway.PaymentMethod.Returns(paymentMethodGateway);
return (braintreeGateway, customerGateway, paymentMethodGateway);
}
}