mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-06 22:52:11 +00:00
Compare commits
No commits in common. "b2aeb35a55a0a918e3b42a9d5dad51f9a0c96ab7" and "dd1ffba06d40b7c4ac09526b3b25fd44dedacb4c" have entirely different histories.
b2aeb35a55
...
dd1ffba06d
14 changed files with 3 additions and 918 deletions
|
|
@ -1,25 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\Api.SeaHavenIndustries\Api.SeaHavenIndustries.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
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<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static UserManager<ApplicationUser> MockUserManager()
|
||||
{
|
||||
var store = new Mock<IUserStore<ApplicationUser>>();
|
||||
return new Mock<UserManager<ApplicationUser>>(
|
||||
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<OkObjectResult>().Subject;
|
||||
ok.Value.Should().BeOfType<DataResponse>().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<BadRequestObjectResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditEvent_WhenEventNotFound_ReturnsNotFound()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var result = await NewController(ctx).EditEvent(ValidDto(id: 999));
|
||||
|
||||
result.Should().BeOfType<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[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<OkObjectResult>();
|
||||
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<OkObjectResult>().Subject;
|
||||
var dto = ok.Value.Should().BeOfType<DataResponse>().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<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[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<OkObjectResult>();
|
||||
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<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[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<OkObjectResult>().Subject;
|
||||
var data = ok.Value.Should().BeOfType<DataResponse>().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<DataResponse>().Data!;
|
||||
var rows = ((IEnumerable)Prop(data, "rows")).Cast<Event_DTO>().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<OkObjectResult>().Subject;
|
||||
var events = ok.Value.As<DataResponse>().Data as IEnumerable<Event_DTO>;
|
||||
events!.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/Calendar")]
|
||||
public class CalendarController : Controller
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// CREATE EVENT
|
||||
// ===================================================================================
|
||||
[HttpPost]
|
||||
[Route("AddEvent")]
|
||||
public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = new Events
|
||||
{
|
||||
Title = eventDto.Title,
|
||||
Description = eventDto.Description,
|
||||
Location = eventDto.Location,
|
||||
RecordID = eventDto.RecordID,
|
||||
RecordName = eventDto.RecordName,
|
||||
RecordTargetType = eventDto.RecordTargetType,
|
||||
ReminderMinutes = eventDto.ReminderMinutes,
|
||||
EventColor = eventDto.EventColor,
|
||||
StartDate = eventDto.StartDate,
|
||||
StartTime = eventDto.StartTime,
|
||||
EndDate = eventDto.EndDate,
|
||||
EndTime = eventDto.EndTime,
|
||||
AllDay = eventDto.AllDay,
|
||||
CreatedDate = DateTime.Now,
|
||||
IsDeleted = false
|
||||
};
|
||||
|
||||
_db.Events.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// UPDATE EVENT
|
||||
// ===================================================================================
|
||||
[HttpPost]
|
||||
[Route("EditEvent")]
|
||||
public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!eventDto.Id.HasValue)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
|
||||
}
|
||||
|
||||
var model = await _db.Events.FindAsync(eventDto.Id.Value);
|
||||
if (model == null || model.IsDeleted == true)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Event not found" });
|
||||
}
|
||||
|
||||
model.Title = eventDto.Title;
|
||||
model.Description = eventDto.Description;
|
||||
model.Location = eventDto.Location;
|
||||
model.RecordID = eventDto.RecordID;
|
||||
model.RecordName = eventDto.RecordName;
|
||||
model.RecordTargetType = eventDto.RecordTargetType;
|
||||
model.ReminderMinutes = eventDto.ReminderMinutes;
|
||||
model.EventColor = eventDto.EventColor;
|
||||
model.StartDate = eventDto.StartDate;
|
||||
model.StartTime = eventDto.StartTime;
|
||||
model.EndDate = eventDto.EndDate;
|
||||
model.EndTime = eventDto.EndTime;
|
||||
model.AllDay = eventDto.AllDay;
|
||||
model.LastModificationTime = DateTime.Now;
|
||||
|
||||
_db.Events.Update(model);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// GET EVENT BY ID
|
||||
// ===================================================================================
|
||||
[HttpGet]
|
||||
[Route("GetEventById/{id}")]
|
||||
public async Task<IActionResult> GetEventById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = await _db.Events
|
||||
.Where(e => e.Id == id && e.IsDeleted != true)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Event not found" });
|
||||
}
|
||||
|
||||
var dto = new Event_DTO
|
||||
{
|
||||
Id = model.Id,
|
||||
Title = model.Title,
|
||||
Description = model.Description,
|
||||
Location = model.Location,
|
||||
RecordID = model.RecordID,
|
||||
RecordName = model.RecordName,
|
||||
RecordTargetType = model.RecordTargetType,
|
||||
ReminderMinutes = model.ReminderMinutes,
|
||||
EventColor = model.EventColor,
|
||||
StartDate = model.StartDate,
|
||||
StartTime = model.StartTime,
|
||||
EndDate = model.EndDate,
|
||||
EndTime = model.EndTime,
|
||||
AllDay = model.AllDay,
|
||||
CreatedDate = model.CreatedDate,
|
||||
createdby = model.createdby
|
||||
};
|
||||
|
||||
return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// DELETE EVENT
|
||||
// ===================================================================================
|
||||
[HttpDelete]
|
||||
[Route("DeleteEvent/{id}")]
|
||||
public async Task<IActionResult> DeleteEvent(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = await _db.Events.FindAsync(id);
|
||||
if (model == null)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Event not found" });
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
model.IsDeleted = true;
|
||||
model.DeletionTime = DateTime.Now;
|
||||
|
||||
_db.Events.Update(model);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// GET EVENTS LIST (WITH PAGINATION AND FILTERS)
|
||||
// ===================================================================================
|
||||
[HttpGet]
|
||||
[Route("GetEventList")]
|
||||
public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _db.Events.Where(e => e.IsDeleted != true);
|
||||
|
||||
// Apply search filter if provided
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var searchLower = search.ToLower();
|
||||
query = query.Where(e =>
|
||||
e.Title.ToLower().Contains(searchLower) ||
|
||||
(e.Description != null && e.Description.ToLower().Contains(searchLower)) ||
|
||||
(e.Location != null && e.Location.ToLower().Contains(searchLower))
|
||||
);
|
||||
}
|
||||
|
||||
// IMPORTANT: Calculate totalCount AFTER applying filters
|
||||
var totalCount = query.Count();
|
||||
|
||||
// Apply pagination
|
||||
var events = query
|
||||
.OrderByDescending(e => e.StartDate)
|
||||
.ThenByDescending(e => e.CreatedDate)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(e => new Event_DTO
|
||||
{
|
||||
Id = e.Id,
|
||||
Title = e.Title,
|
||||
Description = e.Description,
|
||||
Location = e.Location,
|
||||
RecordID = e.RecordID,
|
||||
RecordName = e.RecordName,
|
||||
RecordTargetType = e.RecordTargetType,
|
||||
ReminderMinutes = e.ReminderMinutes,
|
||||
EventColor = e.EventColor,
|
||||
StartDate = e.StartDate,
|
||||
StartTime = e.StartTime,
|
||||
EndDate = e.EndDate,
|
||||
EndTime = e.EndTime,
|
||||
AllDay = e.AllDay,
|
||||
CreatedDate = e.CreatedDate,
|
||||
createdby = e.createdby
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(new DataResponse
|
||||
{
|
||||
Message = "Success",
|
||||
Status = "200",
|
||||
Data = new { rows = events, totalCount }
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// GET EVENTS FOR CALENDAR (Date Range Filter)
|
||||
// ===================================================================================
|
||||
[HttpGet]
|
||||
[Route("GetEventsForCalendar")]
|
||||
public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _db.Events.Where(e => e.IsDeleted != true);
|
||||
|
||||
// Filter by date range if provided
|
||||
if (start.HasValue && end.HasValue)
|
||||
{
|
||||
query = query.Where(e =>
|
||||
(e.StartDate >= start.Value && e.StartDate <= end.Value) ||
|
||||
(e.EndDate >= start.Value && e.EndDate <= end.Value) ||
|
||||
(e.StartDate <= start.Value && e.EndDate >= end.Value)
|
||||
);
|
||||
}
|
||||
|
||||
var events = query
|
||||
.Select(e => new Event_DTO
|
||||
{
|
||||
Id = e.Id,
|
||||
Title = e.Title,
|
||||
Description = e.Description,
|
||||
Location = e.Location,
|
||||
RecordID = e.RecordID,
|
||||
RecordName = e.RecordName,
|
||||
RecordTargetType = e.RecordTargetType,
|
||||
ReminderMinutes = e.ReminderMinutes,
|
||||
EventColor = e.EventColor,
|
||||
StartDate = e.StartDate,
|
||||
StartTime = e.StartTime,
|
||||
EndDate = e.EndDate,
|
||||
EndTime = e.EndTime,
|
||||
AllDay = e.AllDay,
|
||||
CreatedDate = e.CreatedDate,
|
||||
createdby = e.createdby
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(new DataResponse { Message = "Success", Status = "200", Data = events });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO for POST/PUT requests - Create/Edit Event
|
||||
/// Used to create or update event information
|
||||
/// </summary>
|
||||
public class EditEvent_DTO
|
||||
{
|
||||
public int? Id { get; set; } // Null for create, populated for edit
|
||||
|
||||
[Required(ErrorMessage = "Please enter a title")]
|
||||
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "The Remind Attendees field is required")]
|
||||
public int ReminderMinutes { get; set; } = 15;
|
||||
|
||||
[Required(ErrorMessage = "The Select a Calendar Color field is required")]
|
||||
public string EventColor { get; set; } = "primary";
|
||||
|
||||
[Required(ErrorMessage = "Please select a start date")]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select a start time")]
|
||||
public string StartTime { get; set; } = null!;
|
||||
|
||||
[Required(ErrorMessage = "Please select an end date")]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select an end time")]
|
||||
public string EndTime { get; set; } = null!;
|
||||
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO for GET responses - Event data
|
||||
/// Used to return event information to the client
|
||||
/// </summary>
|
||||
public class Event_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = null!;
|
||||
public string? Description { get; set; }
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
public int ReminderMinutes { get; set; }
|
||||
public string EventColor { get; set; } = null!;
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
public string? StartTime { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
public string? EndTime { get; set; }
|
||||
public bool AllDay { get; set; }
|
||||
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? createdby { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +66,6 @@ namespace Data.SeaHavenIndustries
|
|||
public DbSet<FollowUps> FollowUps { get; set; }
|
||||
public DbSet<PMSchedules> PMSchedules { get; set; }
|
||||
public DbSet<ContactDetails> ContactDetails { get; set; }
|
||||
public DbSet<Events> Events { get; set; }
|
||||
|
||||
// Employee related DbSets
|
||||
public DbSet<EmployeeAddress> EmployeeAddresses { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
/// <summary>
|
||||
/// Events entity for Calendar/Events module
|
||||
/// Tracks calendar events, appointments, meetings, etc.
|
||||
/// </summary>
|
||||
public class Events : FullAuditEntity
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[StringLength(150)]
|
||||
public string? Location { get; set; }
|
||||
|
||||
// Related Record (can link to Accounts, Contacts, Work Orders, etc.)
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
// Reminder Settings
|
||||
public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
|
||||
|
||||
// Color for calendar display
|
||||
[StringLength(20)]
|
||||
public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
|
||||
|
||||
// Schedule
|
||||
[Required]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string? StartTime { get; set; } // e.g., "08:00 AM"
|
||||
|
||||
[Required]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string? EndTime { get; set; } // e.g., "08:30 AM"
|
||||
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class PagedResultTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 10, 0)]
|
||||
[InlineData(10, 10, 1)]
|
||||
[InlineData(25, 10, 3)]
|
||||
[InlineData(100, 25, 4)]
|
||||
public void TotalPages_IsCeilingOfCountOverPageSize(int totalCount, int pageSize, int expectedPages)
|
||||
{
|
||||
var result = new PagedResult<string> { TotalCount = totalCount, PageSize = pageSize, Page = 1 };
|
||||
|
||||
result.TotalPages.Should().Be(expectedPages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasPrevious_IsFalseOnFirstPage_TrueAfterward()
|
||||
{
|
||||
new PagedResult<string> { Page = 1, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeFalse();
|
||||
new PagedResult<string> { Page = 2, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasNext_IsTrueBeforeLastPage_FalseOnLastPage()
|
||||
{
|
||||
new PagedResult<string> { Page = 2, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeTrue();
|
||||
new PagedResult<string> { Page = 3, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SeaHaven.Services\SeaHaven.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class CreateAssetValidationTests
|
||||
{
|
||||
private readonly CreateAssetValidation _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNameEmpty_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "" });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNameTooLong_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = new string('a', 201) });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenLocationIdZero_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "Pump", LocationId = 0 });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.LocationId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenValid_Passes()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "Pump", LocationId = 5 });
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateContactValidationTests
|
||||
{
|
||||
private readonly CreateContactValidation _validator = new();
|
||||
|
||||
private static CreateContactDTO Valid() => new()
|
||||
{
|
||||
FirstName = "Ada",
|
||||
LastName = "Lovelace",
|
||||
Email = "ada@example.com"
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNamesMissing_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.FirstName = "";
|
||||
dto.LastName = "";
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.FirstName));
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.LastName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenEmailMalformed_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.Email = "not-an-email";
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.Email));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenPhoneTooLong_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.Phone = new string('1', 21);
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.Phone));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenValid_Passes()
|
||||
{
|
||||
var result = _validator.Validate(Valid());
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class WorkOrderMappingTests
|
||||
{
|
||||
[Fact]
|
||||
public void MapToModel_WithValidEnumStrings_ParsesStatusAndPriority()
|
||||
{
|
||||
var entity = new WorkOrder
|
||||
{
|
||||
Id = 7,
|
||||
WorkerOrderTitle = "Fix pump",
|
||||
Status = "InProgress",
|
||||
Priority = "High"
|
||||
};
|
||||
|
||||
var dto = WorkOrderDTO.MapToModel(entity);
|
||||
|
||||
dto.Id.Should().Be(7);
|
||||
dto.WorkerOrderTitle.Should().Be("Fix pump");
|
||||
dto.Status.Should().Be(WorkOrderStatus.InProgress);
|
||||
dto.Priority.Should().Be(WorkOrderPriority.High);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToModel_WithUnparseableEnumStrings_FallsBackToDefaults()
|
||||
{
|
||||
var entity = new WorkOrder
|
||||
{
|
||||
WorkerOrderTitle = "Garbage",
|
||||
Status = "not-a-status",
|
||||
Priority = "not-a-priority"
|
||||
};
|
||||
|
||||
var dto = WorkOrderDTO.MapToModel(entity);
|
||||
|
||||
dto.Status.Should().Be(WorkOrderStatus.Open);
|
||||
dto.Priority.Should().Be(WorkOrderPriority.Medium);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToEntity_SerializesEnumsToStrings()
|
||||
{
|
||||
var dto = new WorkOrderDTO
|
||||
{
|
||||
Id = 3,
|
||||
WorkerOrderTitle = "Inspect",
|
||||
Status = WorkOrderStatus.Completed,
|
||||
Priority = WorkOrderPriority.Critical
|
||||
};
|
||||
|
||||
var entity = WorkOrderDTO.MapToEntity(dto);
|
||||
|
||||
entity.Status.Should().Be("Completed");
|
||||
entity.Priority.Should().Be("Critical");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMapToEntity_WhenStatusAndPriorityNull_AppliesDefaults()
|
||||
{
|
||||
var dto = new CreateWorkOrderDTO { WorkerOrderTitle = "New" };
|
||||
|
||||
var entity = CreateWorkOrderDTO.MapToEntity(dto);
|
||||
|
||||
entity.Status.Should().Be("Open");
|
||||
entity.Priority.Should().Be("Medium");
|
||||
entity.CreatedDate.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.Services", "SeaHav
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.DataServices", "SeaHaven.DataServices\SeaHaven.DataServices.csproj", "{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.SeaHavenIndustries.Tests", "Api.SeaHavenIndustries.Tests\Api.SeaHavenIndustries.Tests.csproj", "{1222E2E5-2FB0-4AA8-842C-0D77CFBA5E45}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.Services.Tests", "SeaHaven.Services.Tests\SeaHaven.Services.Tests.csproj", "{97455198-C479-47F2-8BC8-601B335DFFAB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -43,14 +39,6 @@ Global
|
|||
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{60DF3943-9BC3-4444-8E7A-EEC34DDC7806}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1222E2E5-2FB0-4AA8-842C-0D77CFBA5E45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1222E2E5-2FB0-4AA8-842C-0D77CFBA5E45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1222E2E5-2FB0-4AA8-842C-0D77CFBA5E45}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1222E2E5-2FB0-4AA8-842C-0D77CFBA5E45}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{97455198-C479-47F2-8BC8-601B335DFFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{97455198-C479-47F2-8BC8-601B335DFFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{97455198-C479-47F2-8BC8-601B335DFFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{97455198-C479-47F2-8BC8-601B335DFFAB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
<PackageReference Include="BlazorGoogleMaps" Version="4.7.7" />
|
||||
<PackageReference Include="EPPlus" Version="7.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4" />
|
||||
<PackageReference Include="PasswordGenerator" Version="2.1.0" />
|
||||
<PackageReference Include="PuppeteerSharp" Version="19.0.1" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="5.1.3" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue