using System.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Moq; using FluentAssertions; using RacePlannerApi.Controllers; using RacePlannerApi.Data; using RacePlannerApi.DTOs; using RacePlannerApi.Models; using backend.Tests.Utilities; namespace backend.Tests.Controllers; public class PaymentsControllerTests : IDisposable { private readonly RacePlannerDbContext _context; private readonly PaymentsController _controller; public PaymentsControllerTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _context = new RacePlannerDbContext(options); _controller = new PaymentsController(_context); } private void SetUserContext(Guid userId, string role = "Participant") { var claims = new List { new Claim(ClaimTypes.NameIdentifier, userId.ToString()), new Claim(ClaimTypes.Role, role) }; var identity = new ClaimsIdentity(claims, "TestAuthType"); var principal = new ClaimsPrincipal(identity); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = principal } }; } public void Dispose() { _context.Dispose(); } #region Record Payment - Positive Tests [Fact] public async Task RecordPayment_WithValidData_RecordsPayment() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreatePaymentRequest { RegistrationId = registration.Id, Amount = 50.00m, Method = PaymentMethod.Cash, Notes = "Cash payment received" }; // Act var result = await _controller.RecordPayment(request); // Assert var createdResult = result.Result.Should().BeOfType().Subject; var response = createdResult.Value.Should().BeOfType().Subject; response.Amount.Should().Be(50.00m); response.Method.Should().Be("Cash"); } [Fact] public async Task RecordPayment_OnlinePayment_WithTransactionId() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant2@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreatePaymentRequest { RegistrationId = registration.Id, Amount = 75.00m, Method = PaymentMethod.Online, TransactionId = "txn_12345", Notes = "Online payment" }; // Act var result = await _controller.RecordPayment(request); // Assert var createdResult = result.Result.Should().BeOfType().Subject; var response = createdResult.Value.Should().BeOfType().Subject; response.Method.Should().Be("Online"); response.TransactionId.Should().Be("txn_12345"); } #endregion #region Record Payment - Negative Tests [Fact] public async Task RecordPayment_ForCancelledRegistration_ReturnsBadRequest() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant3@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Cancelled); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreatePaymentRequest { RegistrationId = registration.Id, Amount = 50.00m, Method = PaymentMethod.Cash }; // Act var result = await _controller.RecordPayment(request); // Assert var badRequestResult = result.Result.Should().BeOfType().Subject; badRequestResult.Value.Should().BeEquivalentTo(new { error = "Cannot record payment for cancelled registration" }); } [Fact] public async Task RecordPayment_ForNonExistentRegistration_ReturnsNotFound() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreatePaymentRequest { RegistrationId = Guid.NewGuid(), Amount = 50.00m, Method = PaymentMethod.Cash }; // Act var result = await _controller.RecordPayment(request); // Assert var notFoundResult = result.Result.Should().BeOfType().Subject; notFoundResult.Value.Should().BeEquivalentTo(new { error = "Registration not found" }); } [Fact] public async Task RecordPayment_NonOrganizer_ReturnsForbidden() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant4@example.com", role: UserRole.Participant); var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer); _context.Users.AddRange(organizer, participant, otherOrganizer); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); SetUserContext(otherOrganizer.Id, "Organizer"); var request = new CreatePaymentRequest { RegistrationId = registration.Id, Amount = 50.00m, Method = PaymentMethod.Cash }; // Act var result = await _controller.RecordPayment(request); // Assert result.Result.Should().BeOfType(); } #endregion #region Get Payment Status - Positive Tests [Fact] public async Task GetPaymentStatus_UnpaidRegistration_ReturnsUnpaid() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant5@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); SetUserContext(participant.Id, "Participant"); // Act var result = await _controller.GetPaymentStatus(registration.Id); // Assert var okResult = result.Result.Should().BeOfType().Subject; var response = okResult.Value; response.Should().NotBeNull(); } [Fact] public async Task GetPaymentStatus_PaidRegistration_ReturnsPaid() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant = TestDataFactory.CreateUser(email: "participant6@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id); _context.Registrations.Add(registration); await _context.SaveChangesAsync(); // Record a payment var payment = TestDataFactory.CreatePayment(registration.Id, 50.00m, PaymentMethod.Cash); _context.Payments.Add(payment); await _context.SaveChangesAsync(); SetUserContext(participant.Id, "Participant"); // Act var result = await _controller.GetPaymentStatus(registration.Id); // Assert var okResult = result.Result.Should().BeOfType().Subject; var response = okResult.Value; response.Should().NotBeNull(); } #endregion #region Get Payment Status - Negative Tests [Fact] public async Task GetPaymentStatus_NonExistentRegistration_ReturnsNotFound() { // Arrange var participant = TestDataFactory.CreateUser(role: UserRole.Participant); _context.Users.Add(participant); await _context.SaveChangesAsync(); SetUserContext(participant.Id, "Participant"); // Act var result = await _controller.GetPaymentStatus(Guid.NewGuid()); // Assert result.Result.Should().BeOfType(); } #endregion #region Get Payment Report - Positive Tests [Fact] public async Task GetPaymentReport_ReturnsEventPaymentSummary() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var participant1 = TestDataFactory.CreateUser(email: "p1@example.com", role: UserRole.Participant); var participant2 = TestDataFactory.CreateUser(email: "p2@example.com", role: UserRole.Participant); _context.Users.AddRange(organizer, participant1, participant2); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); var registration1 = TestDataFactory.CreateRegistration(eventEntity.Id, participant1.Id, RegistrationStatus.Confirmed); var registration2 = TestDataFactory.CreateRegistration(eventEntity.Id, participant2.Id, RegistrationStatus.Confirmed); _context.Registrations.AddRange(registration1, registration2); await _context.SaveChangesAsync(); // Add payment for first registration var payment = TestDataFactory.CreatePayment(registration1.Id, 50.00m, PaymentMethod.Cash); _context.Payments.Add(payment); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); // Act var result = await _controller.GetPaymentReport(eventEntity.Id); // Assert var okResult = result.Result.Should().BeOfType().Subject; var response = okResult.Value.Should().BeOfType().Subject; response.TotalCollected.Should().Be(50.00m); response.TotalRegistrations.Should().Be(2); response.PaidRegistrations.Should().Be(1); response.UnpaidRegistrations.Should().Be(1); } #endregion #region Get Payment Report - Negative Tests [Fact] public async Task GetPaymentReport_NonOrganizer_ReturnsForbidden() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer); _context.Users.AddRange(organizer, otherOrganizer); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); SetUserContext(otherOrganizer.Id, "Organizer"); // Act var result = await _controller.GetPaymentReport(eventEntity.Id); // Assert result.Result.Should().BeOfType(); } [Fact] public async Task GetPaymentReport_NonExistentEvent_ReturnsNotFound() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); // Act var result = await _controller.GetPaymentReport(Guid.NewGuid()); // Assert result.Result.Should().BeOfType(); } #endregion }