Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using AppBlueprint.Presentation.ApiModule.Controllers.Baseline;
using AppBlueprint.Infrastructure.Services;
using AppBlueprint.Contracts.Baseline.Payment.Requests;
using AppBlueprint.Contracts.Baseline.Payment.Responses;
using AppBlueprint.Infrastructure.DatabaseContexts.Baseline.Entities.Customer;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using TUnit;
using Stripe;

namespace AppBlueprint.Tests.ApiControllers;

public class PaymentControllerTests
{
private readonly Mock<StripeSubscriptionService> _stripeServiceMock;
private readonly Mock<ILogger<PaymentController>> _loggerMock;
private readonly PaymentController _controller;

public PaymentControllerTests()
{
_stripeServiceMock = new Mock<StripeSubscriptionService>();
_loggerMock = new Mock<ILogger<PaymentController>>();
_controller = new PaymentController(_stripeServiceMock.Object, _loggerMock.Object);
}

[Test]
public async Task CreateCustomer_ShouldReturnCreated_WhenValidRequest()
{
// Arrange
var request = new CreateCustomerRequest
{
Email = "test@example.com",
PaymentMethodId = "pm_123456"
};

var customerEntity = new CustomerEntity
{
Id = "cus_123456",
Email = "test@example.com",
Name = "Test Customer",
PhoneNumber = "",
CreatedAt = DateTime.UtcNow
};

_stripeServiceMock.Setup(s => s.CreateCustomerAsync(request.Email, request.PaymentMethodId))
.ReturnsAsync(customerEntity);

// Act
var result = await _controller.CreateCustomer(request, CancellationToken.None);

// Assert
result.Should().BeOfType<CreatedAtActionResult>();
var createdResult = result as CreatedAtActionResult;
createdResult!.Value.Should().BeOfType<CustomerResponse>();

var response = createdResult.Value as CustomerResponse;
response!.Id.Should().Be(customerEntity.Id);
response.Email.Should().Be(customerEntity.Email);
}

[Test]
public async Task CreateCustomer_ShouldReturnBadRequest_WhenServiceReturnsNull()
{
// Arrange
var request = new CreateCustomerRequest
{
Email = "test@example.com",
PaymentMethodId = "pm_123456"
};

_stripeServiceMock.Setup(s => s.CreateCustomerAsync(request.Email, request.PaymentMethodId))
.ReturnsAsync((CustomerEntity?)null);

// Act
var result = await _controller.CreateCustomer(request, CancellationToken.None);

// Assert
result.Should().BeOfType<BadRequestObjectResult>();
var badRequestResult = result as BadRequestObjectResult;
badRequestResult!.Value.Should().BeOfType<ProblemDetails>();
}

[Test]
public async Task CreateCustomer_ShouldReturnBadRequest_WhenArgumentException()
{
// Arrange
var request = new CreateCustomerRequest
{
Email = "",
PaymentMethodId = "pm_123456"
};

_stripeServiceMock.Setup(s => s.CreateCustomerAsync(request.Email, request.PaymentMethodId))
.ThrowsAsync(new ArgumentException("Email cannot be null or empty"));

// Act
var result = await _controller.CreateCustomer(request, CancellationToken.None);

// Assert
result.Should().BeOfType<BadRequestObjectResult>();
var badRequestResult = result as BadRequestObjectResult;
badRequestResult!.Value.Should().BeOfType<ProblemDetails>();
}

[Test]
public async Task CreateSubscription_ShouldReturnCreated_WhenValidRequest()
{
// Arrange
var request = new CreateSubscriptionRequest
{
CustomerId = "cus_123456",
PriceId = "price_123456"
};

var stripeSubscription = CreateMockSubscription("sub_123456", "cus_123456", "price_123456");

_stripeServiceMock.Setup(s => s.CreateSubscriptionAsync(request.CustomerId, request.PriceId))
.ReturnsAsync(stripeSubscription);

// Act
var result = await _controller.CreateSubscription(request, CancellationToken.None);

// Assert
result.Should().BeOfType<CreatedAtActionResult>();
var createdResult = result as CreatedAtActionResult;
createdResult!.Value.Should().BeOfType<SubscriptionResponse>();

var response = createdResult.Value as SubscriptionResponse;
response!.Id.Should().Be(stripeSubscription.Id);
response.CustomerId.Should().Be(stripeSubscription.CustomerId);
}

[Test]
public async Task CancelSubscription_ShouldReturnOk_WhenValidRequest()
{
// Arrange
var request = new CancelSubscriptionRequest
{
SubscriptionId = "sub_123456"
};

var stripeSubscription = CreateMockSubscription("sub_123456", "cus_123456", "price_123456");
stripeSubscription.Status = "canceled";
stripeSubscription.CanceledAt = DateTime.UtcNow;

_stripeServiceMock.Setup(s => s.CancelSubscriptionAsync(request.SubscriptionId))
.ReturnsAsync(stripeSubscription);

// Act
var result = await _controller.CancelSubscription(request, CancellationToken.None);

// Assert
result.Should().BeOfType<OkObjectResult>();
var okResult = result as OkObjectResult;
okResult!.Value.Should().BeOfType<SubscriptionResponse>();

var response = okResult.Value as SubscriptionResponse;
response!.Id.Should().Be(stripeSubscription.Id);
response.Status.Should().Be("canceled");
}

private static Subscription CreateMockSubscription(string id, string customerId, string priceId)
{
var subscription = new Subscription
{
Id = id,
CustomerId = customerId,
Status = "active",
Created = DateTime.UtcNow,
CurrentPeriodStart = DateTime.UtcNow,
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1)
};

// Mock subscription items
var price = new Price
{
Id = priceId,
UnitAmount = 1999,
Currency = "usd"
};

var subscriptionItem = new SubscriptionItem
{
Price = price
};

subscription.Items = new StripeList<SubscriptionItem>
{
Data = new List<SubscriptionItem> { subscriptionItem }
};

return subscription;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using AppBlueprint.Infrastructure.Services;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Moq;
using TUnit;
using Stripe;

namespace AppBlueprint.Tests.Infrastructure;

public class StripeSubscriptionServiceTests
{
private readonly Mock<IConfiguration> _configurationMock;
private readonly StripeSubscriptionService _stripeSubscriptionService;
private const string TestApiKey = "sk_test_123456789";

public StripeSubscriptionServiceTests()
{
_configurationMock = new Mock<IConfiguration>();
_configurationMock.Setup(c => c.GetConnectionString("StripeApiKey"))
.Returns(TestApiKey);

_stripeSubscriptionService = new StripeSubscriptionService(_configurationMock.Object);
}

[Test]
public void Constructor_ShouldThrowException_WhenApiKeyIsNull()
{
// Arrange
var configMock = new Mock<IConfiguration>();
configMock.Setup(c => c.GetConnectionString("StripeApiKey"))
.Returns((string?)null);

// Act & Assert
var act = () => new StripeSubscriptionService(configMock.Object);
act.Should().Throw<InvalidOperationException>()
.WithMessage("StripeApiKey connection string is not configured.");
}

[Test]
public async Task CreateCustomerAsync_ShouldThrowArgumentException_WhenEmailIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.CreateCustomerAsync("", "pm_123");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Email cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.CreateCustomerAsync(null!, "pm_123");
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Email cannot be null or empty*");
}

[Test]
public async Task CreateCustomerAsync_ShouldThrowArgumentException_WhenPaymentMethodIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.CreateCustomerAsync("test@test.com", "");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Payment method ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.CreateCustomerAsync("test@test.com", null!);
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Payment method ID cannot be null or empty*");
}

