From 18b482f2d53ce69569df6b182bcc73c914393326 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Mon, 19 May 2025 15:11:21 +0100 Subject: [PATCH 01/14] fix: split FileControlRecord into separate Header/Trailer classes --- .../{FileControlRecord.cs => FileHeaderRecord.cs} | 2 +- .../Models/FileTrailerRecord.cs | 14 ++++++++++++++ .../NbssAppointmentEvents/Models/ParsedFile.cs | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) rename src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/{FileControlRecord.cs => FileHeaderRecord.cs} (91%) create mode 100644 src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileTrailerRecord.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileControlRecord.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileHeaderRecord.cs similarity index 91% rename from src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileControlRecord.cs rename to src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileHeaderRecord.cs index 1021297..dfb8a01 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileControlRecord.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileHeaderRecord.cs @@ -1,6 +1,6 @@ namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; -public class FileControlRecord +public class FileHeaderRecord { public string? RecordTypeIdentifier { get; set; } diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileTrailerRecord.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileTrailerRecord.cs new file mode 100644 index 0000000..b46d00b --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/FileTrailerRecord.cs @@ -0,0 +1,14 @@ +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +public class FileTrailerRecord +{ + public string? RecordTypeIdentifier { get; set; } + + public string? ExtractId { get; set; } + + public string? TransferEndDate { get; set; } + + public string? TransferEndTime { get; set; } + + public string? RecordCount { get; set; } +} diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/ParsedFile.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/ParsedFile.cs index de398ed..0820905 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/ParsedFile.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Models/ParsedFile.cs @@ -2,8 +2,8 @@ namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; public class ParsedFile { - public FileControlRecord? FileHeader { get; set; } - public FileControlRecord? FileTrailer { get; set; } + public FileHeaderRecord? FileHeader { get; set; } + public FileTrailerRecord? FileTrailer { get; set; } public required List ColumnHeadings { get; set; } = []; public required List DataRecords { get; set; } = []; } From ca38cd2cf19289f6acb16e81e5349419e67afa4d Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Mon, 19 May 2025 16:04:38 +0100 Subject: [PATCH 02/14] test: add NBSS appointment events test data builder --- .../NbssAppointmentEvents/TestDataBuilder.cs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/TestDataBuilder.cs diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/TestDataBuilder.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/TestDataBuilder.cs new file mode 100644 index 0000000..95f4ffd --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/TestDataBuilder.cs @@ -0,0 +1,104 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents; + +public static class TestDataBuilder +{ + public static ParsedFile BuildValidParsedFile(int numberOfRecords = 3) + { + var header = new FileHeaderRecord + { + RecordTypeIdentifier = "NBSSAPPT_HDR", + ExtractId = "00000107", + TransferStartDate = "20250519", + TransferStartTime = "153806", + RecordCount = numberOfRecords.ToString() + }; + + var trailer = new FileTrailerRecord + { + RecordTypeIdentifier = "NBSSAPPT_END", + ExtractId = "00000107", + TransferEndDate = "20250519", + TransferEndTime = "153957", + RecordCount = numberOfRecords.ToString() + }; + + var columnHeadings = new List + { + "NBSSAPPT_FLDS", "Sequence", "BSO", "Action", "Clinic Code", "Holding Clinic", "Status", + "Attended Not Scr", "Appointment ID", "NHS Num", "Episode Type", "Episode Start", "Batch ID", + "Screen or Asses", "Screen Appt num", "Booked By", "Cancelled By", "Appt Date", "Appt Time", "Location", + "Clinic Name", "Clinic Name (Let)", "Clinic Address 1", "Clinic Address 2", "Clinic Address 3", + "Clinic Address 4", "Clinic Address 5", "Postcode", "Action Timestamp" + }; + + var dataRecords = Enumerable.Range(1, numberOfRecords) + .Select(BuildValidFileDataRecord) + .ToList(); + + return new ParsedFile + { + FileHeader = header, + FileTrailer = trailer, + ColumnHeadings = columnHeadings, + DataRecords = dataRecords + }; + } + + public static FileDataRecord BuildValidFileDataRecord(int rowNumber = 1) + { + return new FileDataRecord + { + RowNumber = rowNumber, + Fields = + { + ["NBSSAPPT_FLDS"] = "NBSSAPPT_DATA", + ["Sequence"] = rowNumber.ToString("D6"), + ["BSO"] = "KMK", + ["Action"] = "B", + ["Clinic Code"] = "BU003", + ["Holding Clinic"] = "N", + ["Status"] = "B", + ["Attended Not Scr"] = "", + ["Appointment ID"] = "BU003-67291-RA1-DN-T1130-1", + ["NHS Num"] = "9111104716", + ["Episode Type"] = "F", + ["Episode Start"] = "20250317", + ["Batch ID"] = "KMK001334", + ["Screen or Asses"] = "S", + ["Screen Appt num"] = "1", + ["Booked By"] = "H", + ["Cancelled By"] = "", + ["Appt Date"] = "20250327", + ["Appt Time"] = "1130", + ["Location"] = "BU", + ["Clinic Name"] = "BREAST CARE UNIT", + ["Clinic Name (Let)"] = "BREAST CARE UNIT", + ["Clinic Address 1"] = "BREAST CARE UNIT", + ["Clinic Address 2"] = "MILTON KEYNES HOSPITAL", + ["Clinic Address 3"] = "STANDING WAY", + ["Clinic Address 4"] = "MILTON KEYNES", + ["Clinic Address 5"] = "MK6 5LD", + ["Postcode"] = "MK6 5LD", + ["Action Timestamp"] = "20250317-132635", + } + }; + } + + public static FileDataRecord BuildFileDataRecordWithField(string fieldName, string? fieldValue, int rowNumber = 1) + { + var fileDataRecord = BuildValidFileDataRecord(rowNumber); + + if (fieldValue == null) + { + fileDataRecord.Fields.Remove(fieldName); + } + else + { + fileDataRecord.Fields[fieldName] = fieldValue; + } + + return fileDataRecord; + } +} From 7eecabf160331eb0122d18f624541e6ac45e24f8 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Tue, 20 May 2025 16:59:18 +0100 Subject: [PATCH 03/14] Nascent validation implementation --- .../Validation/InlineMaxLengthValidator.cs | 39 ++++++++++ .../Validation/InlineRegexValidator.cs | 40 ++++++++++ .../Validation/ValidatorRegistry.cs | 26 +++++++ src/ServiceLayer.sln.DotSettings | 2 + .../Validation/AppointmentIdValidatorTests.cs | 74 +++++++++++++++++++ .../ClinicAddress3ValidatorTests.cs | 56 ++++++++++++++ .../Validation/ErrorCodes.cs | 74 +++++++++++++++++++ .../Validation/SequenceValidatorTests.cs | 58 +++++++++++++++ .../Validation/ValidationErrorAssertions.cs | 25 +++++++ .../Validation/ValidationTestBase.cs | 31 ++++++++ 10 files changed, 425 insertions(+) create mode 100644 src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs create mode 100644 src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs create mode 100644 src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs create mode 100644 src/ServiceLayer.sln.DotSettings create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationTestBase.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs new file mode 100644 index 0000000..8b0d72b --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs @@ -0,0 +1,39 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public class InlineMaxLengthValidator(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/InlineRegexValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs new file mode 100644 index 0000000..4aa03e9 --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs @@ -0,0 +1,40 @@ +using System.Text.RegularExpressions; +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public class InlineRegexValidator( + 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..604afb9 --- /dev/null +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -0,0 +1,26 @@ +using System.Text.RegularExpressions; + +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + +public static partial class ValidatorRegistry +{ + public static IEnumerable GetAllRecordValidators() + { + return + [ + new InlineRegexValidator("Sequence", SequenceRegex(), "NBSSAPPT012", "NBSSAPPT013"), + new InlineMaxLengthValidator("Appointment ID", 27, "NBSSAPPT026", "NBSSAPPT027"), + new InlineMaxLengthValidator("Clinic Address 3", 30, "NBSSAPPT059", "NBSSAPPT060", true) + + + ]; + } + + public static IEnumerable GetAllFileValidators() + { + return []; + } + + [GeneratedRegex(@"^(?!000000)\d{6}$", RegexOptions.Compiled)] + private static partial Regex SequenceRegex(); +} diff --git a/src/ServiceLayer.sln.DotSettings b/src/ServiceLayer.sln.DotSettings new file mode 100644 index 0000000..4275d1a --- /dev/null +++ b/src/ServiceLayer.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file 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..b78fecb --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs @@ -0,0 +1,74 @@ +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public class AppointmentIdValidationTests : ValidationTestBase +{ + [Fact] + public void Validate_AppointmentIdMissing_ReturnsValidationError() + { + // Arrange + var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Appointment ID")); + + // Act + var validationErrors = Validate(file); + + // Assert + validationErrors.ShouldBeSingleValidationError( + "Appointment ID", + "Appointment ID is missing or empty", + "NBSSAPPT026" + ); + } + + [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.ShouldBeSingleValidationError( + "Appointment ID", + "Appointment ID is missing or empty", + "NBSSAPPT026" + ); + } + + [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.ShouldBeSingleValidationError( + "Appointment ID", + "Appointment ID exceeds maximum length of 27", + "NBSSAPPT027" + ); + } + + [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/ClinicAddress3ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs new file mode 100644 index 0000000..c157369 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs @@ -0,0 +1,56 @@ +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.ShouldBeSingleValidationError( + "Clinic Address 3", + "Clinic Address 3 is missing", + "NBSSAPPT059" + ); + } + + [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.ShouldBeSingleValidationError( + "Clinic Address 3", + "Clinic Address 3 exceeds maximum length of 30", + "NBSSAPPT060" + ); + } + + [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/ErrorCodes.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs new file mode 100644 index 0000000..4b75329 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs @@ -0,0 +1,74 @@ +namespace ServiceLayer.Mesh.Tests.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 MissingHoldingClinicCode = "NBSSAPPT020"; + public const string InvalidHoldingClinicCode = "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/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..3153fdf --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs @@ -0,0 +1,58 @@ +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.ShouldBeSingleValidationError( + "Sequence", + "Sequence is missing", + "NBSSAPPT012" + ); + } + + [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.ShouldBeSingleValidationError( + "Sequence", + "Sequence is in an invalid format", + "NBSSAPPT013" + ); + } + + [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/ValidationErrorAssertions.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs new file mode 100644 index 0000000..abfef8d --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs @@ -0,0 +1,25 @@ +namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; + +public static class ValidationErrorAssertions +{ + public static ValidationError ShouldBeSingleValidationError( + this IEnumerable errors, + string expectedField, + string expectedError, + string expectedCode, + int? expectedRowNumber = null) + { + var error = Assert.Single(errors); + Assert.Equal(expectedField, error.Field); + Assert.Equal(expectedError, error.Error); + + Assert.Equal(expectedCode, error.Code); + + if (expectedRowNumber != null) + { + Assert.Equal(expectedRowNumber, error.RowNumber); + } + + return error; + } +} 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(); + } +} From 2561898ba63a595d94b6cb8accff52e7654122ea Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Wed, 21 May 2025 13:54:59 +0100 Subject: [PATCH 04/14] wip --- .../Validation/ErrorCodes.cs | 2 +- .../Validation/ValidatorRegistry.cs | 11 +++- .../Validation/AppointmentIdValidatorTests.cs | 8 ++- .../ClinicAddress1ValidatorTests.cs | 58 +++++++++++++++++++ .../ClinicAddress2ValidatorTests.cs | 58 +++++++++++++++++++ .../ClinicAddress3ValidatorTests.cs | 6 +- .../ClinicAddress4ValidatorTests.cs | 58 +++++++++++++++++++ .../ClinicAddress5ValidatorTests.cs | 58 +++++++++++++++++++ .../Validation/ClinicNameLetValidatorTests.cs | 58 +++++++++++++++++++ .../Validation/ClinicNameValidatorTests.cs | 58 +++++++++++++++++++ .../Validation/PostcodeValidatorTests.cs | 58 +++++++++++++++++++ .../Validation/SequenceValidatorTests.cs | 6 +- 12 files changed, 428 insertions(+), 11 deletions(-) rename {tests/ServiceLayer.Mesh.Tests => src/ServiceLayer.Mesh}/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs (98%) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs similarity index 98% rename from tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs rename to src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs index 4b75329..2a5c43d 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs @@ -1,4 +1,4 @@ -namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; +namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; public static class ErrorCodes { diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 604afb9..178c753 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -8,10 +8,15 @@ public static IEnumerable GetAllRecordValidators() { return [ - new InlineRegexValidator("Sequence", SequenceRegex(), "NBSSAPPT012", "NBSSAPPT013"), + new InlineRegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, ErrorCodes.InvalidSequence), new InlineMaxLengthValidator("Appointment ID", 27, "NBSSAPPT026", "NBSSAPPT027"), - new InlineMaxLengthValidator("Clinic Address 3", 30, "NBSSAPPT059", "NBSSAPPT060", true) - + new InlineMaxLengthValidator("Clinic Name", 40, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Name (Let)", 50, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Address 1", 30, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Address 2", 30, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Address 3", 30, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Address 4", 30, "NBSSAPPT059", "NBSSAPPT060", true), + new InlineMaxLengthValidator("Clinic Address 5", 30, "NBSSAPPT059", "NBSSAPPT060", true), ]; } diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs index b78fecb..2fc47ea 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs @@ -1,3 +1,5 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; public class AppointmentIdValidationTests : ValidationTestBase @@ -15,7 +17,7 @@ public void Validate_AppointmentIdMissing_ReturnsValidationError() validationErrors.ShouldBeSingleValidationError( "Appointment ID", "Appointment ID is missing or empty", - "NBSSAPPT026" + ErrorCodes.MissingAppointmentId ); } @@ -34,7 +36,7 @@ public void Validate_AppointmentIdBlank_ReturnsValidationError(string value) validationErrors.ShouldBeSingleValidationError( "Appointment ID", "Appointment ID is missing or empty", - "NBSSAPPT026" + ErrorCodes.MissingAppointmentId ); } @@ -53,7 +55,7 @@ public void Validate_AppointmentIdTooLong_ReturnsValidationError(string value) validationErrors.ShouldBeSingleValidationError( "Appointment ID", "Appointment ID exceeds maximum length of 27", - "NBSSAPPT027" + ErrorCodes.InvalidAppointmentId ); } 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..f8ac873 --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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..de270c3 --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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 index c157369..eeaf562 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs @@ -1,3 +1,5 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; public class ClinicAddress3ValidatorTests : ValidationTestBase @@ -15,7 +17,7 @@ public void Validate_ClinicAddress3Missing_ReturnsValidationError() validationErrors.ShouldBeSingleValidationError( "Clinic Address 3", "Clinic Address 3 is missing", - "NBSSAPPT059" + ErrorCodes.MissingClinicAddress3 ); } @@ -34,7 +36,7 @@ public void Validate_ClinicAddress3TooLong_ReturnsValidationError(string value) validationErrors.ShouldBeSingleValidationError( "Clinic Address 3", "Clinic Address 3 exceeds maximum length of 30", - "NBSSAPPT060" + ErrorCodes.InvalidClinicAddress3 ); } 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..1d5b3ba --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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..4bc755d --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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/ClinicNameLetValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs new file mode 100644 index 0000000..0ce611d --- /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.ShouldBeSingleValidationError( + "Clinic Name (Let)", + "Clinic Name (Let) is missing", + ErrorCodes.MissingClinicNameLet + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 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.ShouldBeSingleValidationError( + "Clinic Name (Let)", + "Clinic Name (Let) exceeds maximum length of 30", + ErrorCodes.InvalidClinicNameLet + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [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..959a248 --- /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.ShouldBeSingleValidationError( + "Clinic Name", + "Clinic Name is missing", + ErrorCodes.MissingClinicName + ); + } + + [Theory] + [InlineData("1234567890123456789012345678901")] // 31 characters + [InlineData("1234567890123456789012345678901234567890")] // 40 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.ShouldBeSingleValidationError( + "Clinic Name", + "Clinic Name exceeds maximum length of 30", + ErrorCodes.InvalidClinicName + ); + } + + [Theory] + [InlineData("")] + [InlineData("123456789012345678901234567890")] + [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/PostcodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs new file mode 100644 index 0000000..8163325 --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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/SequenceValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs index 3153fdf..1246072 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs @@ -1,3 +1,5 @@ +using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; + namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; public class SequenceValidatorTests : ValidationTestBase @@ -15,7 +17,7 @@ public void Validate_SequenceMissing_ReturnsValidationError() validationErrors.ShouldBeSingleValidationError( "Sequence", "Sequence is missing", - "NBSSAPPT012" + ErrorCodes.MissingSequence ); } @@ -37,7 +39,7 @@ public void Validate_SequenceInvalidFormat_ReturnsValidationError(string value) validationErrors.ShouldBeSingleValidationError( "Sequence", "Sequence is in an invalid format", - "NBSSAPPT013" + ErrorCodes.InvalidSequence ); } From f3df9577ed2130708dfa829c346b112f9c2b0a92 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Wed, 21 May 2025 14:11:16 +0100 Subject: [PATCH 05/14] WIP --- .../Validation/ValidatorRegistry.cs | 29 ++++++++++++------- .../Validation/ClinicNameLetValidatorTests.cs | 8 ++--- .../Validation/ClinicNameValidatorTests.cs | 8 ++--- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 178c753..7a70b88 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -8,16 +8,25 @@ public static IEnumerable GetAllRecordValidators() { return [ - new InlineRegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, ErrorCodes.InvalidSequence), - new InlineMaxLengthValidator("Appointment ID", 27, "NBSSAPPT026", "NBSSAPPT027"), - new InlineMaxLengthValidator("Clinic Name", 40, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Name (Let)", 50, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Address 1", 30, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Address 2", 30, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Address 3", 30, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Address 4", 30, "NBSSAPPT059", "NBSSAPPT060", true), - new InlineMaxLengthValidator("Clinic Address 5", 30, "NBSSAPPT059", "NBSSAPPT060", true), - + new InlineRegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, + ErrorCodes.InvalidSequence), + new InlineMaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, + ErrorCodes.InvalidAppointmentId), + new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, + true), + new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, + ErrorCodes.InvalidClinicNameLet, true), + new InlineMaxLengthValidator("Clinic Address 1", 30, ErrorCodes.MissingClinicAddress1, + ErrorCodes.InvalidClinicAddress1, true), + new InlineMaxLengthValidator("Clinic Address 2", 30, ErrorCodes.MissingClinicAddress2, + ErrorCodes.InvalidClinicAddress2, true), + new InlineMaxLengthValidator("Clinic Address 3", 30, ErrorCodes.MissingClinicAddress3, + ErrorCodes.InvalidClinicAddress3, true), + new InlineMaxLengthValidator("Clinic Address 4", 30, ErrorCodes.MissingClinicAddress4, + ErrorCodes.InvalidClinicAddress4, true), + new InlineMaxLengthValidator("Clinic Address 5", 30, ErrorCodes.MissingClinicAddress5, + ErrorCodes.InvalidClinicAddress5, true), + new InlineMaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, ErrorCodes.InvalidPostcode, true), ]; } diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs index 0ce611d..818319e 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs @@ -22,8 +22,8 @@ public void Validate_ClinicNameLetMissing_ReturnsValidationError() } [Theory] - [InlineData("1234567890123456789012345678901")] // 31 characters - [InlineData("1234567890123456789012345678901234567890")] // 40 characters + [InlineData("123456789012345678901234567890123456789012345678901")] // 51 characters + [InlineData("123456789012345678901234567890123456789012345678901234567890")] // 60 characters public void Validate_ClinicNameLetTooLong_ReturnsValidationError(string value) { // Arrange @@ -35,14 +35,14 @@ public void Validate_ClinicNameLetTooLong_ReturnsValidationError(string value) // Assert validationErrors.ShouldBeSingleValidationError( "Clinic Name (Let)", - "Clinic Name (Let) exceeds maximum length of 30", + "Clinic Name (Let) exceeds maximum length of 50", ErrorCodes.InvalidClinicNameLet ); } [Theory] [InlineData("")] - [InlineData("123456789012345678901234567890")] + [InlineData("12345678901234567890123456789012345678901234567890")] [InlineData("Breast Care Unit")] public void Validate_ClinicNameLetValid_NoValidationErrorsReturned(string value) { diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs index 959a248..a3841bd 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs @@ -22,8 +22,8 @@ public void Validate_ClinicNameMissing_ReturnsValidationError() } [Theory] - [InlineData("1234567890123456789012345678901")] // 31 characters - [InlineData("1234567890123456789012345678901234567890")] // 40 characters + [InlineData("12345678901234567890123456789012345678901")] // 41 characters + [InlineData("12345678901234567890123456789012345678901234567890")] // 50 characters public void Validate_ClinicNameTooLong_ReturnsValidationError(string value) { // Arrange @@ -35,14 +35,14 @@ public void Validate_ClinicNameTooLong_ReturnsValidationError(string value) // Assert validationErrors.ShouldBeSingleValidationError( "Clinic Name", - "Clinic Name exceeds maximum length of 30", + "Clinic Name exceeds maximum length of 40", ErrorCodes.InvalidClinicName ); } [Theory] [InlineData("")] - [InlineData("123456789012345678901234567890")] + [InlineData("1234567890123456789012345678901234567890")] [InlineData("Breast Care Unit")] public void Validate_ClinicNameValid_NoValidationErrorsReturned(string value) { From f66a5397e5db0f293bc6285db2721d7c3e6900d9 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Wed, 21 May 2025 14:36:32 +0100 Subject: [PATCH 06/14] WIP --- .../Validation/ErrorCodes.cs | 4 +- .../Validation/ValidatorRegistry.cs | 21 ++++- .../Validation/ActionValidatorTests.cs | 64 +++++++++++++++ .../Validation/AppointmentIdValidatorTests.cs | 2 +- .../Validation/BsoValidatorTests.cs | 76 ++++++++++++++++++ .../Validation/ClinicCodeValidatorTests.cs | 77 +++++++++++++++++++ .../Validation/HoldingClinicValidatorTests.cs | 62 +++++++++++++++ 7 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs index 2a5c43d..a0473b3 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ErrorCodes.cs @@ -21,8 +21,8 @@ public static class ErrorCodes public const string InvalidAction = "NBSSAPPT017"; public const string MissingClinicCode = "NBSSAPPT018"; public const string InvalidClinicCode = "NBSSAPPT019"; - public const string MissingHoldingClinicCode = "NBSSAPPT020"; - public const string InvalidHoldingClinicCode = "NBSSAPPT021"; + 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"; diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 7a70b88..3c68878 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -10,10 +10,18 @@ public static IEnumerable GetAllRecordValidators() [ new InlineRegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, ErrorCodes.InvalidSequence), + new InlineMaxLengthValidator("BSO", 3, ErrorCodes.MissingBso, + ErrorCodes.InvalidBso), + new InlineRegexValidator("Action", ActionRegex(), ErrorCodes.MissingAction, + ErrorCodes.InvalidAction), + new InlineRegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, + ErrorCodes.InvalidHoldingClinic), + new InlineMaxLengthValidator("Clinic Code", 5, ErrorCodes.MissingClinicCode, + ErrorCodes.InvalidClinicCode), new InlineMaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, ErrorCodes.InvalidAppointmentId), - new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, - true), + new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, + ErrorCodes.InvalidClinicName, true), new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, ErrorCodes.InvalidClinicNameLet, true), new InlineMaxLengthValidator("Clinic Address 1", 30, ErrorCodes.MissingClinicAddress1, @@ -26,7 +34,8 @@ public static IEnumerable GetAllRecordValidators() ErrorCodes.InvalidClinicAddress4, true), new InlineMaxLengthValidator("Clinic Address 5", 30, ErrorCodes.MissingClinicAddress5, ErrorCodes.InvalidClinicAddress5, true), - new InlineMaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, ErrorCodes.InvalidPostcode, true), + new InlineMaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, + ErrorCodes.InvalidPostcode, true), ]; } @@ -35,6 +44,12 @@ public static IEnumerable GetAllFileValidators() return []; } + [GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)] + private static partial Regex ActionRegex(); + [GeneratedRegex(@"^(?!000000)\d{6}$", RegexOptions.Compiled)] private static partial Regex SequenceRegex(); + + [GeneratedRegex(@"^$|^[ YN]$", RegexOptions.Compiled)] + private static partial Regex YesNoBlankRegex(); } 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..96f2319 --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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 index 2fc47ea..64cdf81 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs @@ -2,7 +2,7 @@ namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; -public class AppointmentIdValidationTests : ValidationTestBase +public class AppointmentIdValidatorTests : ValidationTestBase { [Fact] public void Validate_AppointmentIdMissing_ReturnsValidationError() 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..31c3fed --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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/ClinicCodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs new file mode 100644 index 0000000..d7fcf25 --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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/HoldingClinicValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs new file mode 100644 index 0000000..f9a9b34 --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs @@ -0,0 +1,62 @@ +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.ShouldBeSingleValidationError( + "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 + [InlineData("Y ")] // 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.ShouldBeSingleValidationError( + "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); + } +} From 0c430e43f0a6e8d441f32cb0f29fa9500dd75c06 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Wed, 21 May 2025 14:41:46 +0100 Subject: [PATCH 07/14] WIP --- .../Validation/ValidatorRegistry.cs | 11 +++- .../AttendedNotScrValidatorTests.cs | 62 ++++++++++++++++++ .../Validation/StatusValidatorTests.cs | 65 +++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 3c68878..5b6c919 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -14,10 +14,14 @@ public static IEnumerable GetAllRecordValidators() ErrorCodes.InvalidBso), new InlineRegexValidator("Action", ActionRegex(), ErrorCodes.MissingAction, ErrorCodes.InvalidAction), - new InlineRegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, - ErrorCodes.InvalidHoldingClinic), new InlineMaxLengthValidator("Clinic Code", 5, ErrorCodes.MissingClinicCode, ErrorCodes.InvalidClinicCode), + new InlineRegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, + ErrorCodes.InvalidHoldingClinic), + new InlineRegexValidator("Status", StatusRegex(), ErrorCodes.MissingStatus, + ErrorCodes.InvalidStatus), + new InlineRegexValidator("Attended Not Scr", YesNoBlankRegex(), ErrorCodes.MissingAttendedNotScr, + ErrorCodes.InvalidAttendedNotScr), new InlineMaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, ErrorCodes.InvalidAppointmentId), new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, @@ -47,6 +51,9 @@ public static IEnumerable GetAllFileValidators() [GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)] private static partial Regex ActionRegex(); + [GeneratedRegex(@"^[ABCD]$", RegexOptions.Compiled)] + private static partial Regex StatusRegex(); + [GeneratedRegex(@"^(?!000000)\d{6}$", RegexOptions.Compiled)] private static partial Regex SequenceRegex(); 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..2bddf1f --- /dev/null +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs @@ -0,0 +1,62 @@ +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.ShouldBeSingleValidationError( + "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 + [InlineData("Y ")] // 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.ShouldBeSingleValidationError( + "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/StatusValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs new file mode 100644 index 0000000..f56625c --- /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.ShouldBeSingleValidationError( + "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.ShouldBeSingleValidationError( + "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); + } +} From bbd1899582db04a0b0481f1a5e702507a136cbdb Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Wed, 21 May 2025 17:04:58 +0100 Subject: [PATCH 08/14] WIP --- .../Validation/ValidatorRegistry.cs | 21 ++++- .../Validation/ActionValidatorTests.cs | 4 +- .../Validation/AppointmentIdValidatorTests.cs | 6 +- .../AttendedNotScrValidatorTests.cs | 4 +- .../Validation/BatchIdValidatorTests.cs | 76 +++++++++++++++++++ .../Validation/BsoValidatorTests.cs | 6 +- .../ClinicAddress1ValidatorTests.cs | 4 +- .../ClinicAddress2ValidatorTests.cs | 4 +- .../ClinicAddress3ValidatorTests.cs | 4 +- .../ClinicAddress4ValidatorTests.cs | 4 +- .../ClinicAddress5ValidatorTests.cs | 4 +- .../Validation/ClinicCodeValidatorTests.cs | 6 +- .../Validation/ClinicNameLetValidatorTests.cs | 4 +- .../Validation/ClinicNameValidatorTests.cs | 4 +- .../Validation/EpisodeTypeValidatorTests.cs | 68 +++++++++++++++++ .../Validation/HoldingClinicValidatorTests.cs | 4 +- .../Validation/PostcodeValidatorTests.cs | 4 +- .../Validation/ScreenApptNumValidatorTests.cs | 68 +++++++++++++++++ .../Validation/ScreenOrAssesValidatorTests.cs | 62 +++++++++++++++ .../Validation/SequenceValidatorTests.cs | 4 +- .../Validation/StatusValidatorTests.cs | 4 +- .../Validation/ValidationErrorAssertions.cs | 19 ++--- 22 files changed, 336 insertions(+), 48 deletions(-) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BatchIdValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/EpisodeTypeValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenApptNumValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ScreenOrAssesValidatorTests.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 5b6c919..25fbd7e 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -24,6 +24,14 @@ public static IEnumerable GetAllRecordValidators() ErrorCodes.InvalidAttendedNotScr), new InlineMaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, ErrorCodes.InvalidAppointmentId), + new InlineRegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType, + ErrorCodes.InvalidEpisodeType), + new InlineMaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId, + ErrorCodes.InvalidBatchId), + new InlineRegexValidator("Screen or Asses", ScreenOrAssesRegex(), ErrorCodes.MissingScreenOrAsses, + ErrorCodes.InvalidScreenOrAsses), + new InlineRegexValidator("Screen Appt num", ScreenApptNumRegex(), ErrorCodes.MissingScreenApptNum, + ErrorCodes.InvalidScreenApptNum), new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, true), new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, @@ -51,12 +59,21 @@ public static IEnumerable GetAllFileValidators() [GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)] private static partial Regex ActionRegex(); - [GeneratedRegex(@"^[ABCD]$", RegexOptions.Compiled)] - private static partial Regex StatusRegex(); + [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/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs index 96f2319..9e4f1cc 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ActionValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ActionMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Action", "Action is missing", ErrorCodes.MissingAction @@ -39,7 +39,7 @@ public void Validate_ActionInvalidFormat_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Action", "Action is in an invalid format", ErrorCodes.InvalidAction diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs index 64cdf81..2fe1ce8 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AppointmentIdValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_AppointmentIdMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Appointment ID", "Appointment ID is missing or empty", ErrorCodes.MissingAppointmentId @@ -33,7 +33,7 @@ public void Validate_AppointmentIdBlank_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Appointment ID", "Appointment ID is missing or empty", ErrorCodes.MissingAppointmentId @@ -52,7 +52,7 @@ public void Validate_AppointmentIdTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Appointment ID", "Appointment ID exceeds maximum length of 27", ErrorCodes.InvalidAppointmentId diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs index 2bddf1f..f298193 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_AttendedNotScrMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Attended Not Scr", "Attended Not Scr is missing", ErrorCodes.MissingAttendedNotScr @@ -36,7 +36,7 @@ public void Validate_AttendedNotScrInvalidFormat_ReturnsValidationError(string v var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Attended Not Scr", "Attended Not Scr is in an invalid format", ErrorCodes.InvalidAttendedNotScr 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/BsoValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs index 31c3fed..38ef49d 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BsoValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_BSOMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "BSO", "BSO is missing or empty", ErrorCodes.MissingBso @@ -33,7 +33,7 @@ public void Validate_BsoBlank_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "BSO", "BSO is missing or empty", ErrorCodes.MissingBso @@ -52,7 +52,7 @@ public void Validate_BsoTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "BSO", "BSO exceeds maximum length of 3", ErrorCodes.InvalidBso diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs index f8ac873..87405a9 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress1ValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicAddress1Missing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 1", "Clinic Address 1 is missing", ErrorCodes.MissingClinicAddress1 @@ -33,7 +33,7 @@ public void Validate_ClinicAddress1TooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 1", "Clinic Address 1 exceeds maximum length of 30", ErrorCodes.InvalidClinicAddress1 diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs index de270c3..00e5ec7 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress2ValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicAddress2Missing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 2", "Clinic Address 2 is missing", ErrorCodes.MissingClinicAddress2 @@ -33,7 +33,7 @@ public void Validate_ClinicAddress2TooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 2", "Clinic Address 2 exceeds maximum length of 30", ErrorCodes.InvalidClinicAddress2 diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs index eeaf562..a4a7972 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress3ValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicAddress3Missing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 3", "Clinic Address 3 is missing", ErrorCodes.MissingClinicAddress3 @@ -33,7 +33,7 @@ public void Validate_ClinicAddress3TooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 3", "Clinic Address 3 exceeds maximum length of 30", ErrorCodes.InvalidClinicAddress3 diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs index 1d5b3ba..43e0d5d 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress4ValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicAddress4Missing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 4", "Clinic Address 4 is missing", ErrorCodes.MissingClinicAddress4 @@ -33,7 +33,7 @@ public void Validate_ClinicAddress4TooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 4", "Clinic Address 4 exceeds maximum length of 30", ErrorCodes.InvalidClinicAddress4 diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs index 4bc755d..87c0063 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicAddress5ValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicAddress5Missing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 5", "Clinic Address 5 is missing", ErrorCodes.MissingClinicAddress5 @@ -33,7 +33,7 @@ public void Validate_ClinicAddress5TooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Address 5", "Clinic Address 5 exceeds maximum length of 30", ErrorCodes.InvalidClinicAddress5 diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs index d7fcf25..d1f31cc 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicCodeValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicCodeMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Code", "Clinic Code is missing or empty", ErrorCodes.MissingClinicCode @@ -33,7 +33,7 @@ public void Validate_ClinicCodeBlank_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Code", "Clinic Code is missing or empty", ErrorCodes.MissingClinicCode @@ -52,7 +52,7 @@ public void Validate_ClinicCodeTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Code", "Clinic Code exceeds maximum length of 5", ErrorCodes.InvalidClinicCode diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs index 818319e..6d1600e 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameLetValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicNameLetMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Name (Let)", "Clinic Name (Let) is missing", ErrorCodes.MissingClinicNameLet @@ -33,7 +33,7 @@ public void Validate_ClinicNameLetTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Name (Let)", "Clinic Name (Let) exceeds maximum length of 50", ErrorCodes.InvalidClinicNameLet diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs index a3841bd..b7f14f0 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ClinicNameValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_ClinicNameMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Name", "Clinic Name is missing", ErrorCodes.MissingClinicName @@ -33,7 +33,7 @@ public void Validate_ClinicNameTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Clinic Name", "Clinic Name exceeds maximum length of 40", ErrorCodes.InvalidClinicName 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/HoldingClinicValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs index f9a9b34..a785a9e 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_HoldingClinicMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Holding Clinic", "Holding Clinic is missing", ErrorCodes.MissingHoldingClinic @@ -36,7 +36,7 @@ public void Validate_HoldingClinicInvalidFormat_ReturnsValidationError(string va var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Holding Clinic", "Holding Clinic is in an invalid format", ErrorCodes.InvalidHoldingClinic diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs index 8163325..0bb81d9 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/PostcodeValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_PostcodeMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Postcode", "Postcode is missing", ErrorCodes.MissingPostcode @@ -33,7 +33,7 @@ public void Validate_PostcodeTooLong_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Postcode", "Postcode exceeds maximum length of 8", ErrorCodes.InvalidPostcode 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 index 1246072..a6e9304 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/SequenceValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_SequenceMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Sequence", "Sequence is missing", ErrorCodes.MissingSequence @@ -36,7 +36,7 @@ public void Validate_SequenceInvalidFormat_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Sequence", "Sequence is in an invalid format", ErrorCodes.InvalidSequence diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs index f56625c..52e959a 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/StatusValidatorTests.cs @@ -14,7 +14,7 @@ public void Validate_StatusMissing_ReturnsValidationError() var validationErrors = Validate(file); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Status", "Status is missing", ErrorCodes.MissingStatus @@ -39,7 +39,7 @@ public void Validate_StatusInvalidFormat_ReturnsValidationError(string value) var validationErrors = Validate(file).ToList(); // Assert - validationErrors.ShouldBeSingleValidationError( + validationErrors.ShouldContainValidationError( "Status", "Status is in an invalid format", ErrorCodes.InvalidStatus diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs index abfef8d..1f60bc0 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ValidationErrorAssertions.cs @@ -2,24 +2,21 @@ namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation; public static class ValidationErrorAssertions { - public static ValidationError ShouldBeSingleValidationError( + public static void ShouldContainValidationError( this IEnumerable errors, string expectedField, string expectedError, string expectedCode, int? expectedRowNumber = null) { - var error = Assert.Single(errors); - Assert.Equal(expectedField, error.Field); - Assert.Equal(expectedError, error.Error); + var error = errors.FirstOrDefault(e => + e.Field == expectedField && + e.Error == expectedError && + e.Code == expectedCode && + (expectedRowNumber == null || e.RowNumber == expectedRowNumber) + ); - Assert.Equal(expectedCode, error.Code); + Assert.True(error != null, $"Expected validation error with Field: '{expectedField}', Error: '{expectedError}', Code: '{expectedCode}'{(expectedRowNumber != null ? $", RowNumber: {expectedRowNumber}" : "")}, but none was found."); - if (expectedRowNumber != null) - { - Assert.Equal(expectedRowNumber, error.RowNumber); - } - - return error; } } From 67bd6f95a29080bb944c64318746db2bda25a042 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 08:19:55 +0100 Subject: [PATCH 09/14] wip --- .../Validation/ValidatorRegistry.cs | 10 +++ .../AttendedNotScrValidatorTests.cs | 1 - .../Validation/BookedByValidatorTests.cs | 62 +++++++++++++++++++ .../Validation/CancelledByValidatorTests.cs | 61 ++++++++++++++++++ .../Validation/HoldingClinicValidatorTests.cs | 1 - 5 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/BookedByValidatorTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/CancelledByValidatorTests.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 25fbd7e..05d3d7c 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -32,6 +32,10 @@ public static IEnumerable GetAllRecordValidators() ErrorCodes.InvalidScreenOrAsses), new InlineRegexValidator("Screen Appt num", ScreenApptNumRegex(), ErrorCodes.MissingScreenApptNum, ErrorCodes.InvalidScreenApptNum), + new InlineRegexValidator("Booked By", BookedByRegex(), ErrorCodes.MissingBookedBy, + ErrorCodes.InvalidBookedBy), + new InlineRegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy, + ErrorCodes.InvalidCancelledBy), new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, true), new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, @@ -59,6 +63,12 @@ public static IEnumerable GetAllFileValidators() [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(); diff --git a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs index f298193..2e7e643 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/AttendedNotScrValidatorTests.cs @@ -26,7 +26,6 @@ public void Validate_AttendedNotScrMissing_ReturnsValidationError() [InlineData("D")] // invalid character [InlineData(" ")] // Too many characters [InlineData("YN")] // Too many characters - [InlineData("Y ")] // Too many characters public void Validate_AttendedNotScrInvalidFormat_ReturnsValidationError(string value) { // Arrange 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/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/HoldingClinicValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs index a785a9e..e9a70ef 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/HoldingClinicValidatorTests.cs @@ -26,7 +26,6 @@ public void Validate_HoldingClinicMissing_ReturnsValidationError() [InlineData("D")] // invalid character [InlineData(" ")] // Too many characters [InlineData("YN")] // Too many characters - [InlineData("Y ")] // Too many characters public void Validate_HoldingClinicInvalidFormat_ReturnsValidationError(string value) { // Arrange From 9f3ec2645fea1d6a8b8faa00087b83cae6fb2e45 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 08:41:48 +0100 Subject: [PATCH 10/14] WIP --- .../Validation/ValidatorRegistry.cs | 2 + .../Validation/ErrorCodesTests.cs | 37 +++++++++ .../Validation/LocationValidatorTests.cs | 76 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/ErrorCodesTests.cs create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/LocationValidatorTests.cs diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 05d3d7c..9f40e62 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -36,6 +36,8 @@ public static IEnumerable GetAllRecordValidators() ErrorCodes.InvalidBookedBy), new InlineRegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy, ErrorCodes.InvalidCancelledBy), + new InlineMaxLengthValidator("Location", 5, ErrorCodes.MissingLocation, + ErrorCodes.InvalidLocation), new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, true), new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, 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/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); + } +} From 72c4ad0c886ca27b501bd83d52fb89312e522d65 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 08:47:50 +0100 Subject: [PATCH 11/14] refactoring - renaming --- ...ngthValidator.cs => MaxLengthValidator.cs} | 4 +- ...ineRegexValidator.cs => RegexValidator.cs} | 2 +- .../Validation/ValidatorRegistry.cs | 46 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) rename src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/{InlineMaxLengthValidator.cs => MaxLengthValidator.cs} (87%) rename src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/{InlineRegexValidator.cs => RegexValidator.cs} (96%) diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs similarity index 87% rename from src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs rename to src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs index 8b0d72b..fd686f2 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineMaxLengthValidator.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs @@ -2,7 +2,7 @@ namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; -public class InlineMaxLengthValidator(string fieldName, int maxLength, string errorCodeMissing, string errorCodeTooLong, bool allowEmpty = false) +public class MaxLengthValidator(string fieldName, int maxLength, string errorCodeMissing, string errorCodeTooLong, bool allowEmpty = false) : IRecordValidator { public IEnumerable Validate(FileDataRecord fileDataRecord) @@ -12,7 +12,7 @@ public IEnumerable Validate(FileDataRecord fileDataRecord) if (value == null || (!allowEmpty && string.IsNullOrWhiteSpace(value))) { var error = $"{fieldName} is missing{(allowEmpty ? "" : " or empty")}"; - + yield return new ValidationError { RowNumber = fileDataRecord.RowNumber, diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs similarity index 96% rename from src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs rename to src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs index 4aa03e9..0bf7834 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/InlineRegexValidator.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs @@ -3,7 +3,7 @@ namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; -public class InlineRegexValidator( +public class RegexValidator( string fieldName, Regex pattern, string errorCodeMissing, diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs index 9f40e62..5096d19 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs @@ -8,51 +8,51 @@ public static IEnumerable GetAllRecordValidators() { return [ - new InlineRegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, + new RegexValidator("Sequence", SequenceRegex(), ErrorCodes.MissingSequence, ErrorCodes.InvalidSequence), - new InlineMaxLengthValidator("BSO", 3, ErrorCodes.MissingBso, + new MaxLengthValidator("BSO", 3, ErrorCodes.MissingBso, ErrorCodes.InvalidBso), - new InlineRegexValidator("Action", ActionRegex(), ErrorCodes.MissingAction, + new RegexValidator("Action", ActionRegex(), ErrorCodes.MissingAction, ErrorCodes.InvalidAction), - new InlineMaxLengthValidator("Clinic Code", 5, ErrorCodes.MissingClinicCode, + new MaxLengthValidator("Clinic Code", 5, ErrorCodes.MissingClinicCode, ErrorCodes.InvalidClinicCode), - new InlineRegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, + new RegexValidator("Holding Clinic", YesNoBlankRegex(), ErrorCodes.MissingHoldingClinic, ErrorCodes.InvalidHoldingClinic), - new InlineRegexValidator("Status", StatusRegex(), ErrorCodes.MissingStatus, + new RegexValidator("Status", StatusRegex(), ErrorCodes.MissingStatus, ErrorCodes.InvalidStatus), - new InlineRegexValidator("Attended Not Scr", YesNoBlankRegex(), ErrorCodes.MissingAttendedNotScr, + new RegexValidator("Attended Not Scr", YesNoBlankRegex(), ErrorCodes.MissingAttendedNotScr, ErrorCodes.InvalidAttendedNotScr), - new InlineMaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, + new MaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId, ErrorCodes.InvalidAppointmentId), - new InlineRegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType, + new RegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType, ErrorCodes.InvalidEpisodeType), - new InlineMaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId, + new MaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId, ErrorCodes.InvalidBatchId), - new InlineRegexValidator("Screen or Asses", ScreenOrAssesRegex(), ErrorCodes.MissingScreenOrAsses, + new RegexValidator("Screen or Asses", ScreenOrAssesRegex(), ErrorCodes.MissingScreenOrAsses, ErrorCodes.InvalidScreenOrAsses), - new InlineRegexValidator("Screen Appt num", ScreenApptNumRegex(), ErrorCodes.MissingScreenApptNum, + new RegexValidator("Screen Appt num", ScreenApptNumRegex(), ErrorCodes.MissingScreenApptNum, ErrorCodes.InvalidScreenApptNum), - new InlineRegexValidator("Booked By", BookedByRegex(), ErrorCodes.MissingBookedBy, + new RegexValidator("Booked By", BookedByRegex(), ErrorCodes.MissingBookedBy, ErrorCodes.InvalidBookedBy), - new InlineRegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy, + new RegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy, ErrorCodes.InvalidCancelledBy), - new InlineMaxLengthValidator("Location", 5, ErrorCodes.MissingLocation, + new MaxLengthValidator("Location", 5, ErrorCodes.MissingLocation, ErrorCodes.InvalidLocation), - new InlineMaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, + new MaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName, ErrorCodes.InvalidClinicName, true), - new InlineMaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, + new MaxLengthValidator("Clinic Name (Let)", 50, ErrorCodes.MissingClinicNameLet, ErrorCodes.InvalidClinicNameLet, true), - new InlineMaxLengthValidator("Clinic Address 1", 30, ErrorCodes.MissingClinicAddress1, + new MaxLengthValidator("Clinic Address 1", 30, ErrorCodes.MissingClinicAddress1, ErrorCodes.InvalidClinicAddress1, true), - new InlineMaxLengthValidator("Clinic Address 2", 30, ErrorCodes.MissingClinicAddress2, + new MaxLengthValidator("Clinic Address 2", 30, ErrorCodes.MissingClinicAddress2, ErrorCodes.InvalidClinicAddress2, true), - new InlineMaxLengthValidator("Clinic Address 3", 30, ErrorCodes.MissingClinicAddress3, + new MaxLengthValidator("Clinic Address 3", 30, ErrorCodes.MissingClinicAddress3, ErrorCodes.InvalidClinicAddress3, true), - new InlineMaxLengthValidator("Clinic Address 4", 30, ErrorCodes.MissingClinicAddress4, + new MaxLengthValidator("Clinic Address 4", 30, ErrorCodes.MissingClinicAddress4, ErrorCodes.InvalidClinicAddress4, true), - new InlineMaxLengthValidator("Clinic Address 5", 30, ErrorCodes.MissingClinicAddress5, + new MaxLengthValidator("Clinic Address 5", 30, ErrorCodes.MissingClinicAddress5, ErrorCodes.InvalidClinicAddress5, true), - new InlineMaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, + new MaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode, ErrorCodes.InvalidPostcode, true), ]; } From fc33b0cd9b70648e025e737d2df5760fb8bf83f4 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 09:11:36 +0100 Subject: [PATCH 12/14] regex validator tests --- .../Validation/RegexValidatorTests.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs 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..b342281 --- /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 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 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 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); + } +} From 3c70870c8892d484928a970b9158655c7aba57fe Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 09:26:22 +0100 Subject: [PATCH 13/14] max length validator tests --- .../Validation/MaxLengthValidatorTests.cs | 113 ++++++++++++++++++ .../Validation/RegexValidatorTests.cs | 6 +- 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidatorTests.cs 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/RegexValidatorTests.cs b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs index b342281..d7c81b8 100644 --- a/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs +++ b/tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/Validation/RegexValidatorTests.cs @@ -12,7 +12,7 @@ public class RegexValidatorTests private readonly Regex _pattern = new(@"^[A-Z]{2}\d{2}$", RegexOptions.Compiled); [Fact] - public void NullValue_ShouldReturnMissingError() + public void Validate_NullValue_ShouldReturnMissingError() { // Arrange var record = new FileDataRecord @@ -33,7 +33,7 @@ public void NullValue_ShouldReturnMissingError() [Theory] [InlineData("")] [InlineData("invalid")] - public void ValueNotMatchingPattern_ShouldReturnInvalidFormatError(string invalidValue) + public void Validate_ValueNotMatchingPattern_ShouldReturnInvalidFormatError(string invalidValue) { // Arrange var record = new FileDataRecord @@ -54,7 +54,7 @@ public void ValueNotMatchingPattern_ShouldReturnInvalidFormatError(string invali [Theory] [InlineData("AB12")] [InlineData("CD34")] - public void ValueMatchingPattern_ShouldReturnNoErrors(string validValue) + public void Validate_ValueMatchingPattern_ShouldReturnNoErrors(string validValue) { var record = new FileDataRecord { From ba8128f9a6359e143195fbabb9bec104b1376328 Mon Sep 17 00:00:00 2001 From: Ian Nelson Date: Thu, 22 May 2025 09:41:35 +0100 Subject: [PATCH 14/14] whitespace --- .../NbssAppointmentEvents/Validation/MaxLengthValidator.cs | 7 ++++++- src/ServiceLayer.sln.DotSettings | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs index fd686f2..b4ae58f 100644 --- a/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs +++ b/src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/MaxLengthValidator.cs @@ -2,7 +2,12 @@ namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation; -public class MaxLengthValidator(string fieldName, int maxLength, string errorCodeMissing, string errorCodeTooLong, bool allowEmpty = false) +public class MaxLengthValidator( + string fieldName, + int maxLength, + string errorCodeMissing, + string errorCodeTooLong, + bool allowEmpty = false) : IRecordValidator { public IEnumerable Validate(FileDataRecord fileDataRecord) diff --git a/src/ServiceLayer.sln.DotSettings b/src/ServiceLayer.sln.DotSettings index 4275d1a..a78f4e1 100644 --- a/src/ServiceLayer.sln.DotSettings +++ b/src/ServiceLayer.sln.DotSettings @@ -1,2 +1,2 @@  - True \ No newline at end of file + True