1
0
mirror of https://github.com/bitwarden/server synced 2026-01-10 12:33:49 +00:00

Merge branch 'main' into billing/PM-24964/msp-unable-verfy-bank-account

This commit is contained in:
Alex Morask
2025-09-03 11:25:12 -05:00
97 changed files with 4274 additions and 1412 deletions

View File

@@ -0,0 +1,394 @@
using Bit.Core.Billing.Extensions;
using Stripe;
using Xunit;
namespace Bit.Core.Test.Billing.Extensions;
public class InvoiceExtensionsTests
{
private static Invoice CreateInvoiceWithLines(params InvoiceLineItem[] lineItems)
{
return new Invoice
{
Lines = new StripeList<InvoiceLineItem>
{
Data = lineItems?.ToList() ?? new List<InvoiceLineItem>()
}
};
}
#region FormatForProvider Tests
[Fact]
public void FormatForProvider_NullLines_ReturnsEmptyList()
{
// Arrange
var invoice = new Invoice
{
Lines = null
};
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
[Fact]
public void FormatForProvider_EmptyLines_ReturnsEmptyList()
{
// Arrange
var invoice = CreateInvoiceWithLines();
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
[Fact]
public void FormatForProvider_NullLineItem_SkipsNullLine()
{
// Arrange
var invoice = CreateInvoiceWithLines(null);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
[Fact]
public void FormatForProvider_LineWithNullDescription_SkipsLine()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem { Description = null, Quantity = 1, Amount = 1000 }
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.NotNull(result);
Assert.Empty(result);
}
[Fact]
public void FormatForProvider_ProviderPortalTeams_FormatsCorrectly()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Teams (at $6.00 / month)",
Quantity = 5,
Amount = 3000
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("5 × Manage service provider (at $6.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_ProviderPortalEnterprise_FormatsCorrectly()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Enterprise (at $4.00 / month)",
Quantity = 10,
Amount = 4000
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("10 × Manage service provider (at $4.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_ProviderPortalWithoutPriceInfo_FormatsWithoutPrice()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Teams",
Quantity = 3,
Amount = 1800
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("3 × Manage service provider ", result[0]);
}
[Fact]
public void FormatForProvider_BusinessUnitPortalEnterprise_FormatsCorrectly()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Business Unit Portal - Enterprise (at $5.00 / month)",
Quantity = 8,
Amount = 4000
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("8 × Manage service provider (at $5.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_BusinessUnitPortalGeneric_FormatsCorrectly()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Business Unit Portal (at $3.00 / month)",
Quantity = 2,
Amount = 600
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("2 × Manage service provider (at $3.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_TaxLineWithPriceInfo_FormatsCorrectly()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Tax (at $2.00 / month)",
Quantity = 1,
Amount = 200
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("1 × Tax (at $2.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_TaxLineWithoutPriceInfo_CalculatesPrice()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Tax",
Quantity = 2,
Amount = 400 // $4.00 total, $2.00 per item
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("2 × Tax (at $2.00 / month)", result[0]);
}
[Fact]
public void FormatForProvider_TaxLineWithZeroQuantity_DoesNotCalculatePrice()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Tax",
Quantity = 0,
Amount = 200
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("0 × Tax ", result[0]);
}
[Fact]
public void FormatForProvider_OtherLineItem_ReturnsAsIs()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Some other service",
Quantity = 1,
Amount = 1000
}
);
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("Some other service", result[0]);
}
[Fact]
public void FormatForProvider_InvoiceLevelTax_AddsToResult()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Teams",
Quantity = 1,
Amount = 600
}
);
invoice.Tax = 120; // $1.20 in cents
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Equal(2, result.Count);
Assert.Equal("1 × Manage service provider ", result[0]);
Assert.Equal("1 × Tax (at $1.20 / month)", result[1]);
}
[Fact]
public void FormatForProvider_NoInvoiceLevelTax_DoesNotAddTax()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Teams",
Quantity = 1,
Amount = 600
}
);
invoice.Tax = null;
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("1 × Manage service provider ", result[0]);
}
[Fact]
public void FormatForProvider_ZeroInvoiceLevelTax_DoesNotAddTax()
{
// Arrange
var invoice = CreateInvoiceWithLines(
new InvoiceLineItem
{
Description = "Provider Portal - Teams",
Quantity = 1,
Amount = 600
}
);
invoice.Tax = 0;
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Single(result);
Assert.Equal("1 × Manage service provider ", result[0]);
}
[Fact]
public void FormatForProvider_ComplexScenario_HandlesAllLineTypes()
{
// Arrange
var lineItems = new StripeList<InvoiceLineItem>();
lineItems.Data = new List<InvoiceLineItem>
{
new InvoiceLineItem
{
Description = "Provider Portal - Teams (at $6.00 / month)", Quantity = 5, Amount = 3000
},
new InvoiceLineItem
{
Description = "Provider Portal - Enterprise (at $4.00 / month)", Quantity = 10, Amount = 4000
},
new InvoiceLineItem { Description = "Tax", Quantity = 1, Amount = 800 },
new InvoiceLineItem { Description = "Custom Service", Quantity = 2, Amount = 2000 }
};
var invoice = new Invoice
{
Lines = lineItems,
Tax = 200 // Additional $2.00 tax
};
var subscription = new Subscription();
// Act
var result = invoice.FormatForProvider(subscription);
// Assert
Assert.Equal(5, result.Count);
Assert.Equal("5 × Manage service provider (at $6.00 / month)", result[0]);
Assert.Equal("10 × Manage service provider (at $4.00 / month)", result[1]);
Assert.Equal("1 × Tax (at $8.00 / month)", result[2]);
Assert.Equal("Custom Service", result[3]);
Assert.Equal("1 × Tax (at $2.00 / month)", result[4]);
}
#endregion
}

