Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced subscription tests #12

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Tests/ErrorSuppressionWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AdvancedBilling.Standard.Exceptions;

namespace AdvancedBillingTests
{
public static class ErrorSuppressionWrapper
{
public static async Task RunAsync(Func<Task> action)
{
try
{
await action.Invoke();
}
catch (ApiException e)
{
// Suppress Errors on Cleanup
}
}
}
}
19 changes: 19 additions & 0 deletions Tests/InvalidClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AdvancedBilling.Standard;
using Environment = AdvancedBilling.Standard.Environment;

namespace AdvancedBillingTests
{
internal static class InvalidClient
{
public static AdvancedBillingClient GetInvalidClient()
{
var builder = new AdvancedBillingClient.Builder();
builder.Environment(Environment.Production);
builder.Domain("staging-chargify.com");
builder.Subdomain("dotnet-sdk");
builder.BasicAuthCredentials("abc", "123");

return builder.Build();
}
}
}
9 changes: 1 addition & 8 deletions Tests/SiteTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using AdvancedBilling.Standard;
using AdvancedBilling.Standard.Exceptions;
using FluentAssertions;
using Environment = AdvancedBilling.Standard.Environment;

namespace AdvancedBillingTests
{
Expand All @@ -26,12 +24,7 @@ public async Task ValidSiteReading_SuccessfulRequest_ReturnsSiteObject()
[Fact]
public async Task UnauthorizedSiteReading_InvalidCredentials_Returns401Unauthorized()
{
var builder = new AdvancedBillingClient.Builder();
builder.Environment(Environment.Production);
builder.Domain("staging-chargify.com");
builder.Subdomain("dotnet-sdk");
builder.BasicAuthCredentials("abc", "123");
var invalidClient = builder.Build();
var invalidClient = InvalidClient.GetInvalidClient();

await invalidClient.Invoking(i => i.SitesController.ReadSiteAsync()).Should().ThrowAsync<ApiException>().Where(e => e.ResponseCode == 401);
}
Expand Down
251 changes: 235 additions & 16 deletions Tests/SubscriptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,214 @@ await _client.SubscriptionsController.CreateSubscriptionAsync(
new CreateSubscriptionRequest(subscription));
subscriptionResponse.Subscription.Id.Should().NotBeNull();

try
await ExecuteBasicSubscriptionCleanup(subscriptionResponse, customerResponse, paymentProfile, productResponse);
}

[Fact]
public async Task
CreateSubscription_WrongCouponCodeProvidedToSubscription_ShouldHaveUnprocessableStatusWithMeaningfulError()
{
var productFamilyId = await CreateOrGetProductFamily();

var product = await CreateProduct(productFamilyId);

var randomString = GenerateRandomString(8);

var meteredComponent = new MeteredComponent($"ApiCalls{randomString}", $"api call {randomString}",
PricingScheme.PerUnit, unitPrice: MeteredComponentUnitPrice.FromString("1"));

var componentResponse = await _client.ComponentsController.CreateComponentAsync((int)productFamilyId,
ComponentKindPath.MeteredComponents,
CreateComponentBody.FromCreateMeteredComponent(new CreateMeteredComponent(meteredComponent)));

componentResponse.Component.Id.Should().NotBeNull();

var couponCode = $"100{randomString}OFF";

var createOrUpdatePercentageCoupon = new CreateOrUpdatePercentageCoupon("100% off first month of usage",
couponCode, CreateOrUpdatePercentageCouponPercentage.FromPrecision(100), "100% off one-time", "false",
"false", "2024-08-29T12:00:00-04:00", productFamilyId.ToString(), "false",
excludeMidPeriodAllocations: true, applyOnCancelAtEndOfPeriod: true);

var restrictedProductDictionary = new Dictionary<string, bool> { { product.Product.Id.ToString()!, true } };
var restrictedComponentsDictionary = new Dictionary<string, bool>
{
await _client.SubscriptionsController.PurgeSubscriptionAsync((int)subscriptionResponse.Subscription.Id,
(int)customerResponse.Customer.Id);
{ componentResponse.Component.Id.ToString()!, true }
};

await _client.PaymentProfilesController.DeleteUnusedPaymentProfileAsync(paymentProfile.ToString());
var couponResponse = await _client.CouponsController.CreateCouponAsync((int)productFamilyId,
new CreateOrUpdateCoupon(
CreateOrUpdateCouponCoupon.FromCreateOrUpdatePercentageCoupon(createOrUpdatePercentageCoupon),
restrictedProductDictionary, restrictedComponentsDictionary));
couponResponse.Coupon.Id.Should().NotBeNull();

await _client.CustomersController.DeleteCustomerAsync((int)customerResponse.Customer.Id);
await _client.ProductsController.ArchiveProductAsync((int)productResponse.Product.Id);
}
catch (ApiException e)
var customer = await CreateCustomer();

