diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs new file mode 100644 index 0000000..a0473b3 --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs @@ -0,0 +1,74 @@ +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public static class ErrorCodes +{ + public const string UnableToParseFile = "NBSSAPPT001"; + public const string MissingHeader = "NBSSAPPT002"; + public const string MissingExtractId = "NBSSAPPT003"; + public const string InvalidExtractId = "NBSSAPPT004"; + public const string MissingRecordCount = "NBSSAPPT005"; + public const string InvalidRecordCount = "NBSSAPPT006"; + public const string MissingTrailer = "NBSSAPPT007"; + public const string InconsistentExtractId = "NBSSAPPT008"; + public const string InconsistentRecordCount = "NBSSAPPT009"; + public const string MissingFieldHeadings = "NBSSAPPT010"; + public const string UnexpectedRecordCount = "NBSSAPPT011"; + public const string MissingSequence = "NBSSAPPT012"; + public const string InvalidSequence = "NBSSAPPT013"; + public const string MissingBso = "NBSSAPPT014"; + public const string InvalidBso = "NBSSAPPT015"; + public const string MissingAction = "NBSSAPPT016"; + public const string InvalidAction = "NBSSAPPT017"; + public const string MissingClinicCode = "NBSSAPPT018"; + public const string InvalidClinicCode = "NBSSAPPT019"; + public const string MissingHoldingClinic = "NBSSAPPT020"; + public const string InvalidHoldingClinic = "NBSSAPPT021"; + public const string MissingStatus = "NBSSAPPT022"; + public const string InvalidStatus = "NBSSAPPT023"; + public const string MissingAttendedNotScr = "NBSSAPPT024"; + public const string InvalidAttendedNotScr = "NBSSAPPT025"; + public const string MissingAppointmentId = "NBSSAPPT026"; + public const string InvalidAppointmentId = "NBSSAPPT027"; + public const string MissingNhsNum = "NBSSAPPT028"; + public const string InvalidNhsNum = "NBSSAPPT029"; + public const string InvalidNhsNumCheckDigit = "NBSSAPPT030"; + public const string MissingEpisodeType = "NBSSAPPT031"; + public const string InvalidEpisodeType = "NBSSAPPT032"; + public const string MissingEpisodeStart = "NBSSAPPT033"; + public const string InvalidEpisodeStart = "NBSSAPPT034"; + public const string MissingBatchId = "NBSSAPPT035"; + public const string InvalidBatchId = "NBSSAPPT036"; + public const string MissingScreenOrAsses = "NBSSAPPT037"; + public const string InvalidScreenOrAsses = "NBSSAPPT038"; + public const string MissingScreenApptNum = "NBSSAPPT039"; + public const string InvalidScreenApptNum = "NBSSAPPT040"; + public const string MissingBookedBy = "NBSSAPPT041"; + public const string InvalidBookedBy = "NBSSAPPT042"; + public const string MissingCancelledBy = "NBSSAPPT043"; + public const string InvalidCancelledBy = "NBSSAPPT044"; + public const string MissingApptDate = "NBSSAPPT045"; + public const string InvalidApptDate = "NBSSAPPT046"; + public const string MissingApptTime = "NBSSAPPT047"; + public const string InvalidApptTime = "NBSSAPPT048"; + public const string MissingLocation = "NBSSAPPT049"; + public const string InvalidLocation = "NBSSAPPT050"; + public const string MissingClinicName = "NBSSAPPT051"; + public const string InvalidClinicName = "NBSSAPPT052"; + public const string MissingClinicNameLet = "NBSSAPPT053"; + public const string InvalidClinicNameLet = "NBSSAPPT054"; + public const string MissingClinicAddress1 = "NBSSAPPT055"; + public const string InvalidClinicAddress1 = "NBSSAPPT056"; + public const string MissingClinicAddress2 = "NBSSAPPT057"; + public const string InvalidClinicAddress2 = "NBSSAPPT058"; + public const string MissingClinicAddress3 = "NBSSAPPT059"; + public const string InvalidClinicAddress3 = "NBSSAPPT060"; + public const string MissingClinicAddress4 = "NBSSAPPT061"; + public const string InvalidClinicAddress4 = "NBSSAPPT062"; + public const string MissingClinicAddress5 = "NBSSAPPT063"; + public const string InvalidClinicAddress5 = "NBSSAPPT064"; + public const string MissingPostcode = "NBSSAPPT065"; + public const string InvalidPostcode = "NBSSAPPT066"; + public const string MissingActionTimestamp = "NBSSAPPT067"; + public const string InvalidActionTimestamp = "NBSSAPPT068"; + public const string UnknownRecordTypeIdentifier = "NBSSAPPT069"; +} diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs new file mode 100644 index 0000000..b4ae58f --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs @@ -0,0 +1,44 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public class MaxLengthValidator( + string fieldName, + int maxLength, + string errorCodeMissing, + string errorCodeTooLong, + bool allowEmpty = false) + : IRecordValidator +{ + public IEnumerable Validate(FileDataRecord fileDataRecord) + { + var value = fileDataRecord[fieldName]; + + if (value == null || (!allowEmpty && string.IsNullOrWhiteSpace(value))) + { + var error = $"{fieldName} is missing{(allowEmpty ? "" : " or empty")}"; + + yield return new ValidationError + { + RowNumber = fileDataRecord.RowNumber, + Field = fieldName, + Error = error, + Code = errorCodeMissing, + }; + yield break; + } + + if (value.Length > maxLength) + { + var error = $"{fieldName} exceeds maximum length of {maxLength}"; + + yield return new ValidationError + { + RowNumber = fileDataRecord.RowNumber, + Field = fieldName, + Error = error, + Code = errorCodeTooLong, + }; + } + } +} diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs new file mode 100644 index 0000000..0bf7834 --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs @@ -0,0 +1,40 @@ +using System.Text.RegularExpressions; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public class RegexValidator( + string fieldName, + Regex pattern, + string errorCodeMissing, + string errorCodeInvalidFormat) + : IRecordValidator +{ + public IEnumerable Validate(FileDataRecord fileDataRecord) + { + var value = fileDataRecord[fieldName]; + + if (value == null) + { + yield return new ValidationError + { + RowNumber = fileDataRecord.RowNumber, + Field = fieldName, + Error = $"{fieldName} is missing", + Code = errorCodeMissing, + }; + yield break; + } + + if (!pattern.IsMatch(value)) + { + yield return new ValidationError + { + RowNumber = fileDataRecord.RowNumber, + Field = fieldName, + Error = $"{fieldName} is in an invalid format", + Code = errorCodeInvalidFormat, + }; + } + } +} diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs new file mode 100644 index 0000000..5096d19 --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -0,0 +1,91 @@ +using System.Text.RegularExpressions; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public static partial class ValidatorRegistry +{ + public static IEnumerable GetAllRecordValidators() + { + return + [ + new RegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, + ErrorCodes.InvalidSequence), + new MaxLengthValidator("BSO", 3, ErrorCodes.MissingBso, + ErrorCodes.InvalidBso), + new RegexValidator("Action", ActionRegex(), ErrorCodes.MissingAction, + ErrorCodes.InvalidAction), + new MaxLengthValidator("Clinic Code", 5, ErrorCodes.MissingClinicCode, + ErrorCodes.InvalidClinicCode), + new RegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, + ErrorCodes.InvalidHoldingClinic), + new RegexValidator("Status", StatusRegex(), ErrorCodes.MissingStatus, + ErrorCodes.InvalidStatus), + new RegexValidator("Attended Not Scr", YesNoBlankRegex(), ErrorCodes.MissingAttendedNotScr, + ErrorCodes.InvalidAttendedNotScr), + new MaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, + ErrorCodes.InvalidAppointmentId), + new RegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType, + ErrorCodes.InvalidEpisodeType), + new MaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId, + ErrorCodes.InvalidBatchId), + new RegexValidator("Screen or Asses", ScreenOrAssesRegex(), ErrorCodes.MissingScreenOrAsses, + ErrorCodes.InvalidScreenOrAsses), + new RegexValidator("Screen Appt num", ScreenApptNumRegex(), ErrorCodes.MissingScreenApptNum, + ErrorCodes.InvalidScreenApptNum), + new RegexValidator("Booked By", BookedByRegex(), ErrorCodes.MissingBookedBy, + ErrorCodes.InvalidBookedBy), + new RegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy, + ErrorCodes.InvalidCancelledBy), + new MaxLengthValidator("Location", 5, ErrorCodes.MissingLocation, + ErrorCodes.InvalidLocation), + new MaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, + ErrorCodes.InvalidClinicName, true), + new MaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, + ErrorCodes.InvalidClinicNameLet, true), + new MaxLengthValidator("Clinic Address 1", 30, ErrorCodes.MissingClinicAddress1, + ErrorCodes.InvalidClinicAddress1, true), + new MaxLengthValidator("Clinic Address 2", 30, ErrorCodes.MissingClinicAddress2, + ErrorCodes.InvalidClinicAddress2, true), + new MaxLengthValidator("Clinic Address 3", 30, ErrorCodes.MissingClinicAddress3, + ErrorCodes.InvalidClinicAddress3, true), + new MaxLengthValidator("Clinic Address 4", 30, ErrorCodes.MissingClinicAddress4, + ErrorCodes.InvalidClinicAddress4, true), + new MaxLengthValidator("Clinic Address 5", 30, ErrorCodes.MissingClinicAddress5, + ErrorCodes.InvalidClinicAddress5, true), + new MaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, + ErrorCodes.InvalidPostcode, true), + ]; + } + + public static IEnumerable GetAllFileValidators() + { + return []; + } + + [GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)] + private static partial Regex ActionRegex(); + + [GeneratedRegex(@"^[CH]$", RegexOptions.Compiled)] + private static partial Regex BookedByRegex(); + + [GeneratedRegex(@"^$|^[ CH]$", RegexOptions.Compiled)] + private static partial Regex CancelledByRegex(); + + [GeneratedRegex(@"^[FGHNRST]$", RegexOptions.Compiled)] + private static partial Regex EpisodeTypeRegex(); + + [GeneratedRegex(@"^[AS]$", RegexOptions.Compiled)] + private static partial Regex ScreenOrAssesRegex(); + + [GeneratedRegex(@"^$|^[1-9]$", RegexOptions.Compiled)] + private static partial Regex ScreenApptNumRegex(); + + [GeneratedRegex(@"^(?!000000)\d{6}$", RegexOptions.Compiled)] + private static partial Regex SequenceRegex(); + + [GeneratedRegex(@"^[ABCD]$", RegexOptions.Compiled)] + private static partial Regex StatusRegex(); + + [GeneratedRegex(@"^$|^[ YN]$", RegexOptions.Compiled)] + private static partial Regex YesNoBlankRegex(); +} diff --git a/src/ServiceLayer.sln.DotSettings b/src/ServiceLayer.sln.DotSettings new file mode 100644 index 0000000..a78f4e1 --- /dev/null +++ b/src/ServiceLayer.sln.DotSettings @@ -0,0 +1,2 @@ + + True diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs new file mode 100644 index 0000000..9e4f1cc --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs @@ -0,0 +1,64 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ActionValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ActionMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Action")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Action", + "Action is missing", + ErrorCodes.MissingAction + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("D")] // invalid character + [InlineData("$")] // invalid character + [InlineData("b")] // lowercase + [InlineData("")] // Blank + [InlineData(" ")] // Whitespace + [InlineData("BC")] // Too many characters + [InlineData("BCU")] // Too many characters + public void Validate_ActionInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Action"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Action", + "Action is in an invalid format", + ErrorCodes.InvalidAction + ); + } + + [Theory] + [InlineData("B")] + [InlineData("C")] + [InlineData("U")] + public void Validate_ActionValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Action"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs new file mode 100644 index 0000000..2fe1ce8 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs @@ -0,0 +1,76 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class AppointmentIdValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_AppointmentIdMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Appointment ID")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Appointment ID", + "Appointment ID is missing or empty", + ErrorCodes.MissingAppointmentId + ); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Validate_AppointmentIdBlank_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Appointment ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Appointment ID", + "Appointment ID is missing or empty", + ErrorCodes.MissingAppointmentId + ); + } + + [Theory] + [InlineData("1234567890123456789012345678")] // 28 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_AppointmentIdTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Appointment ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Appointment ID", + "Appointment ID exceeds maximum length of 27", + ErrorCodes.InvalidAppointmentId + ); + } + + [Theory] + [InlineData("AS003-67240-RA1-DN-T1315-1")] + [InlineData("AS003-67240-RA1-DN-T1045-01")] + public void Validate_AppointmentIdValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Appointment ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs new file mode 100644 index 0000000..2e7e643 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs @@ -0,0 +1,61 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class AttendedNotScrValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_AttendedNotScrMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Attended Not Scr")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Attended Not Scr", + "Attended Not Scr is missing", + ErrorCodes.MissingAttendedNotScr + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("D")] // invalid character + [InlineData(" ")] // Too many characters + [InlineData("YN")] // Too many characters + public void Validate_AttendedNotScrInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Attended Not Scr"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Attended Not Scr", + "Attended Not Scr is in an invalid format", + ErrorCodes.InvalidAttendedNotScr + ); + } + + [Theory] + [InlineData("Y")] + [InlineData("N")] + [InlineData(" ")] + [InlineData("")] + public void Validate_AttendedNotScrValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Attended Not Scr"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BatchIdValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BatchIdValidatorTests.cs new file mode 100644 index 0000000..1329f24 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BatchIdValidatorTests.cs @@ -0,0 +1,76 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class BatchIdValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_BatchIdMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Batch ID")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Batch ID", + "Batch ID is missing or empty", + ErrorCodes.MissingBatchId + ); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Validate_BatchIdBlank_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Batch ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Batch ID", + "Batch ID is missing or empty", + ErrorCodes.MissingBatchId + ); + } + + [Theory] + [InlineData("1234567890")] // 10 characters + [InlineData("12345678901")] // 11 characters + public void Validate_BatchIdTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Batch ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Batch ID", + "Batch ID exceeds maximum length of 9", + ErrorCodes.InvalidBatchId + ); + } + + [Theory] + [InlineData("KMKT00001")] + [InlineData("KMKT001")] + public void Validate_BatchIdValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Batch ID"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BookedByValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BookedByValidatorTests.cs new file mode 100644 index 0000000..91892ab --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BookedByValidatorTests.cs @@ -0,0 +1,62 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class BookedByValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_BookedByMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Booked By")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Booked By", + "Booked By is missing", + ErrorCodes.MissingBookedBy + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("B")] // invalid character + [InlineData("$")] // invalid character + [InlineData("c")] // lowercase + [InlineData("")] // Blank + [InlineData(" ")] // Whitespace + [InlineData("CH")] // Too many characters + public void Validate_BookedByInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Booked By"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Booked By", + "Booked By is in an invalid format", + ErrorCodes.InvalidBookedBy + ); + } + + [Theory] + [InlineData("C")] + [InlineData("H")] + public void Validate_BookedByValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Booked By"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs new file mode 100644 index 0000000..38ef49d --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs @@ -0,0 +1,76 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class BsoValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_BSOMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("BSO")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "BSO", + "BSO is missing or empty", + ErrorCodes.MissingBso + ); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Validate_BsoBlank_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["BSO"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "BSO", + "BSO is missing or empty", + ErrorCodes.MissingBso + ); + } + + [Theory] + [InlineData("ABCD")] // 4 characters + [InlineData("ABCDE")] // 5 characters + public void Validate_BsoTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["BSO"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "BSO", + "BSO exceeds maximum length of 3", + ErrorCodes.InvalidBso + ); + } + + [Theory] + [InlineData("ABC")] + [InlineData("RD5")] + public void Validate_BSOValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["BSO"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/CancelledByValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/CancelledByValidatorTests.cs new file mode 100644 index 0000000..2887e85 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/CancelledByValidatorTests.cs @@ -0,0 +1,61 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class CancelledByValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_CancelledByMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Cancelled By")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Cancelled By", + "Cancelled By is missing", + ErrorCodes.MissingCancelledBy + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("D")] // invalid character + [InlineData(" ")] // Too many characters + [InlineData("CH")] // Too many characters + public void Validate_CancelledByInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Cancelled By"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Cancelled By", + "Cancelled By is in an invalid format", + ErrorCodes.InvalidCancelledBy + ); + } + + [Theory] + [InlineData("C")] + [InlineData("H")] + [InlineData(" ")] + [InlineData("")] + public void Validate_CancelledByValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Cancelled By"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs new file mode 100644 index 0000000..87405a9 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicAddress1ValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicAddress1Missing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Address 1")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 1", + "Clinic Address 1 is missing", + ErrorCodes.MissingClinicAddress1 + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_ClinicAddress1TooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 1"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 1", + "Clinic Address 1 exceeds maximum length of 30", + ErrorCodes.InvalidClinicAddress1 + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [InlineData("Milton Keynes")] + public void Validate_ClinicAddress1Valid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 1"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs new file mode 100644 index 0000000..00e5ec7 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicAddress2ValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicAddress2Missing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Address 2")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 2", + "Clinic Address 2 is missing", + ErrorCodes.MissingClinicAddress2 + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_ClinicAddress2TooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 2"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 2", + "Clinic Address 2 exceeds maximum length of 30", + ErrorCodes.InvalidClinicAddress2 + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [InlineData("Milton Keynes")] + public void Validate_ClinicAddress2Valid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 2"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs new file mode 100644 index 0000000..a4a7972 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicAddress3ValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicAddress3Missing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Address 3")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 3", + "Clinic Address 3 is missing", + ErrorCodes.MissingClinicAddress3 + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_ClinicAddress3TooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 3"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 3", + "Clinic Address 3 exceeds maximum length of 30", + ErrorCodes.InvalidClinicAddress3 + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [InlineData("Milton Keynes")] + public void Validate_ClinicAddress3Valid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 3"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs new file mode 100644 index 0000000..43e0d5d --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicAddress4ValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicAddress4Missing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Address 4")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 4", + "Clinic Address 4 is missing", + ErrorCodes.MissingClinicAddress4 + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_ClinicAddress4TooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 4"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 4", + "Clinic Address 4 exceeds maximum length of 30", + ErrorCodes.InvalidClinicAddress4 + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [InlineData("Milton Keynes")] + public void Validate_ClinicAddress4Valid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 4"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs new file mode 100644 index 0000000..87c0063 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicAddress5ValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicAddress5Missing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Address 5")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 5", + "Clinic Address 5 is missing", + ErrorCodes.MissingClinicAddress5 + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 characters + public void Validate_ClinicAddress5TooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 5"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Address 5", + "Clinic Address 5 exceeds maximum length of 30", + ErrorCodes.InvalidClinicAddress5 + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [InlineData("Milton Keynes")] + public void Validate_ClinicAddress5Valid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Address 5"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs new file mode 100644 index 0000000..d1f31cc --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs @@ -0,0 +1,77 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicCodeValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicCodeMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Code")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Code", + "Clinic Code is missing or empty", + ErrorCodes.MissingClinicCode + ); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Validate_ClinicCodeBlank_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Code"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Code", + "Clinic Code is missing or empty", + ErrorCodes.MissingClinicCode + ); + } + + [Theory] + [InlineData("BS0004")] // 6 characters + [InlineData("BSO0007")] // 7 characters + public void Validate_ClinicCodeTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Code"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Code", + "Clinic Code exceeds maximum length of 5", + ErrorCodes.InvalidClinicCode + ); + } + + [Theory] + [InlineData("BS003")] + [InlineData("KI011")] + [InlineData("E17")] + public void Validate_ClinicCodeValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Code"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs new file mode 100644 index 0000000..6d1600e --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicNameLetValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicNameLetMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Name (Let)")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Name (Let)", + "Clinic Name (Let) is missing", + ErrorCodes.MissingClinicNameLet + ); + } + + [Theory] + [InlineData("123456789012345678901234567890123456789012345678901")] // 51 characters + [InlineData("123456789012345678901234567890123456789012345678901234567890")] // 60 characters + public void Validate_ClinicNameLetTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Name (Let)"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Name (Let)", + "Clinic Name (Let) exceeds maximum length of 50", + ErrorCodes.InvalidClinicNameLet + ); + } + + [Theory] + [InlineData("")] + [InlineData("12345678901234567890123456789012345678901234567890")] + [InlineData("Breast Care Unit")] + public void Validate_ClinicNameLetValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Name (Let)"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs new file mode 100644 index 0000000..b7f14f0 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ClinicNameValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ClinicNameMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Clinic Name")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Name", + "Clinic Name is missing", + ErrorCodes.MissingClinicName + ); + } + + [Theory] + [InlineData("12345678901234567890123456789012345678901")] // 41 characters + [InlineData("12345678901234567890123456789012345678901234567890")] // 50 characters + public void Validate_ClinicNameTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Name"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Clinic Name", + "Clinic Name exceeds maximum length of 40", + ErrorCodes.InvalidClinicName + ); + } + + [Theory] + [InlineData("")] + [InlineData("1234567890123456789012345678901234567890")] + [InlineData("Breast Care Unit")] + public void Validate_ClinicNameValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Clinic Name"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/EpisodeTypeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/EpisodeTypeValidatorTests.cs new file mode 100644 index 0000000..63a7fab --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/EpisodeTypeValidatorTests.cs @@ -0,0 +1,68 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class EpisodeTypeValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_EpisodeTypeMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Episode Type")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Episode Type", + "Episode Type is missing", + ErrorCodes.MissingEpisodeType + ); + } + + [Theory] + [InlineData("E")] // invalid character + [InlineData("I")] // invalid character + [InlineData("$")] // invalid character + [InlineData("f")] // lowercase + [InlineData("")] // Blank + [InlineData(" ")] // Whitespace + [InlineData("FG")] // Too many characters + [InlineData("RST")] // Too many characters + public void Validate_EpisoderTypeInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Episode Type"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Episode Type", + "Episode Type is in an invalid format", + ErrorCodes.InvalidEpisodeType + ); + } + + [Theory] + [InlineData("F")] + [InlineData("G")] + [InlineData("H")] + [InlineData("N")] + [InlineData("R")] + [InlineData("S")] + [InlineData("T")] + public void Validate_EpisodeTypeValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Episode Type"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodesTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodesTests.cs new file mode 100644 index 0000000..dc56fae --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodesTests.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ErrorCodesTests +{ + [Fact] + public void AllErrorCodes_ShouldBeUnique() + { + var duplicates = GetErrorCodes() + .GroupBy(kvp => kvp.Value) + .Where(g => g.Count() > 1) + .ToList(); + + Assert.True(duplicates.Count == 0, + $"Duplicate error code values found: {string.Join(", ", duplicates.Select(g => g.Key))}"); + } + + [Fact] + public void AllErrorCodes_ShouldMatchExpectedFormat() + { + foreach (var kvp in GetErrorCodes()) + { + Assert.Matches(@"^NBSSAPPT\d{3}$", kvp.Value); + } + } + + private static Dictionary GetErrorCodes() + { + return typeof(ErrorCodes) + .GetFields(BindingFlags.Public | BindingFlags.Static) + .ToDictionary( + f => f.Name, + f => f.GetValue(null)?.ToString() ?? string.Empty); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs new file mode 100644 index 0000000..e9a70ef --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs @@ -0,0 +1,61 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class HoldingClinicValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_HoldingClinicMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Holding Clinic")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Holding Clinic", + "Holding Clinic is missing", + ErrorCodes.MissingHoldingClinic + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("D")] // invalid character + [InlineData(" ")] // Too many characters + [InlineData("YN")] // Too many characters + public void Validate_HoldingClinicInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Holding Clinic"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Holding Clinic", + "Holding Clinic is in an invalid format", + ErrorCodes.InvalidHoldingClinic + ); + } + + [Theory] + [InlineData("Y")] + [InlineData("N")] + [InlineData(" ")] + [InlineData("")] + public void Validate_HoldingClinicValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Holding Clinic"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/LocationValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/LocationValidatorTests.cs new file mode 100644 index 0000000..8d7f786 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/LocationValidatorTests.cs @@ -0,0 +1,76 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class LocationValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_LocationMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Location")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Location", + "Location is missing or empty", + ErrorCodes.MissingLocation + ); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Validate_LocationBlank_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Location"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Location", + "Location is missing or empty", + ErrorCodes.MissingLocation + ); + } + + [Theory] + [InlineData("123456")] // 6 characters + [InlineData("1234567890")] // 10 characters + public void Validate_LocationTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Location"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Location", + "Location exceeds maximum length of 5", + ErrorCodes.InvalidLocation + ); + } + + [Theory] + [InlineData("KIN10")] + [InlineData("BU")] + public void Validate_LocationValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Location"] = value); + + // Act + var validationErrors = Validate(file); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidatorTests.cs new file mode 100644 index 0000000..ad95c38 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidatorTests.cs @@ -0,0 +1,113 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class MaxLengthValidatorTests +{ + private const string FieldName = "TestField"; + private const string MissingCode = "ERR100"; + private const string TooLongCode = "ERR101"; + + [Theory] + [InlineData(false, "TestField is missing or empty")] + [InlineData(true, "TestField is missing")] + public void Validate_NullValue_ShouldReturnMissingError(bool allowEmpty, string expectedError) + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 1 + }; + record.Fields.Clear(); + + var validator = new MaxLengthValidator(FieldName, 5, MissingCode, TooLongCode, allowEmpty); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + errors.ShouldContainValidationError(FieldName, expectedError, MissingCode, 1); + } + + [Fact] + public void Validate_EmptyValueDisallowed_ShouldReturnMissingError() + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 2 + }; + record.Fields.Add(FieldName, ""); + + var validator = new MaxLengthValidator(FieldName, 5, MissingCode, TooLongCode); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + errors.ShouldContainValidationError(FieldName, "TestField is missing or empty", MissingCode, 2); + } + + [Fact] + public void Validate_EmptyValueAllowed_ShouldReturnNoErrors() + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 3 + }; + record.Fields.Add(FieldName, ""); + + var validator = new MaxLengthValidator(FieldName, 5, MissingCode, TooLongCode, true); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + Assert.Empty(errors); + } + + [Theory] + [InlineData(5, "123456")] + [InlineData(7, "12345678")] + public void Validate_ValueExceedingMaxLength_ShouldReturnTooLongError(int maxLength, string tooLongValue) + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 4 + }; + record.Fields.Add(FieldName, tooLongValue); + + var validator = new MaxLengthValidator(FieldName, maxLength, MissingCode, TooLongCode); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + errors.ShouldContainValidationError(FieldName, $"TestField exceeds maximum length of {maxLength}", TooLongCode, 4); + } + + [Theory] + [InlineData(5, "123")] + [InlineData(6, "123456")] + [InlineData(7, "123456")] + public void Validate_ValueWithinMaxLength_ShouldReturnNoErrors(int maxLength, string validValue) + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 5 + }; + record.Fields.Add(FieldName, validValue); + + var validator = new MaxLengthValidator(FieldName, maxLength, MissingCode, TooLongCode); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + Assert.Empty(errors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs new file mode 100644 index 0000000..0bb81d9 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs @@ -0,0 +1,58 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class PostcodeValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_PostcodeMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Postcode")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Postcode", + "Postcode is missing", + ErrorCodes.MissingPostcode + ); + } + + [Theory] + [InlineData("LS25 6LGG")] // 9 characters + [InlineData("YO31 88RQY")] // 10 characters + public void Validate_PostcodeTooLong_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Postcode"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Postcode", + "Postcode exceeds maximum length of 8", + ErrorCodes.InvalidPostcode + ); + } + + [Theory] + [InlineData("")] + [InlineData("S81 8SH")] + [InlineData("YO31 8RQ")] + public void Validate_PostcodeValid_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Postcode"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs new file mode 100644 index 0000000..d7c81b8 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs @@ -0,0 +1,71 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; +using System.Text.RegularExpressions; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class RegexValidatorTests +{ + private const string FieldName = "TestField"; + private const string MissingCode = "ERR001"; + private const string InvalidFormatCode = "ERR002"; + private readonly Regex _pattern = new(@"^[A-Z]{2}\d{2}$", RegexOptions.Compiled); + + [Fact] + public void Validate_NullValue_ShouldReturnMissingError() + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 1 + }; + record.Fields.Clear(); + + var validator = new RegexValidator(FieldName, _pattern, MissingCode, InvalidFormatCode); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + errors.ShouldContainValidationError(FieldName, $"{FieldName} is missing", MissingCode, 1); + } + + [Theory] + [InlineData("")] + [InlineData("invalid")] + public void Validate_ValueNotMatchingPattern_ShouldReturnInvalidFormatError(string invalidValue) + { + // Arrange + var record = new FileDataRecord + { + RowNumber = 2 + }; + record.Fields.Add(FieldName, invalidValue); + + var validator = new RegexValidator(FieldName, _pattern, MissingCode, InvalidFormatCode); + + // Act + var errors = validator.Validate(record).ToList(); + + // Assert + errors.ShouldContainValidationError(FieldName, $"{FieldName} is in an invalid format", InvalidFormatCode, 2); + } + + [Theory] + [InlineData("AB12")] + [InlineData("CD34")] + public void Validate_ValueMatchingPattern_ShouldReturnNoErrors(string validValue) + { + var record = new FileDataRecord + { + RowNumber = 3 + }; + record.Fields.Add(FieldName, validValue); + + var validator = new RegexValidator(FieldName, _pattern, MissingCode, InvalidFormatCode); + + var errors = validator.Validate(record).ToList(); + + Assert.Empty(errors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenApptNumValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenApptNumValidatorTests.cs new file mode 100644 index 0000000..6d2796c --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenApptNumValidatorTests.cs @@ -0,0 +1,68 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ScreenApptNumValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ScreenApptNumMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Screen Appt num")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Screen Appt num", + "Screen Appt num is missing", + ErrorCodes.MissingScreenApptNum + ); + } + + [Theory] + [InlineData("A")] // invalid character + [InlineData("a")] // lowercase + [InlineData(" ")] // Whitespace + [InlineData("12")] // Too many characters + [InlineData("0")] // Zero + public void Validate_ScreenApptNumInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Screen Appt num"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Screen Appt num", + "Screen Appt num is in an invalid format", + ErrorCodes.InvalidScreenApptNum + ); + } + + [Theory] + [InlineData("1")] + [InlineData("2")] + [InlineData("3")] + [InlineData("4")] + [InlineData("5")] + [InlineData("6")] + [InlineData("7")] + [InlineData("8")] + [InlineData("9")] + [InlineData("")] + public void Validate_ScreenApptNumValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Screen Appt num"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenOrAssesValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenOrAssesValidatorTests.cs new file mode 100644 index 0000000..41fe4a7 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenOrAssesValidatorTests.cs @@ -0,0 +1,62 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class ScreenOrAssesValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_ScreenOrAssesMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Screen or Asses")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Screen or Asses", + "Screen or Asses is missing", + ErrorCodes.MissingScreenOrAsses + ); + } + + [Theory] + [InlineData("B")] // invalid character + [InlineData("C")] // invalid character + [InlineData("$")] // invalid character + [InlineData("a")] // lowercase + [InlineData("")] // Blank + [InlineData(" ")] // Whitespace + [InlineData("AS")] // Too many characters + public void Validate_ScreenOrAssesInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Screen or Asses"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Screen or Asses", + "Screen or Asses is in an invalid format", + ErrorCodes.InvalidScreenOrAsses + ); + } + + [Theory] + [InlineData("A")] + [InlineData("S")] + public void Validate_ScreenOrAssesValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Screen or Asses"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs new file mode 100644 index 0000000..a6e9304 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs @@ -0,0 +1,60 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class SequenceValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_SequenceMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Sequence")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Sequence", + "Sequence is missing", + ErrorCodes.MissingSequence + ); + } + + [Theory] + [InlineData("1")] // Missing leading zeroes + [InlineData("000000")] // Zero is invalid + [InlineData("1000000")] // Too large + [InlineData("")] // Blank + [InlineData("asdf")] // NaN + public void Validate_SequenceInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Sequence"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Sequence", + "Sequence is in an invalid format", + ErrorCodes.InvalidSequence + ); + } + + [Theory] + [InlineData("000001")] + [InlineData("999999")] + public void Validate_SequenceValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Sequence"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs new file mode 100644 index 0000000..52e959a --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs @@ -0,0 +1,65 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class StatusValidatorTests : ValidationTestBase +{ + [Fact] + public void Validate_StatusMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Status")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldContainValidationError( + "Status", + "Status is missing", + ErrorCodes.MissingStatus + ); + } + + [Theory] + [InlineData("E")] // invalid character + [InlineData("F")] // invalid character + [InlineData("$")] // invalid character + [InlineData("b")] // lowercase + [InlineData("")] // Blank + [InlineData(" ")] // Whitespace + [InlineData("AB")] // Too many characters + [InlineData("BCD")] // Too many characters + public void Validate_StatusInvalidFormat_ReturnsValidationError(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Status"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + validationErrors.ShouldContainValidationError( + "Status", + "Status is in an invalid format", + ErrorCodes.InvalidStatus + ); + } + + [Theory] + [InlineData("A")] + [InlineData("B")] + [InlineData("C")] + [InlineData("D")] + public void Validate_StatusValidFormat_NoValidationErrorsReturned(string value) + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields["Status"] = value); + + // Act + var validationErrors = Validate(file).ToList(); + + // Assert + Assert.Empty(validationErrors); + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs new file mode 100644 index 0000000..1f60bc0 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs @@ -0,0 +1,22 @@ +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public static class ValidationErrorAssertions +{ + public static void ShouldContainValidationError( + this IEnumerable errors, + string expectedField, + string expectedError, + string expectedCode, + int? expectedRowNumber = null) + { + var error = errors.FirstOrDefault(e => + e.Field == expectedField && + e.Error == expectedError && + e.Code == expectedCode && + (expectedRowNumber == null || e.RowNumber == expectedRowNumber) + ); + + Assert.True(error != null, $"Expected validation error with Field: '{expectedField}', Error: '{expectedError}', Code: '{expectedCode}'{(expectedRowNumber != null ? $", RowNumber: {expectedRowNumber}" : "")}, but none was found."); + + } +} diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationTestBase.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationTestBase.cs new file mode 100644 index 0000000..f9c4385 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationTestBase.cs @@ -0,0 +1,31 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public abstract class ValidationTestBase +{ + protected readonly ValidationRunner SystemUnderTest; + + protected ValidationTestBase() + { + var recordValidators = ValidatorRegistry.GetAllRecordValidators(); + var fileValidators = ValidatorRegistry.GetAllFileValidators(); + + SystemUnderTest = new ValidationRunner(fileValidators, recordValidators); + } + + protected static ParsedFile ValidParsedFile => + TestDataBuilder.BuildValidParsedFile(); + + protected static ParsedFile ParsedFileWithModifiedRecord(Action mutate) + { + var file = TestDataBuilder.BuildValidParsedFile(); + mutate(file.DataRecords[0]); // Modify the first record + return file; + } + + protected List Validate(ParsedFile file){ + return SystemUnderTest.Validate(file).ToList(); + } +}