1
0
mirror of https://github.com/bitwarden/server synced 2026-02-18 18:33:29 +00:00

[PM-30108] import discount from stripe (#6982)

* [PM-30108] import discount from stripe

* fix repo tests

* pr feedback

* wrap discounts in feature flag

* claude pr feedback
This commit is contained in:
Kyle Denney
2026-02-17 12:57:14 -06:00
committed by GitHub
parent 3753a5e853
commit f0c69cedc2
24 changed files with 1521 additions and 1 deletions

View File

@@ -79,6 +79,11 @@ public class SubscriptionDiscountRepositoryTests
Assert.Contains(activeDiscounts, d => d.Id == activeDiscount.Id);
Assert.DoesNotContain(activeDiscounts, d => d.Id == expiredDiscount.Id);
Assert.DoesNotContain(activeDiscounts, d => d.Id == futureDiscount.Id);
// Cleanup
await subscriptionDiscountRepository.DeleteAsync(activeDiscount);
await subscriptionDiscountRepository.DeleteAsync(expiredDiscount);
await subscriptionDiscountRepository.DeleteAsync(futureDiscount);
}
[Theory, DatabaseData]
@@ -106,6 +111,9 @@ public class SubscriptionDiscountRepositoryTests
Assert.Equal(couponId, result.StripeCouponId);
Assert.Equal(20.00m, result.PercentOff);
Assert.Equal(3, result.DurationInMonths);
// Cleanup
await subscriptionDiscountRepository.DeleteAsync(discount);
}
[Theory, DatabaseData]
@@ -140,6 +148,9 @@ public class SubscriptionDiscountRepositoryTests
Assert.Equal(discount.StripeCouponId, createdDiscount.StripeCouponId);
Assert.Equal(500, createdDiscount.AmountOff);
Assert.Equal("usd", createdDiscount.Currency);
// Cleanup
await subscriptionDiscountRepository.DeleteAsync(createdDiscount);
}
[Theory, DatabaseData]
@@ -164,6 +175,9 @@ public class SubscriptionDiscountRepositoryTests
Assert.NotNull(updatedDiscount);
Assert.Equal("Updated Name", updatedDiscount.Name);
Assert.Equal(15.00m, updatedDiscount.PercentOff);
// Cleanup
await subscriptionDiscountRepository.DeleteAsync(updatedDiscount);
}
[Theory, DatabaseData]
@@ -184,4 +198,123 @@ public class SubscriptionDiscountRepositoryTests
var deletedDiscount = await subscriptionDiscountRepository.GetByIdAsync(discount.Id);
Assert.Null(deletedDiscount);
}
[Theory, DatabaseData]
public async Task ListAsync_ReturnsPagedResults_OrderedByCreationDateDescending(
ISubscriptionDiscountRepository subscriptionDiscountRepository)
{
// Arrange - create discounts with future timestamps (should be at top)
var farFuture = new DateTime(2500, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var discount1 = await subscriptionDiscountRepository.CreateAsync(
CreateTestDiscount(
stripeCouponId: $"test-search-1-{Guid.NewGuid()}",
percentOff: 10.00m,
name: "First Discount",
creationDate: farFuture.AddSeconds(-3)));
var discount2 = await subscriptionDiscountRepository.CreateAsync(
CreateTestDiscount(
stripeCouponId: $"test-search-2-{Guid.NewGuid()}",
percentOff: 20.00m,
name: "Second Discount",
creationDate: farFuture.AddSeconds(-2)));
var discount3 = await subscriptionDiscountRepository.CreateAsync(
CreateTestDiscount(
stripeCouponId: $"test-search-3-{Guid.NewGuid()}",
percentOff: 30.00m,
name: "Third Discount",
creationDate: farFuture.AddSeconds(-1)));
// Act - get first page
var result = await subscriptionDiscountRepository.ListAsync(0, 100);
// Assert
Assert.NotEmpty(result);
var resultList = result.ToList();
// Our discounts should be the first 3 in the result
Assert.Equal(discount3.Id, resultList[0].Id);
Assert.Equal(discount2.Id, resultList[1].Id);
Assert.Equal(discount1.Id, resultList[2].Id);
// Cleanup
await subscriptionDiscountRepository.DeleteAsync(discount1);
await subscriptionDiscountRepository.DeleteAsync(discount2);
await subscriptionDiscountRepository.DeleteAsync(discount3);
}
[Theory, DatabaseData]
public async Task ListAsync_WithSkip_ReturnsCorrectPage(
ISubscriptionDiscountRepository subscriptionDiscountRepository)
{
// Arrange - create several discounts with future timestamps (should be at top)
var farFuture = new DateTime(2500, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var discounts = new List<SubscriptionDiscount>();
for (int i = 0; i < 5; i++)
{
var discount = await subscriptionDiscountRepository.CreateAsync(
CreateTestDiscount(
stripeCouponId: $"test-skip-{i}-{Guid.NewGuid()}",
percentOff: 10.00m + i,
name: $"Discount {i}",
creationDate: farFuture.AddSeconds(-i)));
discounts.Add(discount);
}
// Act - get first page to find where our discounts are
var allResults = await subscriptionDiscountRepository.ListAsync(0, 100);
var allResultsList = allResults.ToList();
// Find the indices of our created discounts
var indices = discounts.Select(d => allResultsList.FindIndex(r => r.Id == d.Id)).Where(i => i >= 0).OrderBy(i => i).ToList();
// Act - skip the first 2 of OUR discounts, take 2
var result = await subscriptionDiscountRepository.ListAsync(indices[2], 2);
// Assert
var resultList = result.ToList();
Assert.True(resultList.Count == 2);
// Verify we got discounts[2] and discounts[3]
Assert.Contains(resultList, d => d.Id == discounts[2].Id);
Assert.Contains(resultList, d => d.Id == discounts[3].Id);
// Cleanup
foreach (var discount in discounts)
{
await subscriptionDiscountRepository.DeleteAsync(discount);
}
}
[Theory, DatabaseData]
public async Task ListAsync_WithTake_LimitsResults(
ISubscriptionDiscountRepository subscriptionDiscountRepository)
{
// Arrange - create 5 discounts
var discounts = new List<SubscriptionDiscount>();
for (int i = 0; i < 5; i++)
{
var discount = await subscriptionDiscountRepository.CreateAsync(
CreateTestDiscount(
stripeCouponId: $"test-limit-{i}-{Guid.NewGuid()}",
percentOff: 10.00m,
name: $"Discount {i}",
creationDate: DateTime.UtcNow.AddMinutes(-i)));
discounts.Add(discount);
}
// Act - get only 3 results
var result = await subscriptionDiscountRepository.ListAsync(0, 3);
// Assert
var resultList = result.ToList();
Assert.True(resultList.Count == 3);
// Cleanup
foreach (var discount in discounts)
{
await subscriptionDiscountRepository.DeleteAsync(discount);
}
}
}