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", organizer.Id); 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", organizer.Id); 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 }