View File

@@ -181,7 +181,7 @@ public class PreviewTaxAmountCommandTests
options.SubscriptionDetails.Items.Count == 1 &&
options.SubscriptionDetails.Items[0].Price == plan.PasswordManager.StripeSeatPlanId &&
options.SubscriptionDetails.Items[0].Quantity == 1 &&
options.AutomaticTax.Enabled == false
options.AutomaticTax.Enabled == true
))
.Returns(expectedInvoice);
@@ -273,4 +273,269 @@ public class PreviewTaxAmountCommandTests
var badRequest = result.AsT1;
Assert.Equal("We couldn't find a corresponding tax ID type for the tax ID you provided. Please try again or contact support for assistance.", badRequest.Response);
}
[Fact]
public async Task Run_USBased_PersonalUse_SetsAutomaticTaxEnabled()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.FamiliesAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "US",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.AutomaticTax.Enabled == true
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_USBased_BusinessUse_SetsAutomaticTaxEnabled()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.EnterpriseAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "US",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.AutomaticTax.Enabled == true
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_NonUSBased_PersonalUse_SetsAutomaticTaxEnabled()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.FamiliesAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "CA",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.AutomaticTax.Enabled == true
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_NonUSBased_BusinessUse_SetsAutomaticTaxEnabled()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.EnterpriseAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "CA",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.AutomaticTax.Enabled == true
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_USBased_PersonalUse_DoesNotSetTaxExempt()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.FamiliesAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "US",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.CustomerDetails.TaxExempt == null
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_USBased_BusinessUse_DoesNotSetTaxExempt()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.EnterpriseAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "US",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.CustomerDetails.TaxExempt == null
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_NonUSBased_PersonalUse_DoesNotSetTaxExempt()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.FamiliesAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "CA",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.CustomerDetails.TaxExempt == null
));
Assert.True(result.IsT0);
}
[Fact]
public async Task Run_NonUSBased_BusinessUse_SetsTaxExemptReverse()
{
// Arrange
var parameters = new OrganizationTrialParameters
{
PlanType = PlanType.EnterpriseAnnually,
ProductType = ProductType.PasswordManager,
TaxInformation = new TaxInformationDTO
{
Country = "CA",
PostalCode = "12345"
}
};
var plan = StaticStore.GetPlan(parameters.PlanType);
_pricingClient.GetPlanOrThrow(parameters.PlanType).Returns(plan);
var expectedInvoice = new Invoice { Tax = 1000 }; // $10.00 in cents
_stripeAdapter.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
.Returns(expectedInvoice);
// Act
var result = await _command.Run(parameters);
// Assert
await _stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
options.CustomerDetails.TaxExempt == StripeConstants.TaxExempt.Reverse
));
Assert.True(result.IsT0);
}
}

