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(); 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(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(), It.IsAny())) .ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "boom" })); var stripeService = new Mock(); 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(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(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 = null, SignInManager? signInManager = null, Mock? stripeService = null) { var unitOfWork = new UnitOfWork(context); var userManagerMock = userManager ?? CreateUserManagerMock(); var signInManagerInstance = signInManager ?? CreateSignInManagerMock(userManagerMock.Object).Object; var platformSettings = new Mock(); platformSettings.Setup(x => x.GetAsync(It.IsAny())).ReturnsAsync((string?)null); var controller = new RegistrationController( unitOfWork, context, userManagerMock.Object, signInManagerInstance, Mock.Of(), Mock.Of(), Mock.Of(), platformSettings.Object, (stripeService ?? new Mock()).Object, Mock.Of(), Mock.Of>()); var httpContext = new DefaultHttpContext(); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = httpContext }; controller.TempData = new TempDataDictionary(httpContext, Mock.Of()); return controller; } private static Mock> CreateUserManagerMock() { var store = new Mock>(); return new Mock>( store.Object, null!, null!, null!, null!, null!, null!, null!, null!); } private static Mock> CreateSignInManagerMock(UserManager userManager) { var contextAccessor = new Mock(); contextAccessor.Setup(x => x.HttpContext).Returns(new DefaultHttpContext()); var claimsFactory = new Mock>(); return new Mock>( userManager, contextAccessor.Object, claimsFactory.Object, null!, null!, null!, null!); } private static ApplicationDbContext CreateContext() { var options = new DbContextOptionsBuilder() .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 }; } }