using System.Collections; using Api.SeaHavenIndustries.Controllers; using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Moq; using SeaHaven.Services.DTOs; using SeaHaven.Services.Interfaces; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class CalendarControllerTests { private static EditEvent_DTO ValidDto(int? id = null, string title = "Standup") => new() { Id = id, Title = title, ReminderMinutes = 15, EventColor = "primary", StartDate = new DateTime(2026, 6, 1), StartTime = "08:00 AM", EndDate = new DateTime(2026, 6, 1), EndTime = "08:30 AM" }; private static CalendarController NewController(Mock service) => new(service.Object, Mock.Of>()); private static object Prop(object source, string name) => source.GetType().GetProperty(name)!.GetValue(source)!; [Fact] public async Task AddEvent_WhenServiceSucceeds_ReturnsOkWithCreatedMessage() { var service = new Mock(); var result = await NewController(service).AddEvent(ValidDto(), CancellationToken.None); var ok = result.Should().BeOfType().Subject; var response = ok.Value.Should().BeOfType().Subject; response.Status.Should().Be("200"); response.Message.Should().Be("Event Created Successfully"); service.Verify(s => s.CreateEventAsync(It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task AddEvent_WhenServiceThrows_ReturnsSanitizedMessageAndLogsInternally() { var service = new Mock(); service.Setup(s => s.CreateEventAsync(It.IsAny(), It.IsAny())) .ThrowsAsync(new InvalidOperationException("SECRET-internal-db-connection-leak")); var logger = new Mock>(); logger.Setup(x => x.IsEnabled(It.IsAny())).Returns(true); var controller = new CalendarController(service.Object, logger.Object); var result = await controller.AddEvent(ValidDto(), CancellationToken.None); var bad = result.Should().BeOfType().Subject; var response = bad.Value.Should().BeOfType().Subject; response.Status.Should().Be("Error"); response.Message.Should().NotContain("SECRET"); response.Message.Should().NotContain("internal-db-connection-leak"); response.Message.Should().Contain("reference"); logger.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.IsAny(), It.Is(ex => ex.Message.Contains("SECRET")), It.IsAny>()), Times.Once); } [Fact] public async Task EditEvent_WhenIdMissing_ReturnsBadRequestWithoutCallingService() { var service = new Mock(); var result = await NewController(service).EditEvent(ValidDto(id: null), CancellationToken.None); var bad = result.Should().BeOfType().Subject; var response = bad.Value.Should().BeOfType().Subject; response.Message.Should().Be("Event ID is required for editing"); service.Verify(s => s.UpdateEventAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task EditEvent_WhenServiceReturnsFalse_ReturnsNotFound() { var service = new Mock(); service.Setup(s => s.UpdateEventAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await NewController(service).EditEvent(ValidDto(id: 5), CancellationToken.None); var nf = result.Should().BeOfType().Subject; var response = nf.Value.Should().BeOfType().Subject; response.Message.Should().Be("Event not found"); } [Fact] public async Task EditEvent_WhenServiceReturnsTrue_ReturnsOkWithUpdatedMessage() { var service = new Mock(); service.Setup(s => s.UpdateEventAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(true); var result = await NewController(service).EditEvent(ValidDto(id: 5, title: "New"), CancellationToken.None); var ok = result.Should().BeOfType().Subject; ok.Value.Should().BeOfType().Which.Message.Should().Be("Event Updated Successfully"); } [Fact] public async Task GetEventById_WhenServiceReturnsNull_ReturnsNotFound() { var service = new Mock(); service.Setup(s => s.GetEventByIdAsync(7, It.IsAny())).ReturnsAsync((EventDTO?)null); var result = await NewController(service).GetEventById(7, CancellationToken.None); result.Should().BeOfType(); } [Fact] public async Task GetEventById_WhenServiceReturnsDto_ReturnsOkWithEventDto() { var service = new Mock(); service.Setup(s => s.GetEventByIdAsync(7, It.IsAny())) .ReturnsAsync(new EventDTO { Id = 7, Title = "Review", createdby = "abc" }); var result = await NewController(service).GetEventById(7, CancellationToken.None); var ok = result.Should().BeOfType().Subject; var response = ok.Value.Should().BeOfType().Subject; var dto = response.Data as Event_DTO; dto.Should().NotBeNull(); dto!.Title.Should().Be("Review"); dto!.createdby.Should().Be("abc"); } [Fact] public async Task DeleteEvent_WhenServiceReturnsFalse_ReturnsNotFound() { var service = new Mock(); service.Setup(s => s.DeleteEventAsync(9, It.IsAny())).ReturnsAsync(false); var result = await NewController(service).DeleteEvent(9, CancellationToken.None); result.Should().BeOfType(); } [Fact] public async Task DeleteEvent_WhenServiceReturnsTrue_ReturnsOkWithDeletedMessage() { var service = new Mock(); service.Setup(s => s.DeleteEventAsync(9, It.IsAny())).ReturnsAsync(true); var result = await NewController(service).DeleteEvent(9, CancellationToken.None); var ok = result.Should().BeOfType().Subject; ok.Value.Should().BeOfType().Which.Message.Should().Be("Event Deleted Successfully"); } [Fact] public async Task GetEventList_ReturnsOkWithRowsAndTotalCount() { var service = new Mock(); service.Setup(s => s.GetEventListAsync("alpha", 1, 10, It.IsAny())) .ReturnsAsync(new PagedEventsResult { TotalCount = 2, Rows = new List { new() { Id = 1, Title = "Alpha meeting" }, new() { Id = 2, Title = "Alpha review" } } }); var result = await NewController(service).GetEventList(search: "alpha", page: 1, pageSize: 10); var ok = result.Should().BeOfType().Subject; var data = ok.Value.Should().BeOfType().Subject.Data!; ((int)Prop(data, "totalCount")).Should().Be(2); var rows = ((IEnumerable)Prop(data, "rows")).Cast().ToList(); rows.Should().HaveCount(2); } [Fact] public async Task GetEventsForCalendar_ReturnsOkWithEvents() { var service = new Mock(); service.Setup(s => s.GetEventsForCalendarAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new List { new() { Id = 1, Title = "InRange" } }); var result = await NewController(service).GetEventsForCalendar( start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30), CancellationToken.None); var ok = result.Should().BeOfType().Subject; var data = ok.Value.Should().BeOfType().Subject.Data; var events = ((IEnumerable)data!).Cast().ToList(); events.Single().Title.Should().Be("InRange"); } }