Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Globalization;
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;

namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;

public class DateFormatValidator(
string fieldName,
string format,
string errorCodeMissing,
string errorCodeInvalidFormat) : IRecordValidator
{
public IEnumerable<ValidationError> Validate(FileDataRecord fileDataRecord)
{
var value = fileDataRecord[fieldName];

if (value == null)
{
yield return new ValidationError
{
Scope = ValidationErrorScope.Record,
RowNumber = fileDataRecord.RowNumber,
Field = fieldName,
Error = $"{fieldName} is missing",
Code = errorCodeMissing,
};
yield break;
}

if (!DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out _))
{
yield return new ValidationError
{
Scope = ValidationErrorScope.Record,
RowNumber = fileDataRecord.RowNumber,
Field = fieldName,
Error = $"{fieldName} is in an invalid format",
Code = errorCodeInvalidFormat,
};
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices.JavaScript;
using System.Text.RegularExpressions;

namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
Expand Down Expand Up @@ -27,6 +28,8 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
new NhsNumValidator(),
new RegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType,
ErrorCodes.InvalidEpisodeType),
new DateFormatValidator("Episode Start", "yyyyMMdd", ErrorCodes.MissingEpisodeStart,
ErrorCodes.InvalidEpisodeStart),
new MaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId,
ErrorCodes.InvalidBatchId),
new RegexValidator("Screen or Asses", ScreenOrAssesRegex(), ErrorCodes.MissingScreenOrAsses,
Expand All @@ -37,6 +40,10 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
ErrorCodes.InvalidBookedBy),
new RegexValidator("Cancelled By", CancelledByRegex(), ErrorCodes.MissingCancelledBy,
ErrorCodes.InvalidCancelledBy),
new DateFormatValidator("Appt Date", "yyyyMMdd", ErrorCodes.MissingApptDate,
ErrorCodes.InvalidApptDate),
new DateFormatValidator("Appt Time", "HHmm", ErrorCodes.MissingApptTime,
ErrorCodes.InvalidApptTime),
new MaxLengthValidator("Location", 5, ErrorCodes.MissingLocation,
ErrorCodes.InvalidLocation),
new MaxLengthValidator("Clinic Name", 40, ErrorCodes.MissingClinicName,
Expand All @@ -55,6 +62,8 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
ErrorCodes.InvalidClinicAddress5, true),
new MaxLengthValidator("Postcode", 8, ErrorCodes.MissingPostcode,
ErrorCodes.InvalidPostcode, true),
new DateFormatValidator("Action Timestamp", "yyyyMMdd-HHmmss", ErrorCodes.MissingActionTimestamp,
ErrorCodes.InvalidActionTimestamp)
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;

namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation;

public class ActionTimestampValidatorTests : ValidationTestBase
{
[Fact]
public void Validate_ActionTimestampMissing_ReturnsValidationError()
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Action Timestamp"));

// Act
var validationErrors = Validate(file);

// Assert
validationErrors.ShouldContainValidationError(
"Action Timestamp",
"Action Timestamp is missing",
ErrorCodes.MissingActionTimestamp
);
}

[Theory]
[InlineData("20250631-183156")] // too many days in June
[InlineData("202S0630-183156")] // invalid character
[InlineData("20250630-1831")] // too short
[InlineData("20250630T1831")] // unexpected separator
[InlineData("250630-183156")] // too short, ddMMyy
[InlineData("20250630-1456")] // No seconds
[InlineData("20250630-18:31:56")] // unexpected separators
[InlineData("20250229-183156")] // Not a leap year
public void Validate_ActionTimestampInvalidFormat_ReturnsValidationError(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Action Timestamp"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
validationErrors.ShouldContainValidationError(
"Action Timestamp",
"Action Timestamp is in an invalid format",
ErrorCodes.InvalidActionTimestamp
);
}

