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

Commit ed4d9b8

Browse files
authored
refactor: DTOSS-9159 - split file-level validation into four separate classes (#48)
1 parent 6e7c68a commit ed4d9b8

File tree

12 files changed

+349
-321
lines changed

12 files changed

+349
-321
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Text.RegularExpressions;
2+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
3+
4+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
5+
6+
public partial class ExtractIdValidator() :
7+
HeaderFieldRegexValidator(h => h.ExtractId, "Extract ID", ExtractIdRegex(), ErrorCodes.MissingExtractId, ErrorCodes.InvalidExtractId)
8+
{
9+
[GeneratedRegex(@"^\d{8}$")]
10+
private static partial Regex ExtractIdRegex();
11+
12+
protected override IEnumerable<ValidationError> RunAdditionalChecks(ParsedFile file, string value, bool hasErrored)
13+
{
14+
if (file.FileTrailer != null && file.FileHeader!.ExtractId != file.FileTrailer.ExtractId)
15+
{
16+
yield return new ValidationError
17+
{
18+
Field = "Extract ID",
19+
Code = ErrorCodes.InconsistentExtractId,
20+
Error = "Extract ID does not match value in header",
21+
Scope = ValidationErrorScope.Trailer
22+
};
23+
}
24+
}
25+
}

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

Lines changed: 0 additions & 109 deletions
This file was deleted.

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public class HeaderFieldRegexValidator(
1414
{
1515
public IEnumerable<ValidationError> Validate(ParsedFile file)
1616
{
17+
if (file.FileHeader == null)
18+
{
19+
yield break;
20+
}
21+
22+
var hasErrored = false;
1723
var header = file.FileHeader!;
1824
var value = fieldSelector.Compile().Invoke(header);
1925

@@ -31,6 +37,7 @@ public IEnumerable<ValidationError> Validate(ParsedFile file)
3137

3238
if (!pattern.IsMatch(value))
3339
{
40+
hasErrored = true;
3441
yield return new ValidationError
3542
{
3643
Scope = ValidationErrorScope.Header,
@@ -39,5 +46,15 @@ public IEnumerable<ValidationError> Validate(ParsedFile file)
3946
Code = errorCodeInvalidFormat,
4047
};
4148
}
49+
50+
foreach (var additionalError in RunAdditionalChecks(file, value, hasErrored))
51+
{
52+
yield return additionalError;
53+
}
54+
}
55+
56+
protected virtual IEnumerable<ValidationError> RunAdditionalChecks(ParsedFile file, string value, bool hasErrored)
57+
{
58+
yield break;
4259
}
4360
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
2+
3+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
4+
5+
public class HeaderPresenceValidator : IFileValidator
6+
{
7+
public IEnumerable<ValidationError> Validate(ParsedFile file)
8+
{
9+
if (file.FileHeader == null)
10+
{
11+
yield return new ValidationError
12+
{
13+
Code = ErrorCodes.MissingHeader,
14+
Error = "Header is missing",
15+
Scope = ValidationErrorScope.File
16+
};
17+
}
18+
}
19+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Text.RegularExpressions;
2+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
3+
4+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
5+
6+
public partial class RecordCountValidator() :
7+
HeaderFieldRegexValidator(h => h.RecordCount, "Record count", RecordCountRegex(), ErrorCodes.MissingRecordCount, ErrorCodes.InvalidRecordCount)
8+
{
9+
[GeneratedRegex(@"^(?!000000)\d{6}$")]
10+
private static partial Regex RecordCountRegex();
11+
12+
protected override IEnumerable<ValidationError> RunAdditionalChecks(ParsedFile file, string value, bool hasErrored)
13+
{
14+
if (file.FileTrailer != null && file.FileHeader!.RecordCount != file.FileTrailer.RecordCount)
15+
{
16+
yield return new ValidationError
17+
{
18+
Field = "Record count",
19+
Code = ErrorCodes.InconsistentRecordCount,
20+
Error = "Record count does not match value in header",
21+
Scope = ValidationErrorScope.Trailer
22+
};
23+
}
24+
else if (!hasErrored && file.DataRecords.Count != int.Parse(file.FileHeader!.RecordCount!))
25+
{
26+
yield return new ValidationError
27+
{
28+
Code = ErrorCodes.UnexpectedRecordCount,
29+
Error = "Record count does not match value in header and trailer",
30+
Scope = ValidationErrorScope.File
31+
};
32+
}
33+
}
34+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
2+
3+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
4+
5+
public class TrailerPresenceValidator : IFileValidator
6+
{
7+
public IEnumerable<ValidationError> Validate(ParsedFile file)
8+
{
9+
if (file.FileTrailer == null)
10+
{
11+
yield return new ValidationError
12+
{
13+
Code = ErrorCodes.MissingTrailer,
14+
Error = "Trailer is missing",
15+
Scope = ValidationErrorScope.File
16+
};
17+
}
18+
}
19+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
6868

6969
public static IEnumerable<IFileValidator> GetAllFileValidators()
7070
{
71-
return [new FileValidator()];
71+
return [
72+
new HeaderPresenceValidator(),
73+
new TrailerPresenceValidator(),
74+
new ExtractIdValidator(),
75+
new RecordCountValidator()
76+
];
7277
}
7378

7479
[GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
2+
3+
namespace ServiceLayer.Mesh.Tests.FileTypes.NbssAppointmentEvents.Validation;
4+
5+
public class ExtractIdValidatorTests : ValidationTestBase
6+
{
7+
[Fact]
8+
public void Validate_HeaderExtractIdMissing_ReturnsValidationError()
9+
{
10+
// Arrange
11+
var file = ValidParsedFile;
12+
file.FileHeader!.ExtractId = null;
13+
14+
// Act
15+
var validationErrors = Validate(file);
16+
17+
// Assert
18+
validationErrors.ShouldContainValidationError(
19+
"Extract ID",
20+
"Extract ID is missing",
21+
ErrorCodes.MissingExtractId,
22+
ValidationErrorScope.Header
23+
);
24+
}
25+
26+
[Theory]
27+
[InlineData("1")] // Missing leading zeroes
28+
[InlineData("100000000")] // Too large
29+
[InlineData("")] // Blank
30+
[InlineData("asdf")] // NaN
31+
public void Validate_HeaderExtractIdInvalidFormat_ReturnsValidationError(string value)
32+
{
33+
// Arrange
34+
var file = ValidParsedFile;
35+
file.FileHeader!.ExtractId = value;
36+
37+
// Act
38+
var validationErrors = Validate(file).ToList();
39+
40+
// Assert
41+
validationErrors.ShouldContainValidationError(
42+
"Extract ID",
43+
"Extract ID is in an invalid format",
44+
ErrorCodes.InvalidExtractId,
45+
ValidationErrorScope.Header
46+
);
47+
}
48+
49+
[Theory]
50+
[InlineData("00000000")]
51+
[InlineData("00000001")]
52+
[InlineData("99999999")]
53+
public void Validate_HeaderExtractIdValidFormat_NoValidationErrorsReturned(string value)
54+
{
55+
// Arrange
56+
var file = ValidParsedFile;
57+
file.FileHeader!.ExtractId = value;
58+
file.FileTrailer!.ExtractId = value;
59+
60+
// Act
61+
var validationErrors = Validate(file).ToList();
62+
63+
// Assert
64+
Assert.Empty(validationErrors);
65+
}
66+
67+
[Theory]
68+
[InlineData(null)]
69+
[InlineData("")]
70+
[InlineData("00000108")]
71+
public void Validate_TrailerExtractIdMismatch_ReturnsValidationError(string? value)
72+
{
73+
// Arrange
74+
var file = ValidParsedFile;
75+
file.FileTrailer!.ExtractId = value;
76+
77+
// Act
78+
var validationErrors = Validate(file).ToList();
79+
80+
// Assert
81+
validationErrors.ShouldContainValidationError(
82+
"Extract ID",
83+
"Extract ID does not match value in header",
84+
ErrorCodes.InconsistentExtractId,
85+
ValidationErrorScope.Trailer
86+
);
87+
}
88+
}

0 commit comments

Comments
 (0)