Add starter unit tests for validators, DTO mappers, and PagedResult (#9)

This commit is contained in:
Adam Moussa 2026-06-22 18:56:46 -04:00 • committed by GitHub
parent 1f3972ae49
commit b2aeb35a55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 241 additions and 0 deletions

View file

@ -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<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();
}
}

View file

@ -0,0 +1,23 @@
<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>

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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