mirror of
https://github.com/bitwarden/server
synced 2025-12-26 13:13:24 +00:00
Merge branch 'main' into billing/PM-24964/msp-unable-verfy-bank-account
This commit is contained in:
@@ -12,7 +12,7 @@ public class SendAccessClaimsPrincipalExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
var guid = Guid.NewGuid();
|
||||
var claims = new[] { new Claim(Claims.SendId, guid.ToString()) };
|
||||
var claims = new[] { new Claim(Claims.SendAccessClaims.SendId, guid.ToString()) };
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
|
||||
|
||||
// Act
|
||||
@@ -30,19 +30,19 @@ public class SendAccessClaimsPrincipalExtensionsTests
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => principal.GetSendId());
|
||||
Assert.Equal("Send ID claim not found.", ex.Message);
|
||||
Assert.Equal("send_id claim not found.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSendId_ThrowsInvalidOperationException_WhenClaimValueIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim(Claims.SendId, "not-a-guid") };
|
||||
var claims = new[] { new Claim(Claims.SendAccessClaims.SendId, "not-a-guid") };
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => principal.GetSendId());
|
||||
Assert.Equal("Invalid Send ID claim value.", ex.Message);
|
||||
Assert.Equal("Invalid send_id claim value.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
394
test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs
Normal file
394
test/Core.Test/Billing/Extensions/InvoiceExtensionsTests.cs
Normal 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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -247,11 +247,18 @@ public class HandlebarsMailServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this test when we add actual tests. It only proves that
|
||||
// we've properly constructed the system under test.
|
||||
[Fact]
|
||||
public void ServiceExists()
|
||||
public async Task SendSendEmailOtpEmailAsync_SendsEmail()
|
||||
{
|
||||
Assert.NotNull(_sut);
|
||||
// Arrange
|
||||
var email = "test@example.com";
|
||||
var token = "aToken";
|
||||
var subject = string.Format("Your Bitwarden Send verification code is {0}", token);
|
||||
|
||||
// Act
|
||||
await _sut.SendSendEmailOtpEmailAsync(email, token, subject);
|
||||
|
||||
// Assert
|
||||
await _mailDeliveryService.Received(1).SendEmailAsync(Arg.Any<MailMessage>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Constants;
|
||||
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.Requests;
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.Billing.Tax.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -23,10 +21,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -74,10 +68,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -125,10 +115,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithoutAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -177,10 +163,6 @@ public class StripePaymentServiceTests
|
||||
public async Task PreviewInvoiceAsync_ForOrganization_CalculatesSalesTaxCorrectlyForFamiliesForEnterpriseWithAdditionalStorage(
|
||||
SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IAutomaticTaxFactory>()
|
||||
.CreateAsync(Arg.Is<AutomaticTaxFactoryParameters>(p => p.PlanType == PlanType.FamiliesAnnually))
|
||||
.Returns(new FakeAutomaticTaxStrategy(true));
|
||||
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
@@ -223,4 +205,340 @@ public class StripePaymentServiceTests
|
||||
Assert.Equal(4.08M, actual.TotalAmount);
|
||||
Assert.Equal(4M, actual.TaxableBaseAmount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsAutomaticTaxEnabled(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.AutomaticTax.Enabled == true
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_USBased_BusinessUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "US",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_PersonalUse_DoesNotSetTaxExempt(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var familiesPlan = new FamiliesPlan();
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.FamiliesAnnually))
|
||||
.Returns(familiesPlan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.FamiliesAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == null
|
||||
));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PreviewInvoiceAsync_NonUSBased_BusinessUse_SetsTaxExemptReverse(SutProvider<StripePaymentService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var plan = new EnterprisePlan(true);
|
||||
sutProvider.GetDependency<IPricingClient>()
|
||||
.GetPlanOrThrow(Arg.Is<PlanType>(p => p == PlanType.EnterpriseAnnually))
|
||||
.Returns(plan);
|
||||
|
||||
var parameters = new PreviewOrganizationInvoiceRequestBody
|
||||
{
|
||||
PasswordManager = new OrganizationPasswordManagerRequestModel
|
||||
{
|
||||
Plan = PlanType.EnterpriseAnnually
|
||||
},
|
||||
TaxInformation = new TaxInformationRequestModel
|
||||
{
|
||||
Country = "FR",
|
||||
PostalCode = "12345"
|
||||
}
|
||||
};
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
stripeAdapter
|
||||
.InvoiceCreatePreviewAsync(Arg.Any<InvoiceCreatePreviewOptions>())
|
||||
.Returns(new Invoice
|
||||
{
|
||||
TotalExcludingTax = 400,
|
||||
Tax = 8,
|
||||
Total = 408
|
||||
});
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PreviewInvoiceAsync(parameters, null, null);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).InvoiceCreatePreviewAsync(Arg.Is<InvoiceCreatePreviewOptions>(options =>
|
||||
options.CustomerDetails.TaxExempt == StripeConstants.TaxExempt.Reverse
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,41 @@ public class ImportCiphersAsyncCommandTests
|
||||
await sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships, importingUserId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).CreateAsync(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<ICipherRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ImportIntoIndividualVaultAsync_WithBulkResourceCreationServiceEnabled_Success(
|
||||
Guid importingUserId,
|
||||
List<CipherDetails> ciphers,
|
||||
SutProvider<ImportCiphersCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.OrganizationDataOwnership)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IFolderRepository>()
|
||||
.GetManyByUserIdAsync(importingUserId)
|
||||
.Returns(new List<Folder>());
|
||||
|
||||
var folders = new List<Folder> { new Folder { UserId = importingUserId } };
|
||||
|
||||
var folderRelationships = new List<KeyValuePair<int, int>>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships, importingUserId);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<ICipherRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync_vNext(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
@@ -77,7 +111,45 @@ public class ImportCiphersAsyncCommandTests
|
||||
|
||||
await sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships, importingUserId);
|
||||
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).CreateAsync(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<ICipherRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ImportIntoIndividualVaultAsync_WithBulkResourceCreationServiceEnabled_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyDisabled_Success(
|
||||
Guid importingUserId,
|
||||
List<CipherDetails> ciphers,
|
||||
SutProvider<ImportCiphersCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<OrganizationDataOwnershipPolicyRequirement>(importingUserId)
|
||||
.Returns(new OrganizationDataOwnershipPolicyRequirement(
|
||||
OrganizationDataOwnershipState.Disabled,
|
||||
[]));
|
||||
|
||||
sutProvider.GetDependency<IFolderRepository>()
|
||||
.GetManyByUserIdAsync(importingUserId)
|
||||
.Returns(new List<Folder>());
|
||||
|
||||
var folders = new List<Folder> { new Folder { UserId = importingUserId } };
|
||||
|
||||
var folderRelationships = new List<KeyValuePair<int, int>>();
|
||||
|
||||
await sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships, importingUserId);
|
||||
|
||||
await sutProvider.GetDependency<ICipherRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync_vNext(importingUserId, ciphers, Arg.Any<List<Folder>>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
@@ -187,6 +259,66 @@ public class ImportCiphersAsyncCommandTests
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ImportIntoOrganizationalVaultAsync_WithBulkResourceCreationServiceEnabled_Success(
|
||||
Organization organization,
|
||||
Guid importingUserId,
|
||||
OrganizationUser importingOrganizationUser,
|
||||
List<Collection> collections,
|
||||
List<CipherDetails> ciphers,
|
||||
SutProvider<ImportCiphersCommand> sutProvider)
|
||||
{
|
||||
organization.MaxCollections = null;
|
||||
importingOrganizationUser.OrganizationId = organization.Id;
|
||||
|
||||
foreach (var collection in collections)
|
||||
{
|
||||
collection.OrganizationId = organization.Id;
|
||||
}
|
||||
|
||||
foreach (var cipher in ciphers)
|
||||
{
|
||||
cipher.OrganizationId = organization.Id;
|
||||
}
|
||||
|
||||
KeyValuePair<int, int>[] collectionRelationships = {
|
||||
new(0, 0),
|
||||
new(1, 1),
|
||||
new(2, 2)
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByOrganizationAsync(organization.Id, importingUserId)
|
||||
.Returns(importingOrganizationUser);
|
||||
|
||||
// Set up a collection that already exists in the organization
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByOrganizationIdAsync(organization.Id)
|
||||
.Returns(new List<Collection> { collections[0] });
|
||||
|
||||
await sutProvider.Sut.ImportIntoOrganizationalVaultAsync(collections, ciphers, collectionRelationships, importingUserId);
|
||||
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).CreateAsync_vNext(
|
||||
ciphers,
|
||||
Arg.Is<IEnumerable<Collection>>(cols => cols.Count() == collections.Count - 1 &&
|
||||
!cols.Any(c => c.Id == collections[0].Id) && // Check that the collection that already existed in the organization was not added
|
||||
cols.All(c => collections.Any(x => c.Name == x.Name))),
|
||||
Arg.Is<IEnumerable<CollectionCipher>>(c => c.Count() == ciphers.Count),
|
||||
Arg.Is<IEnumerable<CollectionUser>>(cus =>
|
||||
cus.Count() == collections.Count - 1 &&
|
||||
!cus.Any(cu => cu.CollectionId == collections[0].Id) && // Check that access was not added for the collection that already existed in the organization
|
||||
cus.All(cu => cu.OrganizationUserId == importingOrganizationUser.Id && cu.Manage == true)));
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1).PushSyncVaultAsync(importingUserId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ImportIntoOrganizationalVaultAsync_ThrowsBadRequestException(
|
||||
Organization organization,
|
||||
|
||||
@@ -674,6 +674,32 @@ public class CipherServiceTests
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData("Correct Time")]
|
||||
public async Task ShareManyAsync_CorrectRevisionDate_WithBulkResourceCreationServiceEnabled_Passes(string revisionDateString,
|
||||
SutProvider<CipherService> sutProvider, IEnumerable<CipherDetails> ciphers, Organization organization, List<Guid> collectionIds)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id)
|
||||
.Returns(new Organization
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
MaxStorageGb = 100
|
||||
});
|
||||
|
||||
var cipherInfos = ciphers.Select(c => (c,
|
||||
string.IsNullOrEmpty(revisionDateString) ? null : (DateTime?)c.RevisionDate));
|
||||
var sharingUserId = ciphers.First().UserId.Value;
|
||||
|
||||
await sutProvider.Sut.ShareManyAsync(cipherInfos, organization.Id, collectionIds, sharingUserId);
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync_vNext(sharingUserId,
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RestoreAsync_UpdatesUserCipher(Guid restoringUserId, CipherDetails cipher, SutProvider<CipherService> sutProvider)
|
||||
@@ -1094,6 +1120,33 @@ public class CipherServiceTests
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ShareManyAsync_PaidOrgWithAttachment_WithBulkResourceCreationServiceEnabled_Passes(SutProvider<CipherService> sutProvider,
|
||||
IEnumerable<CipherDetails> ciphers, Guid organizationId, List<Guid> collectionIds)
|
||||
{
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId)
|
||||
.Returns(new Organization
|
||||
{
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
MaxStorageGb = 100
|
||||
});
|
||||
ciphers.FirstOrDefault().Attachments =
|
||||
"{\"attachment1\":{\"Size\":\"250\",\"FileName\":\"superCoolFile\","
|
||||
+ "\"Key\":\"superCoolFile\",\"ContainerName\":\"testContainer\",\"Validated\":false}}";
|
||||
|
||||
var cipherInfos = ciphers.Select(c => (c,
|
||||
(DateTime?)c.RevisionDate));
|
||||
var sharingUserId = ciphers.First().UserId.Value;
|
||||
|
||||
await sutProvider.Sut.ShareManyAsync(cipherInfos, organizationId, collectionIds, sharingUserId);
|
||||
await sutProvider.GetDependency<ICipherRepository>().Received(1).UpdateCiphersAsync_vNext(sharingUserId,
|
||||
Arg.Is<IEnumerable<Cipher>>(arg => !arg.Except(ciphers).Any()));
|
||||
}
|
||||
|
||||
private class SaveDetailsAsyncDependencies
|
||||
{
|
||||
public CipherDetails CipherDetails { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user