View File

@@ -1,105 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Core.Billing.Pricing;
using Bit.Core.Billing.Tax.Models;
using Bit.Core.Billing.Tax.Services.Implementations;
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Billing.Tax.Services;
[SutProviderCustomize]
public class AutomaticTaxFactoryTests
{
[BitAutoData]
[Theory]
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenSubscriberIsUser(SutProvider<AutomaticTaxFactory> sut)
{
var parameters = new AutomaticTaxFactoryParameters(new User(), []);
var actual = await sut.Sut.CreateAsync(parameters);
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
}
[BitAutoData]
[Theory]
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenSubscriberIsOrganizationWithFamiliesAnnuallyPrice(
SutProvider<AutomaticTaxFactory> sut)
{
var familiesPlan = new FamiliesPlan();
var parameters = new AutomaticTaxFactoryParameters(new Organization(), [familiesPlan.PasswordManager.StripePlanId]);
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
.Returns(new FamiliesPlan());
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually2019))
.Returns(new Families2019Plan());
var actual = await sut.Sut.CreateAsync(parameters);
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
}
[Theory]
[BitAutoData]
public async Task CreateAsync_ReturnsBusinessUseStrategy_WhenSubscriberIsOrganizationWithBusinessUsePrice(
EnterpriseAnnually plan,
SutProvider<AutomaticTaxFactory> sut)
{
var parameters = new AutomaticTaxFactoryParameters(new Organization(), [plan.PasswordManager.StripePlanId]);
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
.Returns(new FamiliesPlan());
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually2019))
.Returns(new Families2019Plan());
var actual = await sut.Sut.CreateAsync(parameters);
Assert.IsType<BusinessUseAutomaticTaxStrategy>(actual);
}
[Theory]
[BitAutoData]
public async Task CreateAsync_ReturnsPersonalUseStrategy_WhenPlanIsMeantForPersonalUse(SutProvider<AutomaticTaxFactory> sut)
{
var parameters = new AutomaticTaxFactoryParameters(PlanType.FamiliesAnnually);
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == parameters.PlanType.Value))
.Returns(new FamiliesPlan());
var actual = await sut.Sut.CreateAsync(parameters);
Assert.IsType<PersonalUseAutomaticTaxStrategy>(actual);
}
[Theory]
[BitAutoData]
public async Task CreateAsync_ReturnsBusinessUseStrategy_WhenPlanIsMeantForBusinessUse(SutProvider<AutomaticTaxFactory> sut)
{
var parameters = new AutomaticTaxFactoryParameters(PlanType.EnterpriseAnnually);
sut.GetDependency<IPricingClient>()
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == parameters.PlanType.Value))
.Returns(new EnterprisePlan(true));
var actual = await sut.Sut.CreateAsync(parameters);
Assert.IsType<BusinessUseAutomaticTaxStrategy>(actual);
}
public record EnterpriseAnnually : EnterprisePlan
{
public EnterpriseAnnually() : base(true)
{
}
}
}

View File