var paymentProfile = await CreatePaymentProfile(customer.Customer.Id);

var initialBillingDate = DateTime.Now.AddDays(20);

var wrongCouponCode = $"WrongCode{randomString}";

var createdSubscription = new CreateSubscription
{
// Suppress Errors on Cleanup
}
CustomerId = customer.Customer.Id,
ProductId = product.Product.Id,
PaymentCollectionMethod = PaymentCollectionMethod.Automatic,
PaymentProfileId = paymentProfile.PaymentProfile.Id,
DunningCommunicationDelayEnabled = false,
SkipBillingManifestTaxes = false,
Components = new List<CreateSubscriptionComponent>()
{
new CreateSubscriptionComponent(
CreateSubscriptionComponentComponentId.FromNumber((int)componentResponse.Component.Id),
quantity: 10)
},
CouponCode = wrongCouponCode,
InitialBillingAt = initialBillingDate.ToString("yyyy-MM-dd")
};

await _client.Invoking(c => c.SubscriptionsController.CreateSubscriptionAsync(
new CreateSubscriptionRequest(createdSubscription))).Should()
.ThrowAsync<ErrorListResponseException>()
.Where(e => e.ResponseCode == 422 && e.Errors.Any(a => a.Contains("Coupon code could not be found")));

await ExecuteCleanupForPaymentProfileProductCustomer(customer, paymentProfile, product);

await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.ComponentsController.ArchiveComponentAsync((int)productFamilyId,
componentResponse.Component.Id.ToString());
});

await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.CouponsController.ArchiveCouponAsync((int)productFamilyId, (int)couponResponse.Coupon.Id);
});
}

[Fact]
public async Task
CreateSubscription_UnauthorizedAccessCreateSubscription_ShouldHaveReturns401Unauthorized()
{
var invalidClient = InvalidClient.GetInvalidClient();

var createdSubscription = new CreateSubscription
{
CustomerId = _fixture.Create<int>(),
ProductId = _fixture.Create<int>(),
PaymentCollectionMethod = PaymentCollectionMethod.Automatic,
PaymentProfileId = _fixture.Create<int>(),
DunningCommunicationDelayEnabled = false,
SkipBillingManifestTaxes = false,
Components = new List<CreateSubscriptionComponent>()
{
new CreateSubscriptionComponent(
CreateSubscriptionComponentComponentId.FromNumber(_fixture.Create<int>()),
quantity: 10)
},
CouponCode = _fixture.Create<string>(),
InitialBillingAt = _fixture.Create<DateTime>().ToString("yyyy-MM-dd")
};

await invalidClient.Invoking(c => c.SubscriptionsController.CreateSubscriptionAsync(
new CreateSubscriptionRequest(createdSubscription))).Should()
.ThrowAsync<ApiException>()
.Where(e => e.ResponseCode == 401);
}