[Theory]
[InlineData("20250529-163243")]
[InlineData("20240229-163243")]
[InlineData("20250731-163243")]
[InlineData("19990806-235959")]
[InlineData("20561212-000000")]
public void Validate_ActionTimestampValidFormat_NoValidationErrorsReturned(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Action Timestamp"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
Assert.Empty(validationErrors);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;

namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation;

public class ApptDateValidatorTests : ValidationTestBase
{
[Fact]
public void Validate_ApptDateMissing_ReturnsValidationError()
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Appt Date"));

// Act
var validationErrors = Validate(file);

// Assert
validationErrors.ShouldContainValidationError(
"Appt Date",
"Appt Date is missing",
ErrorCodes.MissingApptDate
);
}

[Theory]
[InlineData("20250631")] // too many days in June
[InlineData("202S0630")] // invalid character
[InlineData("202506")] // too short
[InlineData("30062025")] // ddMMyyyy and not valid as yyyyMMdd
[InlineData("250630")] // too short, ddMMyy
[InlineData("20250630-145621")] // Includes time
[InlineData("20250229")] // Not a leap year
public void Validate_ApptDateInvalidFormat_ReturnsValidationError(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Appt Date"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
validationErrors.ShouldContainValidationError(
"Appt Date",
"Appt Date is in an invalid format",
ErrorCodes.InvalidApptDate
);
}

[Theory]
[InlineData("20250101")]
[InlineData("20250228")]
[InlineData("20250331")]
[InlineData("20251231")]
[InlineData("20240229")]
[InlineData("19990331")]
public void Validate_ApptDateValidFormat_NoValidationErrorsReturned(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Appt Date"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
Assert.Empty(validationErrors);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;

namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation;

public class ApptTimeValidatorTests : ValidationTestBase
{
[Fact]
public void Validate_ApptTimeMissing_ReturnsValidationError()
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("Appt Time"));

// Act
var validationErrors = Validate(file);

// Assert
validationErrors.ShouldContainValidationError(
"Appt Time",
"Appt Time is missing",
ErrorCodes.MissingApptTime
);
}

[Theory]
[InlineData("2407")] // too many hours
[InlineData("1960")] // too many minutes
[InlineData("842")] // too short
[InlineData("10435")] // too long
[InlineData("193S")] // invalid characters
public void Validate_ApptTimeInvalidFormat_ReturnsValidationError(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Appt Time"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
validationErrors.ShouldContainValidationError(
"Appt Time",
"Appt Time is in an invalid format",
ErrorCodes.InvalidApptTime
);
}

[Theory]
[InlineData("0000")]
[InlineData("2359")]
[InlineData("0001")]
[InlineData("2358")]
[InlineData("1200")]
[InlineData("1300")]
public void Validate_ApptTimeValidFormat_NoValidationErrorsReturned(string value)
{
// Arrange
var file = ParsedFileWithModifiedRecord(r => r.Fields["Appt Time"] = value);

// Act
var validationErrors = Validate(file).ToList();

// Assert
Assert.Empty(validationErrors);
}
}
Original file line number Diff line number Diff line change
@@ -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 DateFormatValidatorTests
{
private const string FieldName = "TestField";
private const string MissingCode = "ERR001";
private const string InvalidFormatCode = "ERR002";
private const string Format = "yyyyMMdd";

[Fact]
public void Validate_NullValue_ShouldReturnMissingError()
{
// Arrange
var record = new FileDataRecord
{
RowNumber = 1
};
record.Fields.Clear();

var validator = new DateFormatValidator(FieldName, Format, MissingCode, InvalidFormatCode);

// Act
var errors = validator.Validate(record).ToList();

// Assert
errors.ShouldContainValidationError(FieldName, $"{FieldName} is missing", MissingCode, ValidationErrorScope.Record, 1);
}

[Theory]
[InlineData("")]
[InlineData("20250631")]
public void Validate_ValueNotMatchingPattern_ShouldReturnInvalidFormatError(string invalidValue)
{
// Arrange
var record = new FileDataRecord
{
RowNumber = 2
};
record.Fields.Add(FieldName, invalidValue);

var validator = new DateFormatValidator(FieldName, Format, MissingCode, InvalidFormatCode);

// Act
var errors = validator.Validate(record).ToList();

// Assert
errors.ShouldContainValidationError(FieldName, $"{FieldName} is in an invalid format", InvalidFormatCode,ValidationErrorScope.Record, 2);
}

[Theory]
[InlineData("20250630")]
[InlineData("19990807")]
public void Validate_ValueMatchingPattern_ShouldReturnNoErrors(string validValue)
{
var record = new FileDataRecord
{
RowNumber = 3
};
record.Fields.Add(FieldName, validValue);

var validator = new DateFormatValidator(FieldName, Format, MissingCode, InvalidFormatCode);

var errors = validator.Validate(record).ToList();

Assert.Empty(errors);
}
}
Loading
Loading