@@ -1,492 +0,0 @@
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Tax.Services.Implementations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Stripe;
using Xunit;
namespace Bit.Core.Test.Billing.Tax.Services;
[SutProviderCustomize]
public class BusinessUseAutomaticTaxStrategyTests
{
[Theory]
[BitAutoData]
public void GetUpdateOptions_ReturnsNull_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription();
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(false);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.Null(actual);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_ReturnsNull_WhenSubscriptionDoesNotNeedUpdating(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "US",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.Null(actual);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.False(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForAmericanCustomers(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = "US",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.True(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>
{
new()
{
Country = "ES",
Type = "eu_vat",
Value = "ESZ8880999Z"
}
}
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.True(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_ThrowsArgumentNullException_WhenTaxIdsIsNull(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = null
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
Assert.Throws<ArgumentNullException>(() => sutProvider.Sut.GetUpdateOptions(subscription));
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>()
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.False(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsNothing_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
Customer = new Customer
{
Address = new()
{
Country = "US"
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(false);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.Null(options.AutomaticTax);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsNothing_WhenSubscriptionDoesNotNeedUpdating(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "US",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.Null(options.AutomaticTax);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.False(options.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForAmericanCustomers(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = "US",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.True(options.AutomaticTax!.Enabled);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>
{
new()
{
Country = "ES",
Type = "eu_vat",
Value = "ESZ8880999Z"
}
}
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.True(options.AutomaticTax!.Enabled);
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_ThrowsArgumentNullException_WhenTaxIdsIsNull(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = null
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
Assert.Throws<ArgumentNullException>(() => sutProvider.Sut.SetUpdateOptions(options, subscription));
}
[Theory]
[BitAutoData]
public void SetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
SutProvider<BusinessUseAutomaticTaxStrategy> sutProvider)
{
var options = new SubscriptionUpdateOptions();
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "ES",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>()
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
sutProvider.Sut.SetUpdateOptions(options, subscription);
Assert.False(options.AutomaticTax!.Enabled);
}
}

View File

@@ -1,35 +0,0 @@
using Bit.Core.Billing.Tax.Services;
using Stripe;
namespace Bit.Core.Test.Billing.Tax.Services;
/// <param name="isAutomaticTaxEnabled">
/// Whether the subscription options will have automatic tax enabled or not.
/// </param>
public class FakeAutomaticTaxStrategy(
bool isAutomaticTaxEnabled) : IAutomaticTaxStrategy
{
public SubscriptionUpdateOptions? GetUpdateOptions(Subscription subscription)
{
return new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled }
};
}
public void SetCreateOptions(SubscriptionCreateOptions options, Customer customer)
{
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
}
public void SetUpdateOptions(SubscriptionUpdateOptions options, Subscription subscription)
{
options.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
}
public void SetInvoiceCreatePreviewOptions(InvoiceCreatePreviewOptions options)
{
options.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = isAutomaticTaxEnabled };
}
}

View File

@@ -1,217 +0,0 @@
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Tax.Services.Implementations;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Stripe;
using Xunit;
namespace Bit.Core.Test.Billing.Tax.Services;
[SutProviderCustomize]
public class PersonalUseAutomaticTaxStrategyTests
{
[Theory]
[BitAutoData]
public void GetUpdateOptions_ReturnsNull_WhenFeatureFlagAllowingToUpdateSubscriptionsIsDisabled(
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription();
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(false);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.Null(actual);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_ReturnsNull_WhenSubscriptionDoesNotNeedUpdating(
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Address = new Address
{
Country = "US",
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.Null(actual);
}
[Theory]
[BitAutoData]
public void GetUpdateOptions_SetsAutomaticTaxToFalse_WhenTaxLocationIsUnrecognizedOrInvalid(
SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = true
},
Customer = new Customer
{
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.UnrecognizedLocation
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.False(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData("CA")]
[BitAutoData("ES")]
[BitAutoData("US")]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForAllCountries(
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = country
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.True(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData("CA")]
[BitAutoData("ES")]
[BitAutoData("US")]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithTaxIds(
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = country,
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>
{
new()
{
Country = "ES",
Type = "eu_vat",
Value = "ESZ8880999Z"
}
}
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.True(actual.AutomaticTax.Enabled);
}
[Theory]
[BitAutoData("CA")]
[BitAutoData("ES")]
[BitAutoData("US")]
public void GetUpdateOptions_SetsAutomaticTaxToTrue_ForGlobalCustomersWithoutTaxIds(
string country, SutProvider<PersonalUseAutomaticTaxStrategy> sutProvider)
{
var subscription = new Subscription
{
AutomaticTax = new SubscriptionAutomaticTax
{
Enabled = false
},
Customer = new Customer
{
Address = new Address
{
Country = country
},
Tax = new CustomerTax
{
AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported
},
TaxIds = new StripeList<TaxId>
{
Data = new List<TaxId>()
}
}
};
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM19422_AllowAutomaticTaxUpdates))
.Returns(true);
var actual = sutProvider.Sut.GetUpdateOptions(subscription);
Assert.NotNull(actual);
Assert.True(actual.AutomaticTax.Enabled);
}
}