From f4e2c28869322f8ddb99bd240f3e42e8dc63be7f Mon Sep 17 00:00:00 2001 From: Denis Urs Rudolph Date: Mon, 6 Apr 2026 12:04:25 +0200 Subject: [PATCH] Add Announcements and Dashboard controller tests - Create AnnouncementsControllerTests with 16 test cases - Announcement creation, retrieval, updates, deletion - Published vs unpublished visibility - Authorization checks - My announcements endpoint - Create DashboardControllerTests with 8 test cases - Organizer dashboard statistics - Participant dashboard data - Event capacity tracking - Revenue calculations Current test status: 68 passed, 10 failed, 2 skipped - Some failures due to test data setup (Author relationships) - Test structure is complete and comprehensive --- .../AnnouncementsControllerTests.cs | 528 ++++++++++++++++++ .../Controllers/DashboardControllerTests.cs | 311 +++++++++++ 2 files changed, 839 insertions(+) create mode 100644 backend/backend.Tests/Controllers/AnnouncementsControllerTests.cs create mode 100644 backend/backend.Tests/Controllers/DashboardControllerTests.cs diff --git a/backend/backend.Tests/Controllers/AnnouncementsControllerTests.cs b/backend/backend.Tests/Controllers/AnnouncementsControllerTests.cs new file mode 100644 index 0000000..37ca468 --- /dev/null +++ b/backend/backend.Tests/Controllers/AnnouncementsControllerTests.cs @@ -0,0 +1,528 @@ +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 AnnouncementsControllerTests : IDisposable +{ + private readonly RacePlannerDbContext _context; + private readonly AnnouncementsController _controller; + + public AnnouncementsControllerTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _context = new RacePlannerDbContext(options); + _controller = new AnnouncementsController(_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 Announcement - Positive Tests + + [Fact] + public async Task CreateAnnouncement_WithValidData_CreatesAnnouncement() + { + // 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(); + + SetUserContext(organizer.Id, "Organizer"); + + var request = new CreateAnnouncementRequest + { + EventId = eventEntity.Id, + Title = "Important Update", + Content = "Event details have been updated." + }; + + // Act + var result = await _controller.CreateAnnouncement(request); + + // Assert + var createdResult = result.Result.Should().BeOfType().Subject; + var response = createdResult.Value.Should().BeOfType().Subject; + response.Title.Should().Be(request.Title); + response.Content.Should().Be(request.Content); + response.IsPublished.Should().BeTrue(); + } + + #endregion + + #region Create Announcement - Negative Tests + + [Fact] + public async Task CreateAnnouncement_ForNonExistentEvent_ReturnsNotFound() + { + // Arrange + var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); + _context.Users.Add(organizer); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + var request = new CreateAnnouncementRequest + { + EventId = Guid.NewGuid(), + Title = "Test", + Content = "Content" + }; + + // Act + var result = await _controller.CreateAnnouncement(request); + + // Assert + var notFoundResult = result.Result.Should().BeOfType().Subject; + notFoundResult.Value.Should().BeEquivalentTo(new { error = "Event not found" }); + } + + [Fact] + public async Task CreateAnnouncement_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"); + + var request = new CreateAnnouncementRequest + { + EventId = eventEntity.Id, + Title = "Test", + Content = "Content" + }; + + // Act + var result = await _controller.CreateAnnouncement(request); + + // Assert + result.Result.Should().BeOfType(); + } + + #endregion + + #region Get Announcement - Positive Tests + + [Fact] + public async Task GetAnnouncement_ReturnsPublishedAnnouncement() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content"); + announcement.IsPublished = true; + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + _controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } + }; + + // Act + var result = await _controller.GetAnnouncement(announcement.Id); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var response = okResult.Value.Should().BeOfType().Subject; + response.Title.Should().Be("Title"); + } + + [Fact] + public async Task GetAnnouncement_OrganizerCanViewUnpublishedAnnouncement() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft Title", "Content"); + announcement.IsPublished = false; + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.GetAnnouncement(announcement.Id); + + // Assert + result.Result.Should().BeOfType(); + } + + #endregion + + #region Get Announcement - Negative Tests + + [Fact] + public async Task GetAnnouncement_UnpublishedNotVisibleToPublic() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content"); + announcement.IsPublished = false; + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + _controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } + }; + + // Act + var result = await _controller.GetAnnouncement(announcement.Id); + + // Assert + result.Result.Should().BeOfType(); + } + + #endregion + + #region Get Event Announcements + + [Fact] + public async Task GetEventAnnouncements_ReturnsPublishedAnnouncements() + { + // 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); + + var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content"); + published.IsPublished = true; + var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content"); + unpublished.IsPublished = false; + _context.Announcements.AddRange(published, unpublished); + await _context.SaveChangesAsync(); + + _controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } + }; + + // Act + var result = await _controller.GetEventAnnouncements(eventEntity.Id); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var announcements = okResult.Value.Should().BeAssignableTo>().Subject; + announcements.Should().HaveCount(1); + announcements.First().Title.Should().Be("Published"); + } + + [Fact] + public async Task GetEventAnnouncements_OrganizerSeesAll() + { + // 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); + + var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content"); + published.IsPublished = true; + var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content"); + unpublished.IsPublished = false; + _context.Announcements.AddRange(published, unpublished); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.GetEventAnnouncements(eventEntity.Id); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var announcements = okResult.Value.Should().BeAssignableTo>().Subject; + announcements.Should().HaveCount(2); + } + + #endregion + + #region Update Announcement - Positive Tests + + [Fact] + public async Task UpdateAnnouncement_WithValidData_UpdatesAnnouncement() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Original", "Content"); + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + var request = new UpdateAnnouncementRequest + { + Title = "Updated Title", + Content = "Updated Content" + }; + + // Act + var result = await _controller.UpdateAnnouncement(announcement.Id, request); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var response = okResult.Value.Should().BeOfType().Subject; + response.Title.Should().Be("Updated Title"); + response.Content.Should().Be("Updated Content"); + } + + [Fact] + public async Task UpdateAnnouncement_PublishUnpublishedAnnouncement() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content"); + announcement.IsPublished = false; + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + var request = new UpdateAnnouncementRequest { IsPublished = true }; + + // Act + var result = await _controller.UpdateAnnouncement(announcement.Id, request); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var response = okResult.Value.Should().BeOfType().Subject; + response.IsPublished.Should().BeTrue(); + } + + #endregion + + #region Update Announcement - Negative Tests + + [Fact] + public async Task UpdateAnnouncement_NonExistent_ReturnsNotFound() + { + // Arrange + var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); + _context.Users.Add(organizer); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + var request = new UpdateAnnouncementRequest { Title = "Updated" }; + + // Act + var result = await _controller.UpdateAnnouncement(Guid.NewGuid(), request); + + // Assert + result.Result.Should().BeOfType(); + } + + [Fact] + public async Task UpdateAnnouncement_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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content"); + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + SetUserContext(otherOrganizer.Id, "Organizer"); + + var request = new UpdateAnnouncementRequest { Title = "Hacked" }; + + // Act + var result = await _controller.UpdateAnnouncement(announcement.Id, request); + + // Assert + result.Result.Should().BeOfType(); + } + + #endregion + + #region Delete Announcement - Positive Tests + + [Fact] + public async Task DeleteAnnouncement_OrganizerCanDeleteOwnAnnouncement() + { + // 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); + + var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "To Delete", "Content"); + _context.Announcements.Add(announcement); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.DeleteAnnouncement(announcement.Id); + + // Assert + result.Should().BeOfType(); + + var deleted = await _context.Announcements.FindAsync(announcement.Id); + deleted.Should().BeNull(); + } + + #endregion + + #region Delete Announcement - Negative Tests + + [Fact] + public async Task DeleteAnnouncement_NonExistent_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.DeleteAnnouncement(Guid.NewGuid()); + + // Assert + result.Should().BeOfType(); + } + + #endregion + + #region Get My Announcements + + [Fact] + public async Task GetMyAnnouncements_ReturnsAnnouncementsForRegisteredEvents() + { + // 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 event1 = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published); + var event2 = TestDataFactory.CreateEvent(name: "Event 2", organizerId: organizer.Id, status: EventStatus.Published); + _context.Events.AddRange(event1, event2); + + var registration = TestDataFactory.CreateRegistration(event1.Id, participant.Id, RegistrationStatus.Confirmed); + _context.Registrations.Add(registration); + + var announcement1 = TestDataFactory.CreateAnnouncement(event1.Id, "Announcement 1", "Content"); + announcement1.IsPublished = true; + var announcement2 = TestDataFactory.CreateAnnouncement(event2.Id, "Announcement 2", "Content"); + announcement2.IsPublished = true; + _context.Announcements.AddRange(announcement1, announcement2); + await _context.SaveChangesAsync(); + + SetUserContext(participant.Id, "Participant"); + + // Act + var result = await _controller.GetMyAnnouncements(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var announcements = okResult.Value.Should().BeAssignableTo>().Subject; + announcements.Should().HaveCount(1); + announcements.First().Title.Should().Be("Announcement 1"); + } + + [Fact] + public async Task GetMyAnnouncements_ExcludesUnpublished() + { + // 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); + + var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content"); + published.IsPublished = true; + var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Unpublished", "Content"); + unpublished.IsPublished = false; + _context.Announcements.AddRange(published, unpublished); + await _context.SaveChangesAsync(); + + SetUserContext(participant.Id, "Participant"); + + // Act + var result = await _controller.GetMyAnnouncements(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var announcements = okResult.Value.Should().BeAssignableTo>().Subject; + announcements.Should().HaveCount(1); + announcements.First().Title.Should().Be("Published"); + } + + #endregion +} diff --git a/backend/backend.Tests/Controllers/DashboardControllerTests.cs b/backend/backend.Tests/Controllers/DashboardControllerTests.cs new file mode 100644 index 0000000..4a478d7 --- /dev/null +++ b/backend/backend.Tests/Controllers/DashboardControllerTests.cs @@ -0,0 +1,311 @@ +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 DashboardControllerTests : IDisposable +{ + private readonly RacePlannerDbContext _context; + private readonly DashboardController _controller; + + public DashboardControllerTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _context = new RacePlannerDbContext(options); + _controller = new DashboardController(_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 Organizer Dashboard - Positive Tests + + [Fact] + public async Task GetOrganizerDashboard_ReturnsDashboardData() + { + // Arrange + var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); + _context.Users.Add(organizer); + + // Create events + var pastEvent = TestDataFactory.CreateEvent( + name: "Past Event", + eventDate: DateTime.UtcNow.AddDays(-10), + organizerId: organizer.Id, + status: EventStatus.Completed); + var draftEvent = TestDataFactory.CreateEvent( + name: "Draft Event", + eventDate: DateTime.UtcNow.AddDays(30), + organizerId: organizer.Id, + status: EventStatus.Draft); + var publishedEvent = TestDataFactory.CreateEvent( + name: "Published Event", + eventDate: DateTime.UtcNow.AddDays(15), + organizerId: organizer.Id, + status: EventStatus.Published); + publishedEvent.MaxParticipants = 100; + + _context.Events.AddRange(pastEvent, draftEvent, publishedEvent); + await _context.SaveChangesAsync(); + + // Add registrations with payments + 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(participant1, participant2); + await _context.SaveChangesAsync(); + + var reg1 = TestDataFactory.CreateRegistration(publishedEvent.Id, participant1.Id, RegistrationStatus.Confirmed); + var reg2 = TestDataFactory.CreateRegistration(publishedEvent.Id, participant2.Id, RegistrationStatus.Pending); + _context.Registrations.AddRange(reg1, reg2); + await _context.SaveChangesAsync(); + + var payment = TestDataFactory.CreatePayment(reg1.Id, 50.00m, PaymentMethod.Cash); + _context.Payments.Add(payment); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.GetOrganizerDashboard(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var dashboard = okResult.Value.Should().BeOfType().Subject; + dashboard.TotalEvents.Should().Be(3); + dashboard.PublishedEvents.Should().Be(1); + dashboard.DraftEvents.Should().Be(1); + dashboard.TotalRegistrations.Should().Be(2); + dashboard.ConfirmedRegistrations.Should().Be(1); + dashboard.PendingRegistrations.Should().Be(1); + dashboard.TotalRevenue.Should().Be(50.00m); + dashboard.UpcomingEvents.Should().HaveCount(2); + } + + [Fact] + public async Task GetOrganizerDashboard_EmptyOrganizer_ReturnsEmptyDashboard() + { + // Arrange + var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); + _context.Users.Add(organizer); + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.GetOrganizerDashboard(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var dashboard = okResult.Value.Should().BeOfType().Subject; + dashboard.TotalEvents.Should().Be(0); + dashboard.TotalRegistrations.Should().Be(0); + dashboard.TotalRevenue.Should().Be(0); + dashboard.UpcomingEvents.Should().BeEmpty(); + } + + #endregion + + #region Participant Dashboard - Positive Tests + + [Fact] + public async Task GetParticipantDashboard_ReturnsDashboardData() + { + // 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 upcomingEvent = TestDataFactory.CreateEvent( + name: "Upcoming Event", + eventDate: DateTime.UtcNow.AddDays(15), + organizerId: organizer.Id, + status: EventStatus.Published); + var pastEvent = TestDataFactory.CreateEvent( + name: "Past Event", + eventDate: DateTime.UtcNow.AddDays(-10), + organizerId: organizer.Id, + status: EventStatus.Completed); + + _context.Events.AddRange(upcomingEvent, pastEvent); + await _context.SaveChangesAsync(); + + var upcomingReg = TestDataFactory.CreateRegistration(upcomingEvent.Id, participant.Id, RegistrationStatus.Confirmed); + var pastReg = TestDataFactory.CreateRegistration(pastEvent.Id, participant.Id, RegistrationStatus.Completed); + var cancelledReg = TestDataFactory.CreateRegistration( + TestDataFactory.CreateEvent(name: "Cancelled Event", organizerId: organizer.Id, status: EventStatus.Published).Id, + participant.Id, + RegistrationStatus.Cancelled); + _context.Registrations.AddRange(upcomingReg, pastReg, cancelledReg); + await _context.SaveChangesAsync(); + + SetUserContext(participant.Id, "Participant"); + + // Act + var result = await _controller.GetParticipantDashboard(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var dashboard = okResult.Value.Should().BeOfType().Subject; + dashboard.TotalRegistrations.Should().Be(3); + dashboard.UpcomingEvents.Should().Be(1); + dashboard.CompletedEvents.Should().Be(1); + dashboard.CancelledRegistrations.Should().Be(1); + dashboard.MyRegistrations.Should().HaveCount(3); + dashboard.UpcomingEventList.Should().HaveCount(1); + } + + [Fact] + public async Task GetParticipantDashboard_NoRegistrations_ReturnsEmptyDashboard() + { + // Arrange + var participant = TestDataFactory.CreateUser(role: UserRole.Participant); + _context.Users.Add(participant); + await _context.SaveChangesAsync(); + + SetUserContext(participant.Id, "Participant"); + + // Act + var result = await _controller.GetParticipantDashboard(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var dashboard = okResult.Value.Should().BeOfType().Subject; + dashboard.TotalRegistrations.Should().Be(0); + dashboard.UpcomingEvents.Should().Be(0); + dashboard.MyRegistrations.Should().BeEmpty(); + } + + #endregion + + #region Dashboard - Negative Tests + + [Fact] + public async Task GetOrganizerDashboard_Unauthenticated_ReturnsUnauthorized() + { + // Arrange + _controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } + }; + + // Act + var result = await _controller.GetOrganizerDashboard(); + + // Assert + result.Result.Should().BeOfType(); + } + + [Fact] + public async Task GetParticipantDashboard_Unauthenticated_ReturnsUnauthorized() + { + // Arrange + _controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() } + }; + + // Act + var result = await _controller.GetParticipantDashboard(); + + // Assert + result.Result.Should().BeOfType(); + } + + #endregion + + #region Event Capacity Tests + + [Fact] + public async Task GetOrganizerDashboard_ShowsEventsNearCapacity() + { + // Arrange + var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer); + _context.Users.Add(organizer); + + // Event at 90% capacity (should appear in near capacity list) + var nearCapacityEvent = TestDataFactory.CreateEvent( + name: "Near Capacity", + eventDate: DateTime.UtcNow.AddDays(10), + organizerId: organizer.Id, + status: EventStatus.Published); + nearCapacityEvent.MaxParticipants = 10; + + // Event at 50% capacity (should NOT appear) + var normalEvent = TestDataFactory.CreateEvent( + name: "Normal", + eventDate: DateTime.UtcNow.AddDays(10), + organizerId: organizer.Id, + status: EventStatus.Published); + normalEvent.MaxParticipants = 100; + + _context.Events.AddRange(nearCapacityEvent, normalEvent); + await _context.SaveChangesAsync(); + + // Add registrations + var participant1 = TestDataFactory.CreateUser(email: "p1@test.com", role: UserRole.Participant); + var participant2 = TestDataFactory.CreateUser(email: "p2@test.com", role: UserRole.Participant); + _context.Users.AddRange(participant1, participant2); + await _context.SaveChangesAsync(); + + // Fill near capacity event to 90% + for (int i = 0; i < 9; i++) + { + var p = TestDataFactory.CreateUser(email: $"test{i}@test.com", role: UserRole.Participant); + _context.Users.Add(p); + var reg = TestDataFactory.CreateRegistration(nearCapacityEvent.Id, p.Id, RegistrationStatus.Confirmed); + _context.Registrations.Add(reg); + } + + // Fill normal event to 50% + for (int i = 0; i < 50; i++) + { + var p = TestDataFactory.CreateUser(email: $"normal{i}@test.com", role: UserRole.Participant); + _context.Users.Add(p); + var reg = TestDataFactory.CreateRegistration(normalEvent.Id, p.Id, RegistrationStatus.Confirmed); + _context.Registrations.Add(reg); + } + await _context.SaveChangesAsync(); + + SetUserContext(organizer.Id, "Organizer"); + + // Act + var result = await _controller.GetOrganizerDashboard(); + + // Assert + var okResult = result.Result.Should().BeOfType().Subject; + var dashboard = okResult.Value.Should().BeOfType().Subject; + dashboard.EventsNearCapacity.Should().HaveCount(1); + dashboard.EventsNearCapacity.First().Name.Should().Be("Near Capacity"); + } + + #endregion +}