Harden paid registration flow and add unit tests
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using PowderCoating.Application.Interfaces;
|
||||
using PowderCoating.Core.Entities;
|
||||
using PowderCoating.Infrastructure.Data;
|
||||
using PowderCoating.Infrastructure.Repositories;
|
||||
using PowderCoating.Web.Controllers;
|
||||
using Xunit;
|
||||
|
||||
namespace PowderCoating.UnitTests;
|
||||
|
||||
public class RegistrationControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task PaymentSuccess_WhenStripeSessionIsNotPaid_DoesNotBurnPendingSession()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.PendingRegistrationSessions.Add(CreatePendingSession("token-1", "owner@example.com"));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var stripeService = new Mock<IStripeService>();
|
||||
stripeService.Setup(x => x.IsRegistrationCheckoutPaidAsync("sess_unpaid")).ReturnsAsync(false);
|
||||
|
||||
var controller = CreateController(context, stripeService: stripeService);
|
||||
|
||||
var result = await controller.PaymentSuccess("sess_unpaid", "token-1");
|
||||
|
||||
var redirect = Assert.IsType<Microsoft.AspNetCore.Mvc.RedirectToActionResult>(result);
|
||||
Assert.Equal("Index", redirect.ActionName);
|
||||
Assert.False((await context.PendingRegistrationSessions.SingleAsync()).IsCompleted);
|
||||
Assert.Contains("couldn't verify a completed payment", controller.TempData["Error"]?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PaymentSuccess_WhenUserCreationFails_ReleasesPendingSessionAndDeletesCompany()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.PendingRegistrationSessions.Add(CreatePendingSession("token-2", "owner2@example.com"));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var userManager = CreateUserManagerMock();
|
||||
userManager.Setup(x => x.FindByEmailAsync("owner2@example.com")).ReturnsAsync((ApplicationUser?)null);
|
||||
userManager.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
|
||||
.ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "boom" }));
|
||||
|
||||
var stripeService = new Mock<IStripeService>();
|
||||
stripeService.Setup(x => x.IsRegistrationCheckoutPaidAsync("sess_paid")).ReturnsAsync(true);
|
||||
|
||||
var controller = CreateController(context, userManager, stripeService: stripeService);
|
||||
|
||||
var result = await controller.PaymentSuccess("sess_paid", "token-2");
|
||||
|
||||
var redirect = Assert.IsType<Microsoft.AspNetCore.Mvc.RedirectToActionResult>(result);
|
||||
Assert.Equal("Index", redirect.ActionName);
|
||||
Assert.False((await context.PendingRegistrationSessions.SingleAsync()).IsCompleted);
|
||||
Assert.Empty(context.Companies);
|
||||
Assert.Contains("Please try the success link again", controller.TempData["Error"]?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PaymentSuccess_WhenSessionAlreadyCompletedAndUserExists_SignsUserInAndRedirectsToWelcome()
|
||||
{
|
||||
await using var context = CreateContext();
|
||||
context.PendingRegistrationSessions.Add(CreatePendingSession("token-3", "owner3@example.com", isCompleted: true));
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var existingUser = new ApplicationUser
|
||||
{
|
||||
Id = "user-3",
|
||||
Email = "owner3@example.com",
|
||||
UserName = "owner3@example.com",
|
||||
FirstName = "Terry",
|
||||
LastName = "Tenant",
|
||||
CompanyId = 3
|
||||
};
|
||||
|
||||
var userManager = CreateUserManagerMock();
|
||||
userManager.Setup(x => x.FindByEmailAsync("owner3@example.com")).ReturnsAsync(existingUser);
|
||||
userManager.Setup(x => x.UpdateAsync(existingUser)).ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
var signInManager = CreateSignInManagerMock(userManager.Object);
|
||||
signInManager.Setup(x => x.SignInAsync(existingUser, false, null)).Returns(Task.CompletedTask).Verifiable();
|
||||
|
||||
var controller = CreateController(context, userManager, signInManager.Object);
|
||||
|
||||
var result = await controller.PaymentSuccess("sess_complete", "token-3");
|
||||
|
||||
var redirect = Assert.IsType<Microsoft.AspNetCore.Mvc.RedirectToActionResult>(result);
|
||||
Assert.Equal("Welcome", redirect.ActionName);
|
||||
signInManager.Verify(x => x.SignInAsync(existingUser, false, null), Times.Once);
|
||||
Assert.True((await context.PendingRegistrationSessions.SingleAsync()).IsCompleted);
|
||||
}
|
||||
|
||||
private static RegistrationController CreateController(
|
||||
ApplicationDbContext context,
|
||||
Mock<UserManager<ApplicationUser>>? userManager = null,
|
||||
SignInManager<ApplicationUser>? signInManager = null,
|
||||
Mock<IStripeService>? stripeService = null)
|
||||
{
|
||||
var unitOfWork = new UnitOfWork(context);
|
||||
var userManagerMock = userManager ?? CreateUserManagerMock();
|
||||
var signInManagerInstance = signInManager ?? CreateSignInManagerMock(userManagerMock.Object).Object;
|
||||
|
||||
var platformSettings = new Mock<IPlatformSettingsService>();
|
||||
platformSettings.Setup(x => x.GetAsync(It.IsAny<string>())).ReturnsAsync((string?)null);
|
||||
|
||||
var controller = new RegistrationController(
|
||||
unitOfWork,
|
||||
context,
|
||||
userManagerMock.Object,
|
||||
signInManagerInstance,
|
||||
Mock.Of<ISeedDataService>(),
|
||||
Mock.Of<IAdminNotificationService>(),
|
||||
Mock.Of<IInAppNotificationService>(),
|
||||
platformSettings.Object,
|
||||
(stripeService ?? new Mock<IStripeService>()).Object,
|
||||
Mock.Of<IEmailService>(),
|
||||
Mock.Of<ILogger<RegistrationController>>());
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext
|
||||
{
|
||||
HttpContext = httpContext
|
||||
};
|
||||
controller.TempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static Mock<UserManager<ApplicationUser>> CreateUserManagerMock()
|
||||
{
|
||||
var store = new Mock<IUserStore<ApplicationUser>>();
|
||||
return new Mock<UserManager<ApplicationUser>>(
|
||||
store.Object,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!);
|
||||
}
|
||||
|
||||
private static Mock<SignInManager<ApplicationUser>> CreateSignInManagerMock(UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
var contextAccessor = new Mock<IHttpContextAccessor>();
|
||||
contextAccessor.Setup(x => x.HttpContext).Returns(new DefaultHttpContext());
|
||||
|
||||
var claimsFactory = new Mock<IUserClaimsPrincipalFactory<ApplicationUser>>();
|
||||
|
||||
return new Mock<SignInManager<ApplicationUser>>(
|
||||
userManager,
|
||||
contextAccessor.Object,
|
||||
claimsFactory.Object,
|
||||
null!,
|
||||
null!,
|
||||
null!,
|
||||
null!);
|
||||
}
|
||||
|
||||
private static ApplicationDbContext CreateContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static PendingRegistrationSession CreatePendingSession(string token, string email, bool isCompleted = false)
|
||||
{
|
||||
return new PendingRegistrationSession
|
||||
{
|
||||
Token = token,
|
||||
CompanyName = "Retry Co",
|
||||
CompanyPhone = "555-0100",
|
||||
FirstName = "Pat",
|
||||
LastName = "Owner",
|
||||
Email = email,
|
||||
Plan = 1,
|
||||
IsAnnual = false,
|
||||
IsCompleted = isCompleted,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user