using System.Collections; using Api.SeaHavenIndustries.Controllers; using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using FluentAssertions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class CalendarControllerTests { private static ApplicationDbContext NewContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new ApplicationDbContext(options); } private static UserManager MockUserManager() { var store = new Mock>(); return new Mock>( store.Object, null!, null!, null!, null!, null!, null!, null!, null!).Object; } private static CalendarController NewController(ApplicationDbContext ctx) => new(MockUserManager(), ctx); 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 Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false) { var ev = new Events { Title = title, EventColor = "primary", ReminderMinutes = 15, StartDate = startDate, EndDate = startDate, IsDeleted = isDeleted, CreatedDate = DateTime.Now }; ctx.Events.Add(ev); ctx.SaveChanges(); return ev; } private static object Prop(object source, string name) => source.GetType().GetProperty(name)!.GetValue(source)!; [Fact] public async Task AddEvent_WhenValid_PersistsEventAndReturnsOk() { using var ctx = NewContext(); var result = await NewController(ctx).AddEvent(ValidDto()); var ok = result.Should().BeOfType().Subject; ok.Value.Should().BeOfType().Which.Status.Should().Be("200"); var saved = ctx.Events.Single(); saved.Title.Should().Be("Standup"); saved.IsDeleted.Should().Be(false); saved.CreatedDate.Should().NotBeNull(); } [Fact] public async Task EditEvent_WhenIdMissing_ReturnsBadRequest() { using var ctx = NewContext(); var result = await NewController(ctx).EditEvent(ValidDto(id: null)); result.Should().BeOfType(); } [Fact] public async Task EditEvent_WhenEventNotFound_ReturnsNotFound() { using var ctx = NewContext(); var result = await NewController(ctx).EditEvent(ValidDto(id: 999)); result.Should().BeOfType(); } [Fact] public async Task EditEvent_WhenEventExists_UpdatesFieldsAndStampsModified() { using var ctx = NewContext(); var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1)); var result = await NewController(ctx).EditEvent(ValidDto(id: existing.Id, title: "New")); result.Should().BeOfType(); var updated = ctx.Events.Single(); updated.Title.Should().Be("New"); updated.LastModificationTime.Should().NotBeNull(); } [Fact] public async Task GetEventById_WhenExists_ReturnsEventDto() { using var ctx = NewContext(); var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1)); var result = await NewController(ctx).GetEventById(existing.Id); var ok = result.Should().BeOfType().Subject; var dto = ok.Value.Should().BeOfType().Subject.Data as Event_DTO; dto!.Title.Should().Be("Review"); } [Fact] public async Task GetEventById_WhenSoftDeleted_ReturnsNotFound() { using var ctx = NewContext(); var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true); var result = await NewController(ctx).GetEventById(deleted.Id); result.Should().BeOfType(); } [Fact] public async Task DeleteEvent_WhenExists_SoftDeletesAndKeepsRow() { using var ctx = NewContext(); var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1)); var result = await NewController(ctx).DeleteEvent(existing.Id); result.Should().BeOfType(); var row = ctx.Events.Single(); row.IsDeleted.Should().Be(true); row.DeletionTime.Should().NotBeNull(); } [Fact] public async Task DeleteEvent_WhenNotFound_ReturnsNotFound() { using var ctx = NewContext(); var result = await NewController(ctx).DeleteEvent(12345); result.Should().BeOfType(); } [Fact] public void GetEventList_ExcludesDeletedAndCountsAfterFilter() { using var ctx = NewContext(); SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1)); SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2)); SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3)); SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true); var result = NewController(ctx).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); } [Fact] public void GetEventList_OrdersByStartDateDescending() { using var ctx = NewContext(); SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1)); SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10)); SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5)); var result = NewController(ctx).GetEventList(page: 1, pageSize: 10); var data = (result as OkObjectResult)!.Value.As().Data!; var rows = ((IEnumerable)Prop(data, "rows")).Cast().ToList(); rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest"); } [Fact] public void GetEventsForCalendar_ReturnsOnlyEventsOverlappingRange() { using var ctx = NewContext(); SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15)); SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1)); SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1)); var result = NewController(ctx).GetEventsForCalendar( start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30)); var ok = result.Should().BeOfType().Subject; var events = ok.Value.As().Data as IEnumerable; events!.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" }); } }