diff --git a/SeaHaven.Services.Tests/PagedResultTests.cs b/SeaHaven.Services.Tests/PagedResultTests.cs new file mode 100644 index 0000000..911370e --- /dev/null +++ b/SeaHaven.Services.Tests/PagedResultTests.cs @@ -0,0 +1,34 @@ +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 { TotalCount = totalCount, PageSize = pageSize, Page = 1 }; + + result.TotalPages.Should().Be(expectedPages); + } + + [Fact] + public void HasPrevious_IsFalseOnFirstPage_TrueAfterward() + { + new PagedResult { Page = 1, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeFalse(); + new PagedResult { Page = 2, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeTrue(); + } + + [Fact] + public void HasNext_IsTrueBeforeLastPage_FalseOnLastPage() + { + new PagedResult { Page = 2, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeTrue(); + new PagedResult { Page = 3, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeFalse(); + } +} diff --git a/SeaHaven.Services.Tests/SeaHaven.Services.Tests.csproj b/SeaHaven.Services.Tests/SeaHaven.Services.Tests.csproj new file mode 100644 index 0000000..fdbbfc2 --- /dev/null +++ b/SeaHaven.Services.Tests/SeaHaven.Services.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + + + + diff --git a/SeaHaven.Services.Tests/ValidationTests.cs b/SeaHaven.Services.Tests/ValidationTests.cs new file mode 100644 index 0000000..5d682a9 --- /dev/null +++ b/SeaHaven.Services.Tests/ValidationTests.cs @@ -0,0 +1,104 @@ +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(); + } +} diff --git a/SeaHaven.Services.Tests/WorkOrderMappingTests.cs b/SeaHaven.Services.Tests/WorkOrderMappingTests.cs new file mode 100644 index 0000000..28f6ea3 --- /dev/null +++ b/SeaHaven.Services.Tests/WorkOrderMappingTests.cs @@ -0,0 +1,74 @@ +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(); + } +} diff --git a/SeaHavenIndustries.sln b/SeaHavenIndustries.sln index b9df8e5..429aed6 100644 --- a/SeaHavenIndustries.sln +++ b/SeaHavenIndustries.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeaHaven.DataServices", "Se 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 @@ -45,6 +47,10 @@ Global {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