Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 9e42794

Browse files
committed
feat: DTOSS-9160 - NHS Num validator
1 parent 784b6f3 commit 9e42794

File tree

4 files changed

+139
-5
lines changed

4 files changed

+139
-5
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Text.RegularExpressions;
2+
3+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
4+
5+
public partial class NhsNumValidator() :
6+
RegexValidator("NHS Num", NhsNumberRegex(), ErrorCodes.MissingNhsNum, ErrorCodes.InvalidNhsNum)
7+
{
8+
protected override IEnumerable<ValidationError> RunAdditionalChecks(int rowNumber, string value)
9+
{
10+
if (!HasValidCheckDigit(value))
11+
{
12+
yield return new ValidationError
13+
{
14+
RowNumber = rowNumber,
15+
Field = FieldName,
16+
Error = "NHS Num has invalid check digit",
17+
Code = ErrorCodes.InvalidNhsNumCheckDigit
18+
};
19+
}
20+
}
21+
22+
private static bool HasValidCheckDigit(string value)
23+
{
24+
var weightedSum = 0;
25+
for (var i = 0; i < 9; i++)
26+
{
27+
weightedSum += (10 - i) * (value[i] - '0');
28+
}
29+
30+
var remainder = weightedSum % 11;
31+
var expectedCheckDigit = (11 - remainder) % 11;
32+
33+
return expectedCheckDigit == value[9] - '0';
34+
}
35+
36+
[GeneratedRegex(@"^\d{10}$", RegexOptions.Compiled)]
37+
private static partial Regex NhsNumberRegex();
38+
}

src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/RegexValidator.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ public class RegexValidator(
1010
string errorCodeInvalidFormat)
1111
: IRecordValidator
1212
{
13+
protected string FieldName { get; } = fieldName;
14+
1315
public IEnumerable<ValidationError> Validate(FileDataRecord fileDataRecord)
1416
{
15-
var value = fileDataRecord[fieldName];
17+
var value = fileDataRecord[FieldName];
1618

1719
if (value == null)
1820
{
1921
yield return new ValidationError
2022
{
2123
RowNumber = fileDataRecord.RowNumber,
22-
Field = fieldName,
23-
Error = $"{fieldName} is missing",
24+
Field = FieldName,
25+
Error = $"{FieldName} is missing",
2426
Code = errorCodeMissing,
2527
};
2628
yield break;
@@ -31,10 +33,21 @@ public IEnumerable<ValidationError> Validate(FileDataRecord fileDataRecord)
3133
yield return new ValidationError
3234
{
3335
RowNumber = fileDataRecord.RowNumber,
34-
Field = fieldName,
35-
Error = $"{fieldName} is in an invalid format",
36+
Field = FieldName,
37+
Error = $"{FieldName} is in an invalid format",
3638
Code = errorCodeInvalidFormat,
3739
};
40+
yield break;
41+
}
42+
43+
foreach (var additionalError in RunAdditionalChecks(fileDataRecord.RowNumber, value))
44+
{
45+
yield return additionalError;
3846
}
3947
}
48+
49+
protected virtual IEnumerable<ValidationError> RunAdditionalChecks(int rowNumber, string value)
50+
{
51+
yield break;
52+
}
4053
}

src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
2424
ErrorCodes.InvalidAttendedNotScr),
2525
new MaxLengthValidator("Appointment ID", 27, ErrorCodes.MissingAppointmentId,
2626
ErrorCodes.InvalidAppointmentId),
27+
new NhsNumValidator(),
2728
new RegexValidator("Episode Type", EpisodeTypeRegex(), ErrorCodes.MissingEpisodeType,
2829
ErrorCodes.InvalidEpisodeType),
2930
new MaxLengthValidator("Batch ID", 9, ErrorCodes.MissingBatchId,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
2+
3+
namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation;
4+
5+
public class NhsNumValidatorTests : ValidationTestBase
6+
{
7+
[Fact]
8+
public void Validate_NhsNumMissing_ReturnsValidationError()
9+
{
10+
// Arrange
11+
var file = ParsedFileWithModifiedRecord(r => r.Fields.Remove("NHS Num"));
12+
13+
// Act
14+
var validationErrors = Validate(file);
15+
16+
// Assert
17+
validationErrors.ShouldContainValidationError(
18+
"NHS Num",
19+
"NHS Num is missing",
20+
ErrorCodes.MissingNhsNum
21+
);
22+
}
23+
24+
[Theory]
25+
[InlineData("308 407 5425")] // we don't anticipate spaces
26+
[InlineData("857320211")] // too few character
27+
[InlineData("90238807571")] // Too many characters
28+
[InlineData("159278895S")] // invalid characters
29+
public void Validate_NhsNumInvalidFormat_ReturnsValidationError(string value)
30+
{
31+
// Arrange
32+
var file = ParsedFileWithModifiedRecord(r => r.Fields["NHS Num"] = value);
33+
34+
// Act
35+
var validationErrors = Validate(file).ToList();
36+
37+
// Assert
38+
validationErrors.ShouldContainValidationError(
39+
"NHS Num",
40+
"NHS Num is in an invalid format",
41+
ErrorCodes.InvalidNhsNum
42+
);
43+
}
44+
45+
[Theory]
46+
[InlineData("3244700471")]
47+
[InlineData("7326012282")]
48+
[InlineData("6245827145")]
49+
[InlineData("4745895257")]
50+
public void Validate_NhsNumInvalidCheckDigit_ReturnsValidationError(string value)
51+
{
52+
// Arrange
53+
var file = ParsedFileWithModifiedRecord(r => r.Fields["NHS Num"] = value);
54+
55+
// Act
56+
var validationErrors = Validate(file).ToList();
57+
58+
// Assert
59+
validationErrors.ShouldContainValidationError(
60+
"NHS Num",
61+
"NHS Num has invalid check digit",
62+
ErrorCodes.InvalidNhsNumCheckDigit
63+
);
64+
}
65+
66+
[Theory]
67+
[InlineData("4941273230")]
68+
[InlineData("6451357219")]
69+
[InlineData("3365582983")]
70+
[InlineData("8799244780")]
71+
public void Validate_NHSNumValidFormat_NoValidationErrorsReturned(string value)
72+
{
73+
// Arrange
74+
var file = ParsedFileWithModifiedRecord(r => r.Fields["NHS Num"] = value);
75+
76+
// Act
77+
var validationErrors = Validate(file).ToList();
78+
79+
// Assert
80+
Assert.Empty(validationErrors);
81+
}
82+
}

0 commit comments

Comments
 (0)