proposal-system/api/tests/ProposalSystem.Tests/Validators/CreateProposalValidatorTests.cs
Adam Moussa 9c04ba4756 fix: resolve test compile errors from merge, update AUDIT-REPORT.md
Fix CreateProposalRequest constructor calls (missing PoNumber param)
and ProposalService constructor (missing ILogger param) that diverged
when test-bootstrap and api-hardening worktrees merged.

Mark all Critical and High findings as fixed in AUDIT-REPORT.md with
remediation status for each phase.
2026-05-27 18:18:44 -04:00

189 lines
6 KiB
C#

using FluentAssertions;
using ProposalSystem.Application.DTOs;
using ProposalSystem.Application.Validators;
using ProposalSystem.Domain.Entities;
using Xunit;
namespace ProposalSystem.Tests.Validators;
/// <summary>
/// Tests for CreateProposalValidator — validates request payloads before
/// they hit the service layer.
/// </summary>
public class CreateProposalValidatorTests
{
private readonly CreateProposalValidator _sut = new();
[Fact(DisplayName = "Valid proposal request passes validation")]
public void ValidRequest_Passes()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Acme Corp",
CustomerAddress: "123 Main St, Suite 100",
ScopeOfWork: "Replace HVAC system in building B",
ServiceCategory: ServiceCategory.HVAC,
Priority: Priority.Standard,
Notes: "Initial assessment complete"
);
var result = _sut.Validate(request);
result.IsValid.Should().BeTrue();
}
[Theory(DisplayName = "Empty required fields fail validation")]
[InlineData("", "Customer", "Address", "Scope")]
[InlineData("WO-001", "", "Address", "Scope")]
[InlineData("WO-001", "Customer", "", "Scope")]
[InlineData("WO-001", "Customer", "Address", "")]
public void EmptyRequiredFields_Fail(string wo, string name, string address, string scope)
{
var request = new CreateProposalRequest(wo, null, name, address, scope,
ServiceCategory.General, Priority.Standard, null);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
}
[Fact(DisplayName = "WorkOrderNumber exceeding 50 chars fails")]
public void WorkOrderNumber_TooLong_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: new string('X', 51),
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: ServiceCategory.General,
Priority: Priority.Standard,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "WorkOrderNumber");
}
[Fact(DisplayName = "CustomerName exceeding 200 chars fails")]
public void CustomerName_TooLong_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: new string('X', 201),
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: ServiceCategory.General,
Priority: Priority.Standard,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "CustomerName");
}
[Fact(DisplayName = "ScopeOfWork exceeding 10000 chars fails")]
public void ScopeOfWork_TooLong_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: new string('X', 10001),
ServiceCategory: ServiceCategory.General,
Priority: Priority.Standard,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "ScopeOfWork");
}
[Fact(DisplayName = "Notes exceeding 5000 chars fails")]
public void Notes_TooLong_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: ServiceCategory.General,
Priority: Priority.Standard,
Notes: new string('X', 5001)
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "Notes");
}
[Fact(DisplayName = "Null notes are valid")]
public void NullNotes_Passes()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: ServiceCategory.General,
Priority: Priority.Standard,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeTrue();
}
[Fact(DisplayName = "Invalid ServiceCategory enum value fails")]
public void InvalidServiceCategory_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: (ServiceCategory)999,
Priority: Priority.Standard,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "ServiceCategory");
}
[Fact(DisplayName = "Invalid Priority enum value fails")]
public void InvalidPriority_Fails()
{
var request = new CreateProposalRequest(
WorkOrderNumber: "WO-001",
PoNumber: null,
CustomerName: "Customer",
CustomerAddress: "Address",
ScopeOfWork: "Scope",
ServiceCategory: ServiceCategory.General,
Priority: (Priority)999,
Notes: null
);
var result = _sut.Validate(request);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "Priority");
}
}