using FluentAssertions; using ProposalSystem.Application.DTOs; using ProposalSystem.Application.Validators; using ProposalSystem.Domain.Entities; using Xunit; namespace ProposalSystem.Tests.Validators; /// /// PR5: Tests for CreatePricingLibraryItemValidator. /// public class CreatePricingLibraryItemValidatorTests { private readonly CreatePricingLibraryItemValidator _sut = new(); [Fact(DisplayName = "PR5: Valid pricing library item passes validation")] public void ValidRequest_Passes() { var request = new CreatePricingLibraryItemRequest( Title: "HVAC Filter Replacement", Description: "Standard 20x25x1 MERV-13 filter replacement", ServiceCategory: ServiceCategory.HVAC, Unit: "ea", UnitPrice: 45.00m, Keywords: "filter HVAC maintenance preventive", Source: "Manual" ); var result = _sut.Validate(request); result.IsValid.Should().BeTrue(); } [Fact(DisplayName = "PR5: Empty title fails validation")] public void EmptyTitle_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "", Description: null, ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Title"); } [Fact(DisplayName = "PR5: Title exceeding 200 chars fails")] public void Title_TooLong_Fails() { var request = new CreatePricingLibraryItemRequest( Title: new string('X', 201), Description: null, ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Title"); } [Fact(DisplayName = "PR5: Description exceeding 5000 chars fails")] public void Description_TooLong_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: new string('X', 5001), ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Description"); } [Fact(DisplayName = "PR5: Negative UnitPrice fails")] public void NegativeUnitPrice_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: null, ServiceCategory: ServiceCategory.Plumbing, Unit: "hr", UnitPrice: -10.00m, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "UnitPrice"); } [Fact(DisplayName = "PR5: Zero UnitPrice is valid")] public void ZeroUnitPrice_Passes() { var request = new CreatePricingLibraryItemRequest( Title: "Complimentary Inspection", Description: null, ServiceCategory: ServiceCategory.General, Unit: "ea", UnitPrice: 0m, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeTrue(); } [Fact(DisplayName = "PR5: Null UnitPrice is valid")] public void NullUnitPrice_Passes() { var request = new CreatePricingLibraryItemRequest( Title: "Variable Price Item", Description: null, ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeTrue(); } [Fact(DisplayName = "PR5: Keywords exceeding 2000 chars fails")] public void Keywords_TooLong_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: null, ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: new string('X', 2001), Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Keywords"); } [Fact(DisplayName = "PR5: Invalid ServiceCategory enum value fails")] public void InvalidServiceCategory_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: null, ServiceCategory: (ServiceCategory)999, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "ServiceCategory"); } [Fact(DisplayName = "PR5: Unit exceeding 50 chars fails")] public void Unit_TooLong_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: null, ServiceCategory: ServiceCategory.General, Unit: new string('X', 51), UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Unit"); } [Fact(DisplayName = "PR5: Source exceeding 100 chars fails")] public void Source_TooLong_Fails() { var request = new CreatePricingLibraryItemRequest( Title: "Valid Title", Description: null, ServiceCategory: ServiceCategory.General, Unit: null, UnitPrice: null, Keywords: null, Source: new string('X', 101) ); var result = _sut.Validate(request); result.IsValid.Should().BeFalse(); result.Errors.Should().Contain(e => e.PropertyName == "Source"); } [Fact(DisplayName = "PR5: Minimal valid request (title + category only) passes")] public void MinimalValidRequest_Passes() { var request = new CreatePricingLibraryItemRequest( Title: "Basic Item", Description: null, ServiceCategory: ServiceCategory.Other, Unit: null, UnitPrice: null, Keywords: null, Source: null ); var result = _sut.Validate(request); result.IsValid.Should().BeTrue(); } }