[Fact]
public async Task CreateSubscription_RestrictedCouponMeteredComponentData_ShouldHaveAwaitingSignupStatus()
{
var productFamilyId = await CreateOrGetProductFamily();

var product = await CreateProduct(productFamilyId);

var randomString = GenerateRandomString(4);

var quantityComponent = new QuantityBasedComponent($"widget{randomString}", $"widget {randomString}",
PricingScheme.PerUnit, unitPrice: QuantityBasedComponentUnitPrice.FromPrecision(1));

var restrictedComponentResponse = await _client.ComponentsController.CreateComponentAsync(
(int)productFamilyId,
ComponentKindPath.QuantityBasedComponents,
CreateComponentBody.FromCreateQuantityBasedComponent(
new CreateQuantityBasedComponent(quantityComponent)));

restrictedComponentResponse.Component.Id.Should().NotBeNull();

var meteredComponent = new MeteredComponent($"ApiCalls{randomString}", $"api call {randomString}",
PricingScheme.PerUnit, unitPrice: MeteredComponentUnitPrice.FromString("1"));

var componentResponse = await _client.ComponentsController.CreateComponentAsync((int)productFamilyId,
ComponentKindPath.MeteredComponents,
CreateComponentBody.FromCreateMeteredComponent(new CreateMeteredComponent(meteredComponent)));

componentResponse.Component.Id.Should().NotBeNull();

var couponCode = $"100{randomString}OFF";

var createOrUpdatePercentageCoupon = new CreateOrUpdatePercentageCoupon("100% off first month of usage",
couponCode, CreateOrUpdatePercentageCouponPercentage.FromPrecision(100), "100% off one-time", "false",
"false", "2024-08-29T12:00:00-04:00", productFamilyId.ToString(), "false",
excludeMidPeriodAllocations: true, applyOnCancelAtEndOfPeriod: true);

var restrictedProductDictionary = new Dictionary<string, bool> { { product.Product.Id.ToString()!, true } };
var restrictedComponentsDictionary = new Dictionary<string, bool>
{
{ restrictedComponentResponse.Component.Id.ToString()!, false },
{ componentResponse.Component.Id.ToString()!, true }
};

var couponResponse = await _client.CouponsController.CreateCouponAsync((int)productFamilyId,
new CreateOrUpdateCoupon(
CreateOrUpdateCouponCoupon.FromCreateOrUpdatePercentageCoupon(createOrUpdatePercentageCoupon),
restrictedProductDictionary, restrictedComponentsDictionary));
couponResponse.Coupon.Id.Should().NotBeNull();

var customer = await CreateCustomer();

var paymentProfile = await CreatePaymentProfile(customer.Customer.Id);

var initialBillingDate = DateTime.Now.AddDays(20);

var createdSubscription = new CreateSubscription
{
CustomerId = customer.Customer.Id,
ProductId = product.Product.Id,
PaymentCollectionMethod = PaymentCollectionMethod.Automatic,
PaymentProfileId = paymentProfile.PaymentProfile.Id,
DunningCommunicationDelayEnabled = false,
SkipBillingManifestTaxes = false,
Components = new List<CreateSubscriptionComponent>()
{
new CreateSubscriptionComponent(
CreateSubscriptionComponentComponentId.FromNumber((int)componentResponse.Component.Id),
quantity: 10)
},
CouponCode = couponCode,
InitialBillingAt = initialBillingDate.ToString("yyyy-MM-dd")
};
var subscriptionResponse =
await _client.SubscriptionsController.CreateSubscriptionAsync(
new CreateSubscriptionRequest(createdSubscription));

subscriptionResponse.Subscription.Id.Should().NotBeNull();
subscriptionResponse.Subscription.State.Should().Be(SubscriptionState.AwaitingSignup);

await ExecuteBasicSubscriptionCleanup(subscriptionResponse, customer, paymentProfile, product);

await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.ComponentsController.ArchiveComponentAsync((int)productFamilyId,
componentResponse.Component.Id.ToString());
});

await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.ComponentsController.ArchiveComponentAsync((int)productFamilyId,
restrictedComponentResponse.Component.Id.ToString());
});
}

[Fact]
Expand Down Expand Up @@ -109,20 +303,38 @@ await _client.SubscriptionsController.CreateSubscriptionAsync(
subscription.Subscription.Customer.Id.Should().Be(customerResponse.Customer.Id);
subscription.Subscription.PaymentCollectionMethod.Should().Be(PaymentCollectionMethod.Automatic);

try
await ExecuteBasicSubscriptionCleanup(subscriptionResponse, customerResponse, paymentProfile, productResponse);
}

private async Task ExecuteBasicSubscriptionCleanup(SubscriptionResponse subscriptionResponse,
CustomerResponse customerResponse, CreatePaymentProfileResponse paymentProfile, ProductResponse productResponse)
{
await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.SubscriptionsController.PurgeSubscriptionAsync((int)subscriptionResponse.Subscription.Id,
(int)customerResponse.Customer.Id);
});

await ExecuteCleanupForPaymentProfileProductCustomer(customerResponse, paymentProfile, productResponse);
}

private async Task ExecuteCleanupForPaymentProfileProductCustomer(CustomerResponse customerResponse,
CreatePaymentProfileResponse paymentProfile, ProductResponse productResponse)
{
await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.PaymentProfilesController.DeleteUnusedPaymentProfileAsync(paymentProfile.ToString());
});

await ErrorSuppressionWrapper.RunAsync(async () =>
{
await _client.CustomersController.DeleteCustomerAsync((int)customerResponse.Customer.Id);
await _client.ProductsController.ArchiveProductAsync((int)productResponse.Product.Id);
}
catch (ApiException e)
});

await ErrorSuppressionWrapper.RunAsync(async () =>
{
// Suppress Errors on Cleanup
}
await _client.ProductsController.ArchiveProductAsync((int)productResponse.Product.Id);
});
}

private async Task<CreatePaymentProfileResponse> CreatePaymentProfile(int? customerId)
Expand Down Expand Up @@ -203,5 +415,12 @@ await _client.ProductsController.CreateProductAsync((int)productFamilyId,
return productFamilyId;
}

static string GenerateRandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
}
}
Loading