Skip to content

Commit

Permalink
Merge pull request #913 from DFE-Digital/bugs/fix-bulk-edit-date-vali…
Browse files Browse the repository at this point in the history
…dation

Fix - Bulk edit date validation logic
  • Loading branch information
sukhybhullar-nimble authored Oct 16, 2024
2 parents 6ea8b2d + 28b985d commit bfc9d01
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dfe.ManageFreeSchoolProjects.API.UseCases.BulkEdit.Validations;
using Dfe.ManageFreeSchoolProjects.API.UseCases.BulkEdit;
using Dfe.ManageFreeSchoolProjects.API.UseCases.BulkEdit.Validations;

namespace Dfe.ManageFreeSchoolProjects.API.Tests.UseCases.BulkEdit.Validation
{
Expand All @@ -10,29 +11,29 @@ public class DateValidationCommandTests
[InlineData("12-13-2025", "Enter a valid date. For example, 27/03/2021")]
[InlineData("12/10/2051", "Year must be between 2000 and 2050")]
[InlineData("12/10/1999", "Year must be between 2000 and 2050")]
[InlineData("31/04/2024", "Day must be between 1 and 30 for the given month.")]
[InlineData("29/02/2023", "Day must be between 1 and 28 for the given month.")]
[InlineData("00/10/2025", "Day must be a valid number between 1 and 31")]
[InlineData("12/00/2025", "Month must be a valid number between 1 and 12")]
[InlineData("12/13/2025", "Month must be a valid number between 1 and 12")]
public void DateValidationFails(string date, string error)
[InlineData("31/04/2024", "Day must be between 1 and 30")]
[InlineData("29/02/2023", "Day must be between 1 and 28")]
[InlineData("00/10/2025", "Day must be between 1 and 31")]
[InlineData("12/00/2025", "Month must be between 1 and 12")]
[InlineData("12/13/2025", "Month must be between 1 and 12")]
public void DateValidationFails(string date, string expectedErrorMessage)
{
var dateValidation = new DateValidationCommand();
var validationResult = dateValidation.Execute(new() { Data = null, Value = date });
var validationResult = dateValidation.Execute(new ValidationCommandParameters<BulkEditDto> { Data = null, Value = date });

validationResult.IsValid.Should().BeFalse();
validationResult.ErrorMessage.Should().Be(error);
validationResult.ErrorMessage.Should().Be(expectedErrorMessage);
}

[Theory]
[InlineData("12/10/2025")]
[InlineData("29/02/2024")]
[InlineData("29/02/2024")] // Leap year test
[InlineData("31/12/2050")]
[InlineData("01/01/2000")]
public void DateValidationPasses(string date)
{
var dateValidation = new DateValidationCommand();
var validationResult = dateValidation.Execute(new() { Data = null, Value = date });
var validationResult = dateValidation.Execute(new ValidationCommandParameters<BulkEditDto> { Data = null, Value = date });

validationResult.IsValid.Should().BeTrue();
validationResult.ErrorMessage.Should().BeNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,75 @@
using System.Globalization;

namespace Dfe.ManageFreeSchoolProjects.API.UseCases.BulkEdit.Validations
namespace Dfe.ManageFreeSchoolProjects.API.UseCases.BulkEdit.Validations
{
public class DateValidationCommand : IValidationCommand<BulkEditDto>
{
public ValidationResult Execute(ValidationCommandParameters<BulkEditDto> parameters)
{
var actualOpeningDateString = parameters.Value;
var dateParts = CleanAndSplitDate(parameters.Value);

var dateParts = actualOpeningDateString.Split('/');
if (dateParts.Length != 3)
return new ValidationResult
{ IsValid = false, ErrorMessage = "Enter a valid date. For example, 27/03/2021" };
if (!IsValidDateFormat(dateParts))
return CreateValidationResult(false, "Enter a valid date. For example, 27/03/2021");

var dateObject = new { Day = dateParts[0], Month = dateParts[1], Year = dateParts[2] };

var missingParts = new List<string>();
var (day, month, year) = (dateParts[0], dateParts[1], dateParts[2]);

if (string.IsNullOrEmpty(dateObject.Day)) missingParts.Add("day");
if (string.IsNullOrEmpty(dateObject.Month)) missingParts.Add("month");
if (string.IsNullOrEmpty(dateObject.Year)) missingParts.Add("year");
var missingPartsMessage = CheckForMissingDateParts(day, month, year);
if (missingPartsMessage != null)
return CreateValidationResult(false, missingPartsMessage);

if (missingParts.Count == 3)
return new ValidationResult
{ IsValid = false, ErrorMessage = "Date must include a day, month, and year" };
if (!IsValidMonth(month, out var monthNumber))
return CreateValidationResult(false, "Month must be between 1 and 12");

if (missingParts.Count > 0)
return new ValidationResult
{ IsValid = false, ErrorMessage = $"Date must include a {string.Join(" and ", missingParts)}" };
if (!IsValidYear(year, out var yearNumber))
return CreateValidationResult(false, "Year must be between 2000 and 2050");

if (!int.TryParse(dateObject.Day, out var day) || day < 1 || day > 31)
return new ValidationResult
{ IsValid = false, ErrorMessage = "Day must be a valid number between 1 and 31" };
if (!IsValidDay(day, yearNumber, monthNumber, out _))
return CreateValidationResult(false,
$"Day must be between 1 and {DateTime.DaysInMonth(yearNumber, monthNumber)}");

if (!int.TryParse(dateObject.Month, out var month) || month < 1 || month > 12)
return new ValidationResult
{ IsValid = false, ErrorMessage = "Month must be a valid number between 1 and 12" };
return CreateValidationResult(true, null);
}

if (!int.TryParse(dateObject.Year, out var year) || year < 2000 || year > 2050)
return new ValidationResult { IsValid = false, ErrorMessage = "Year must be between 2000 and 2050" };
private static string[] CleanAndSplitDate(string date)
{
if (!date.Contains('/'))
return [date];

var daysInCurrentMonth = DateTime.DaysInMonth(year, month);
if (day > daysInCurrentMonth)
return new ValidationResult
{
IsValid = false,
ErrorMessage = $"Day must be between 1 and {daysInCurrentMonth} for the given month."
};
var dateParts = date.Split('/');
if (dateParts[2].EndsWith("00:00:00"))
dateParts[2] = dateParts[2][..^" 00:00:00".Length];

return dateParts;
}

return new ValidationResult { IsValid = true };
private static bool IsValidDateFormat(string[] dateParts) => dateParts.Length == 3;

private static string CheckForMissingDateParts(string day, string month, string year)
{
var missingParts = new List<string>();
if (string.IsNullOrEmpty(day)) missingParts.Add("day");
if (string.IsNullOrEmpty(month)) missingParts.Add("month");
if (string.IsNullOrEmpty(year)) missingParts.Add("year");

if (missingParts.Count == 3)
return "Date must include a day, month, and year";
if (missingParts.Count > 0)
return $"Date must include a {string.Join(" and ", missingParts)}";
return null;
}

private static bool IsValidMonth(string month, out int monthNumber) =>
int.TryParse(month, out monthNumber) && monthNumber is >= 1 and <= 12;

private static bool IsValidYear(string year, out int yearNumber) =>
int.TryParse(year, out yearNumber) && yearNumber is >= 2000 and <= 2050;

private static bool IsValidDay(string day, int year, int month, out int dayNumber)
{
var isValid = int.TryParse(day, out dayNumber) && dayNumber >= 1 &&
dayNumber <= DateTime.DaysInMonth(year, month);
return isValid;
}

private static ValidationResult CreateValidationResult(bool isValid, string message) =>
new() { IsValid = isValid, ErrorMessage = message };
}
}

0 comments on commit bfc9d01

Please sign in to comment.