[Test]
public async Task CreateSubscriptionAsync_ShouldThrowArgumentException_WhenCustomerIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.CreateSubscriptionAsync("", "price_123");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Customer ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.CreateSubscriptionAsync(null!, "price_123");
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Customer ID cannot be null or empty*");
}

[Test]
public async Task CreateSubscriptionAsync_ShouldThrowArgumentException_WhenPriceIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.CreateSubscriptionAsync("cus_123", "");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Price ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.CreateSubscriptionAsync("cus_123", null!);
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Price ID cannot be null or empty*");
}

[Test]
public async Task GetSubscriptionAsync_ShouldThrowArgumentException_WhenSubscriptionIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.GetSubscriptionAsync("");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Subscription ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.GetSubscriptionAsync(null!);
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Subscription ID cannot be null or empty*");
}

[Test]
public async Task CancelSubscriptionAsync_ShouldThrowArgumentException_WhenSubscriptionIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.CancelSubscriptionAsync("");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Subscription ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.CancelSubscriptionAsync(null!);
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Subscription ID cannot be null or empty*");
}

[Test]
public async Task GetCustomerSubscriptionsAsync_ShouldThrowArgumentException_WhenCustomerIdIsNullOrEmpty()
{
// Act & Assert
var act = async () => await _stripeSubscriptionService.GetCustomerSubscriptionsAsync("");
await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("Customer ID cannot be null or empty*");

var act2 = async () => await _stripeSubscriptionService.GetCustomerSubscriptionsAsync(null!);
await act2.Should().ThrowAsync<ArgumentException>()
.WithMessage("Customer ID cannot be null or empty*");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AppBlueprint.Contracts.Baseline.Payment.Requests;

public class CreateCustomerRequest
{
public required string Email { get; set; }
public required string PaymentMethodId { get; set; }
public string? Name { get; set; }
public string? Phone { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace AppBlueprint.Contracts.Baseline.Payment.Requests;

public class CreateSubscriptionRequest
{
public required string CustomerId { get; set; }
public required string PriceId { get; set; }
}

public class CancelSubscriptionRequest
{
public required string SubscriptionId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace AppBlueprint.Contracts.Baseline.Payment.Responses;

public class CustomerResponse
{
public required string Id { get; set; }
public required string Email { get; set; }
public string? Name { get; set; }
public string? Phone { get; set; }
public DateTime CreatedAt { get; set; }
}

public class SubscriptionResponse
{
public required string Id { get; set; }
public required string CustomerId { get; set; }
public required string Status { get; set; }
public required string PriceId { get; set; }
public long Amount { get; set; }
public required string Currency { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? CanceledAt { get; set; }
public DateTime? CurrentPeriodStart { get; set; }
public DateTime? CurrentPeriodEnd { get; set; }
}

public class SubscriptionListResponse
{
public required List<SubscriptionResponse> Subscriptions { get; set; }
public bool HasMore { get; set; }
public int TotalCount { get; set; }
}
Loading