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 EventsControllerTests : IDisposable { private readonly RacePlannerDbContext _context; private readonly EventsController _controller; public EventsControllerTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _context = new RacePlannerDbContext(options); _controller = new EventsController(_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 Create Event - Positive Tests [Fact] public async Task CreateEvent_WithValidData_CreatesEvent() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreateEventRequest { Name = "Test Marathon", Description = "A test marathon event", EventDate = DateTime.UtcNow.AddDays(30), Location = "Test City", Category = "Running", Tags = new List { "marathon", "running" }, MaxParticipants = 100 }; // Act var result = await _controller.CreateEvent(request); // Assert var createdResult = result.Result.Should().BeOfType().Subject; var response = createdResult.Value.Should().BeOfType().Subject; response.Name.Should().Be(request.Name); response.Status.Should().Be("Draft"); response.Organizer.Id.Should().Be(organizer.Id); } [Fact] public async Task CreateEvent_SetsStatusToDraft() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new CreateEventRequest { Name = "Test Event", EventDate = DateTime.UtcNow.AddDays(10), Location = "Test Location", MaxParticipants = 50 }; // Act var result = await _controller.CreateEvent(request); // Assert var createdResult = result.Result.Should().BeOfType().Subject; var response = createdResult.Value.Should().BeOfType().Subject; response.Status.Should().Be("Draft"); } #endregion #region Create Event - Negative Tests [Fact(Skip = "Authorization attributes require integration tests with full ASP.NET Core pipeline")] public async Task CreateEvent_WithoutOrganizerRole_ReturnsForbidden() { // Arrange var participant = TestDataFactory.CreateUser(role: UserRole.Participant); _context.Users.Add(participant); await _context.SaveChangesAsync(); SetUserContext(participant.Id, "Participant"); var request = new CreateEventRequest { Name = "Test Event", EventDate = DateTime.UtcNow.AddDays(10), Location = "Test Location" }; // Act var result = await _controller.CreateEvent(request); // Assert result.Result.Should().BeOfType(); } [Fact] public async Task CreateEvent_WithUnauthenticatedUser_ReturnsUnauthorized() { // Arrange _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; var request = new CreateEventRequest { Name = "Test Event", EventDate = DateTime.UtcNow.AddDays(10), Location = "Test Location" }; // Act var result = await _controller.CreateEvent(request); // Assert result.Result.Should().BeOfType(); } #endregion #region Get Events - Positive Tests [Fact] public async Task GetEvents_ReturnsPublishedEventsForAnonymous() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var publishedEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft); _context.Events.AddRange(publishedEvent, draftEvent); await _context.SaveChangesAsync(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; // Act var result = await _controller.GetEvents(); // Assert var okResult = result.Result.Should().BeOfType().Subject; var events = okResult.Value.Should().BeAssignableTo>().Subject; events.Should().HaveCount(1); events.First().Status.Should().Be("Published"); } [Fact] public async Task GetEvents_WithCategoryFilter_ReturnsFilteredEvents() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var runningEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); runningEvent.Category = "Running"; var cyclingEvent = TestDataFactory.CreateEvent(name: "Cycling Event", organizerId: organizer.Id, status: EventStatus.Published); cyclingEvent.Category = "Cycling"; _context.Events.AddRange(runningEvent, cyclingEvent); await _context.SaveChangesAsync(); // Set anonymous context _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; var filter = new EventFilterRequest { Category = "Running" }; // Act var result = await _controller.GetEvents(filter); // Assert var okResult = result.Result.Should().BeOfType().Subject; var events = okResult.Value.Should().BeAssignableTo>().Subject; events.Should().HaveCount(1); events.First().Category.Should().Be("Running"); } [Fact] public async Task GetEvents_WithDateRangeFilter_ReturnsFilteredEvents() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var futureEvent = TestDataFactory.CreateEvent( name: "Future Event", eventDate: DateTime.UtcNow.AddDays(60), organizerId: organizer.Id, status: EventStatus.Published); var pastEvent = TestDataFactory.CreateEvent( name: "Past Event", eventDate: DateTime.UtcNow.AddDays(-10), organizerId: organizer.Id, status: EventStatus.Published); _context.Events.AddRange(futureEvent, pastEvent); await _context.SaveChangesAsync(); // Set anonymous context _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; var filter = new EventFilterRequest { FromDate = DateTime.UtcNow.AddDays(1), ToDate = DateTime.UtcNow.AddDays(100) }; // Act var result = await _controller.GetEvents(filter); // Assert var okResult = result.Result.Should().BeOfType().Subject; var events = okResult.Value.Should().BeAssignableTo>().Subject; events.Should().HaveCount(1); events.First().Name.Should().Be("Future Event"); } #endregion #region Get Single Event - Positive Tests [Fact] public async Task GetEvent_ReturnsPublishedEvent() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; // Act var result = await _controller.GetEvent(eventEntity.Id); // Assert var okResult = result.Result.Should().BeOfType().Subject; var response = okResult.Value.Should().BeOfType().Subject; response.Id.Should().Be(eventEntity.Id); response.Name.Should().Be(eventEntity.Name); } [Fact] public async Task GetEvent_OrganizerCanViewOwnDraftEvent() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft); _context.Events.Add(draftEvent); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); // Act var result = await _controller.GetEvent(draftEvent.Id); // Assert result.Result.Should().BeOfType(); } #endregion #region Get Single Event - Negative Tests [Fact] public async Task GetEvent_DraftEventNotVisibleToPublic() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft); _context.Events.Add(draftEvent); await _context.SaveChangesAsync(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; // Act var result = await _controller.GetEvent(draftEvent.Id); // Assert result.Result.Should().BeOfType(); } [Fact] public async Task GetEvent_NonExistentEvent_ReturnsNotFound() { // Arrange var nonExistentId = Guid.NewGuid(); _controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } }; // Act var result = await _controller.GetEvent(nonExistentId); // Assert result.Result.Should().BeOfType(); } #endregion #region Update Event - Positive Tests [Fact] public async Task UpdateEvent_WithValidData_UpdatesEvent() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new UpdateEventRequest { Name = "Updated Event Name", Description = "Updated description", Status = EventStatus.Published }; // Act var result = await _controller.UpdateEvent(eventEntity.Id, request); // Assert var okResult = result.Result.Should().BeOfType().Subject; var response = okResult.Value.Should().BeOfType().Subject; response.Name.Should().Be("Updated Event Name"); response.Status.Should().Be("Published"); } #endregion #region Update Event - Negative Tests [Fact] public async Task UpdateEvent_NonOrganizer_ReturnsForbidden() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer); _context.Users.AddRange(organizer, otherUser); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); SetUserContext(otherUser.Id, "Organizer"); var request = new UpdateEventRequest { Name = "Hacked Name" }; // Act var result = await _controller.UpdateEvent(eventEntity.Id, request); // Assert result.Result.Should().BeOfType(); } [Fact] public async Task UpdateEvent_NonExistentEvent_ReturnsNotFound() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); var request = new UpdateEventRequest { Name = "Updated Name" }; // Act var result = await _controller.UpdateEvent(Guid.NewGuid(), request); // Assert result.Result.Should().BeOfType(); } #endregion #region Delete Event - Positive Tests [Fact] public async Task DeleteEvent_OrganizerCanDeleteOwnEvent() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); _context.Users.Add(organizer); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); SetUserContext(organizer.Id, "Organizer"); // Act var result = await _controller.DeleteEvent(eventEntity.Id); // Assert result.Should().BeOfType(); var deletedEvent = await _context.Events.FindAsync(eventEntity.Id); deletedEvent.Should().BeNull(); } #endregion #region Delete Event - Negative Tests [Fact] public async Task DeleteEvent_NonOrganizer_ReturnsForbidden() { // Arrange var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer); _context.Users.AddRange(organizer, otherUser); var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id); _context.Events.Add(eventEntity); await _context.SaveChangesAsync(); SetUserContext(otherUser.Id, "Organizer"); // Act var result = await _controller.DeleteEvent(eventEntity.Id); // Assert result.Should().BeOfType(); } [Fact] public async Task DeleteEvent_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.DeleteEvent(Guid.NewGuid()); // Assert result.Should().BeOfType(); } #endregion }