Repository: companieshouse/acsp-web
Pull request #734: IDVA5-1970 fixing back button and 100 years validation
Author: login: rdas-ch
Created: 2025-03-28T09:18:02Z | Merged: 2025-04-04T13:41:11Z

IDVA5-1970, Test Issue fixes

- Cancel button should bring back the main update page.
- Fixing the validation from 110 years to 100 years.
- The JS issue of the date prior to 1970 is also fixed. Ryan fixed the similar issue earlier in the below PR
https://github.com/companieshouse/acsp-confirm-client-id-verification-web/pull/128


Diff:
diff --git a/locales/cy/update-acsp-details-change-effecting-date.json b/locales/cy/update-acsp-details-change-effecting-date.json
index 9bbeaf5a2..260486021 100644
--- a/locales/cy/update-acsp-details-change-effecting-date.json
+++ b/locales/cy/update-acsp-details-change-effecting-date.json
@@ -14,6 +14,6 @@
     "error-noChangeDateDay" : "Change start date must include a day Welsh",
     "error-noChangeDateMonth" : "Change start date must include a month Welsh",
     "error-noChangeDateYear" : "Change start date must include a year Welsh",
-    "error-tooChangeDateOld" : "The change must be less than 110 years Welsh",
+    "error-tooChangeDateOld" : "The change must be less than 100 years Welsh",
     "error-nonChangeDateNumeric" : "Change start date must only include numbers Welsh"
 }
\ No newline at end of file
diff --git a/locales/en/update-acsp-details-change-effecting-date.json b/locales/en/update-acsp-details-change-effecting-date.json
index 53164b1fa..375b9a9f1 100644
--- a/locales/en/update-acsp-details-change-effecting-date.json
+++ b/locales/en/update-acsp-details-change-effecting-date.json
@@ -14,6 +14,6 @@
     "error-noChangeDateDay" : "Change start date must include a day",
     "error-noChangeDateMonth" : "Change start date must include a month",
     "error-noChangeDateYear" : "Change start date must include a year",
-    "error-tooChangeDateOld" : "The change must be less than 110 years",
+    "error-tooChangeDateOld" : "The change must be less than 100 years",
     "error-nonChangeDateNumeric" : "Change start date must only include numbers"
 }
\ No newline at end of file
diff --git a/src/controllers/features/update-acsp/dateOfTheChangeController.ts b/src/controllers/features/update-acsp/dateOfTheChangeController.ts
index 356a93999..3382243f4 100644
--- a/src/controllers/features/update-acsp/dateOfTheChangeController.ts
+++ b/src/controllers/features/update-acsp/dateOfTheChangeController.ts
@@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from "express";
 import * as config from "../../../config";
 import { validationResult } from "express-validator";
 import { formatValidationError, getPageProperties } from "../../../validation/validation";
-import { UPDATE_ACSP_DETAILS_BASE_URL, UPDATE_DATE_OF_THE_CHANGE, UPDATE_CHECK_YOUR_UPDATES } from "../../../types/pageURL";
+import { UPDATE_ACSP_DETAILS_BASE_URL, UPDATE_DATE_OF_THE_CHANGE, UPDATE_CHECK_YOUR_UPDATES, UPDATE_YOUR_ANSWERS } from "../../../types/pageURL";
 import { selectLang, addLangToUrl, getLocalesService, getLocaleInfo } from "../../../utils/localise";
 import { getPreviousPageUrl } from "../../../services/url";
 import { updateWithTheEffectiveDateAmendment } from "../../../services/update-acsp/dateOfTheChangeService";
@@ -11,12 +11,14 @@ export const get = async (req: Request, res: Response, next: NextFunction) => {
     try {
         const lang = selectLang(req.query.lang);
         const locales = getLocalesService();
+        const cancelTheUpdateUrl = addLangToUrl(UPDATE_ACSP_DETAILS_BASE_URL + UPDATE_YOUR_ANSWERS, lang);
         const prevUrl = getPreviousPageUrl(req, UPDATE_ACSP_DETAILS_BASE_URL) || UPDATE_ACSP_DETAILS_BASE_URL;
         const previousPage: string = addLangToUrl(prevUrl, lang);
         const currentUrl: string = UPDATE_ACSP_DETAILS_BASE_URL + UPDATE_DATE_OF_THE_CHANGE;
         res.render(config.UPDATE_DATE_OF_THE_CHANGE, {
             ...getLocaleInfo(locales, lang),
             previousPage,
+            cancelTheUpdateUrl,
             currentUrl
         });
     } catch (err) {
@@ -29,6 +31,7 @@ export const post = async (req: Request, res: Response, next: NextFunction) => {
         const errorList = validationResult(req);
         const lang = selectLang(req.query.lang);
         const locales = getLocalesService();
+        const cancelTheUpdateUrl = addLangToUrl(UPDATE_ACSP_DETAILS_BASE_URL + UPDATE_YOUR_ANSWERS, lang);
         const currentUrl: string = UPDATE_ACSP_DETAILS_BASE_URL + UPDATE_DATE_OF_THE_CHANGE;
         const prevUrl = getPreviousPageUrl(req, UPDATE_ACSP_DETAILS_BASE_URL) || UPDATE_ACSP_DETAILS_BASE_URL;
         const previousPage: string = addLangToUrl(prevUrl, lang);
@@ -38,6 +41,7 @@ export const post = async (req: Request, res: Response, next: NextFunction) => {
                 previousPage,
                 ...getLocaleInfo(locales, lang),
                 currentUrl,
+                cancelTheUpdateUrl,
                 pageProperties: pageProperties,
                 payload: req.body
             });
@@ -46,7 +50,7 @@ export const post = async (req: Request, res: Response, next: NextFunction) => {
                 req.body["change-year"],
                 req.body["change-month"] - 1,
                 req.body["change-day"]);
-            updateWithTheEffectiveDateAmendment(req, dateOfChange);
+            updateWithTheEffectiveDateAmendment(req, dateOfChange.toISOString());
             res.redirect(addLangToUrl(UPDATE_ACSP_DETAILS_BASE_URL + UPDATE_CHECK_YOUR_UPDATES, lang));
         }
     } catch (err) {
diff --git a/src/services/update-acsp/dateOfTheChangeService.ts b/src/services/update-acsp/dateOfTheChangeService.ts
index 07bd61357..8216ec497 100644
--- a/src/services/update-acsp/dateOfTheChangeService.ts
+++ b/src/services/update-acsp/dateOfTheChangeService.ts
@@ -17,7 +17,7 @@ import {
 } from "../../types/pageURL";
 import { soleTraderNameDetails } from "../../model/SoleTraderNameDetails";
 
-export const updateWithTheEffectiveDateAmendment = (req: Request, dateOfChange: Date): void => {
+export const updateWithTheEffectiveDateAmendment = (req: Request, dateOfChange: string): void => {
     const session: Session = req.session as any as Session;
     const acspUpdatedFullProfile: AcspFullProfile = session.getExtraData(ACSP_DETAILS_UPDATED)!;
     const currentPage = session.getExtraData(ACSP_DETAILS_UPDATE_ELEMENT);
diff --git a/src/validation/dateOfACSPUpdateDetailsChange.ts b/src/validation/dateOfACSPUpdateDetailsChange.ts
index e1d48498e..c90bb646b 100644
--- a/src/validation/dateOfACSPUpdateDetailsChange.ts
+++ b/src/validation/dateOfACSPUpdateDetailsChange.ts
@@ -50,6 +50,8 @@ export const validDataChecker = (day: string, month: string, year: string, type:
         validateDate(day, month, year, type);
         if (type === "dob") {
             validateDobAge(day, month, year);
+        } else if (type === "change") {
+            validateDateOfChangeLimit(day, month, year);
         }
     }
     return true;
@@ -88,6 +90,12 @@ const validateDobAge = (day: string, month: string, year: string): void => {
     }
 };
 
+const validateDateOfChangeLimit = (day: string, month: string, year: string): void => {
+    if (!notMoreThanACentury(+day, +month, +year)) {
+        throw new Error("tooChangeDateOld");
+    }
+};
+
 const isValidDay = (day: number, month: number, year: number): boolean => {
     const numbDays = new Date(year, month, 0).getDate();
     return day >= 1 && day <= numbDays;
@@ -119,6 +127,16 @@ export const isNotTooOld = (day: number, month: number, year: number): boolean =
     return age <= 110;
 };
 
+const notMoreThanACentury = (day: number, month: number, year: number): boolean => {
+    const currentDate = new Date();
+    const inputDate = new Date(year, month - 1, day);
+    let deepDifference = currentDate.getFullYear() - inputDate.getFullYear();
+    if (currentDate.getMonth() < inputDate.getMonth() || (currentDate.getMonth() === inputDate.getMonth() && currentDate.getDate() < inputDate.getDate())) {
+        deepDifference--;
+    }
+    return deepDifference <= 100;
+};
+
 const isNumeric = (number: string): boolean => {
     const regex = /^\d+$/;
     return regex.test(number);
diff --git a/src/views/features/update-acsp-details/date-of-the-change/date-of-the-change.njk b/src/views/features/update-acsp-details/date-of-the-change/date-of-the-change.njk
index 0f579c893..58e2df497 100644
--- a/src/views/features/update-acsp-details/date-of-the-change/date-of-the-change.njk
+++ b/src/views/features/update-acsp-details/date-of-the-change/date-of-the-change.njk
@@ -69,7 +69,7 @@
                 text: i18n.Continue,
                 id:"update-acsp-details"
                 }) }}
-            <a class="govuk-link" href="{{ cancelAllUpdatesUrl }}" id="cancel-id">{{i18n.Cancel}}</a>
+            <a class="govuk-link" href="{{ cancelTheUpdateUrl }}" id="cancel-id">{{i18n.Cancel}}</a>
         </div>
     </form>
   </div>
diff --git a/test/src/services/update_acsp/dateOfTheChangeService.test.ts b/test/src/services/update_acsp/dateOfTheChangeService.test.ts
index d45a5211b..f4d19d408 100644
--- a/test/src/services/update_acsp/dateOfTheChangeService.test.ts
+++ b/test/src/services/update_acsp/dateOfTheChangeService.test.ts
@@ -35,7 +35,7 @@ describe("updateWithTheEffectiveDateAmendment", () => {
     });
 
     it("should update the business name and set the change date", () => {
-        const dateOfChange = new Date();
+        const dateOfChange = new Date("2025-04-03T14:24:38.901Z");
         const acspInProgress = { name: "New Business Name" };
         const acspUpdated = { name: "Old Business Name" };
 
@@ -46,10 +46,10 @@ describe("updateWithTheEffectiveDateAmendment", () => {
                 if (key === ACSP_DETAILS_UPDATE_ELEMENT) return UPDATE_WHAT_IS_THE_BUSINESS_NAME;
             });
 
-        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange);
+        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange.toISOString());
 
         expect(acspUpdated.name).toBe(acspInProgress);
-        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.NAME_OF_BUSINESS, dateOfChange);
+        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.NAME_OF_BUSINESS, dateOfChange.toISOString());
     });
 
     it("should update sole trader details and set the change date", () => {
@@ -82,12 +82,12 @@ describe("updateWithTheEffectiveDateAmendment", () => {
             if (key === ACSP_DETAILS_UPDATE_ELEMENT) return UPDATE_ACSP_WHAT_IS_YOUR_NAME;
         });
 
-        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange);
+        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange.toISOString());
 
         expect(acspUpdatedFullProfile.soleTraderDetails!.forename).toBe(acspinProgressFullProfile.forename);
         expect(acspUpdatedFullProfile.soleTraderDetails!.otherForenames).toBe(acspinProgressFullProfile.otherForenames);
         expect(acspUpdatedFullProfile.soleTraderDetails!.surname).toBe(acspinProgressFullProfile.surname);
-        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.NAME, dateOfChange);
+        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.NAME, dateOfChange.toISOString());
         expect(session.deleteExtraData).toHaveBeenCalledWith(ACSP_DETAILS_UPDATE_IN_PROGRESS);
     });
 
@@ -103,10 +103,10 @@ describe("updateWithTheEffectiveDateAmendment", () => {
                 if (key === ACSP_DETAILS_UPDATE_ELEMENT) return UPDATE_WHERE_DO_YOU_LIVE;
             });
 
-        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange);
+        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange.toISOString());
 
         expect(acspUpdated.soleTraderDetails!.usualResidentialCountry).toBe(acspInProgress);
-        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.WHERE_DO_YOU_LIVE, dateOfChange);
+        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.WHERE_DO_YOU_LIVE, dateOfChange.toISOString());
     });
 
     it("should update the registered office address and set the change date", () => {
@@ -121,10 +121,10 @@ describe("updateWithTheEffectiveDateAmendment", () => {
                 if (key === ACSP_DETAILS_UPDATE_ELEMENT) return UPDATE_BUSINESS_ADDRESS_CONFIRM;
             });
 
-        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange);
+        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange.toISOString());
 
         expect(acspUpdated.registeredOfficeAddress).toEqual(acspInProgress);
-        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.REGISTERED_OFFICE_ADDRESS, dateOfChange);
+        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.REGISTERED_OFFICE_ADDRESS, dateOfChange.toISOString());
     });
 
     it("should update serviceAddress for non-sole trader and set the change date", () => {
@@ -143,9 +143,9 @@ describe("updateWithTheEffectiveDateAmendment", () => {
             if (key === ACSP_DETAILS_UPDATE_ELEMENT) return UPDATE_CORRESPONDENCE_ADDRESS_CONFIRM;
         });
 
-        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange);
+        updateWithTheEffectiveDateAmendment(req as Request, dateOfChange.toISOString());
 
         expect(acspUpdatedFullProfile.serviceAddress).toEqual(acspinProgressFullProfile);
-        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.CORRESPONDENCE_ADDRESS, dateOfChange);
+        expect(session.setExtraData).toHaveBeenCalledWith(ACSP_UPDATE_CHANGE_DATE.CORRESPONDENCE_ADDRESS, dateOfChange.toISOString());
     });
 });
diff --git a/test/src/validation/dateOfACSPUpdateDetailsChange.test.ts b/test/src/validation/dateOfACSPUpdateDetailsChange.test.ts
index f9e0acf3c..d524760ff 100644
--- a/test/src/validation/dateOfACSPUpdateDetailsChange.test.ts
+++ b/test/src/validation/dateOfACSPUpdateDetailsChange.test.ts
@@ -1,256 +1,181 @@
-import {
-    dateDayChecker,
-    dateMonthChecker,
-    dateYearChecker,
-    validDataChecker,
-    isNotTooYoung,
-    isNotTooOld
-} from "../../../src/validation/dateOfACSPUpdateDetailsChange";
-
-describe("Missing input validation tests", () => {
-    test("Error if date field is completely empty", () => {
-        expect(() => dateDayChecker("", "", "", "dob")).toThrow(new Error("noData"));
-    });
-
-    test("Error if date field is completely empty", () => {
-        expect(() => dateDayChecker("", "", "", "change")).toThrow(new Error("noChangeDateData"));
-    });
-
-    test("Error if day and month fields are empty", () => {
-        expect(() => dateDayChecker("", "", "1999", "dob")).toThrow(new Error("noDayMonth"));
-    });
-
-    test("Error if day and month fields are empty", () => {
-        expect(() => dateDayChecker("", "", "1999", "change")).toThrow(new Error("noChangeDateYear"));
-    });
-
-    test("Error if day and year fields are empty", () => {
-        expect(() => dateDayChecker("", "02", "", "dob")).toThrow(new Error("noDayYear"));
-    });
+import { dateOfACSPUpdateDetailsChange, dateDayChecker, dateMonthChecker, dateYearChecker, validDataChecker } from "../../../src/validation/dateOfACSPUpdateDetailsChange";
+import { body } from "express-validator";
 
-    test("Error if day and year fields are empty", () => {
-        expect(() => dateDayChecker("", "02", "", "change")).toThrow(new Error("noChangeDateMonth"));
+describe("dateOfACSPUpdateDetailsChange", () => {
+    it("should return an array of validation chains for 'dob'", () => {
+        const result = dateOfACSPUpdateDetailsChange("dob");
+        expect(result).toHaveLength(4);
+        result.forEach((chain) => {
+            expect(chain).toBeInstanceOf(body("").constructor);
+        });
     });
 
-    test("Error if month and year fields are empty", () => {
-        expect(() => dateMonthChecker("11", "", "", "dob")).toThrow(new Error("noMonthYear"));
-    });
-
-    test("Error if month and year fields are empty", () => {
-        expect(() => dateMonthChecker("11", "", "", "change")).toThrow(new Error("noChangeDateMonthYear"));
-    });
-
-    test("Error if day field is empty", () => {
-        expect(() => dateDayChecker("", "02", "1999", "dob")).toThrow(new Error("noDay"));
-    });
-
-    test("Error if day field is empty", () => {
-        expect(() => dateDayChecker("", "02", "1999", "change")).toThrow(new Error("noChangeDateDay"));
+    it("should return an array of validation chains for 'change'", () => {
+        const result = dateOfACSPUpdateDetailsChange("change");
+        expect(result).toHaveLength(4);
+        result.forEach((chain) => {
+            expect(chain).toBeInstanceOf(body("").constructor);
+        });
     });
+});
 
-    test("Error if month field is empty", () => {
-        expect(() => dateMonthChecker("11", "", "1999", "dob")).toThrow(new Error("noMonth"));
+describe("dateDayChecker", () => {
+    it("should throw an error if day, month, and year are empty", () => {
+        expect(() => dateDayChecker("", "", "", "dob")).toThrow("noData");
+        expect(() => dateDayChecker("", "", "", "change")).toThrow("noChangeDateData");
     });
 
-    test("Error if month field is empty", () => {
-        expect(() => dateMonthChecker("11", "", "1999", "change")).toThrow(new Error("noChangeDateMonthYear"));
+    it("should throw an error if day and month are empty", () => {
+        expect(() => dateDayChecker("", "", "2023", "dob")).toThrow("noDayMonth");
+        expect(() => dateDayChecker("", "", "2023", "change")).toThrow("noChangeDateYear");
     });
 
-    test("Error if year field is empty", () => {
-        expect(() => dateYearChecker("11", "02", "", "dob")).toThrow(new Error("noYear"));
+    it("should throw an error if day and year are empty", () => {
+        expect(() => dateDayChecker("", "12", "", "dob")).toThrow("noDayYear");
+        expect(() => dateDayChecker("", "12", "", "change")).toThrow("noChangeDateMonth");
     });
 
-    test("Error if year field is empty", () => {
-        expect(() => dateYearChecker("11", "02", "", "change")).toThrow(new Error("noChangeDateDayYear"));
+    it("should throw an error if day is empty", () => {
+        expect(() => dateDayChecker("", "12", "2023", "dob")).toThrow("noDay");
+        expect(() => dateDayChecker("", "12", "2023", "change")).toThrow("noChangeDateDay");
     });
 
-    test("No error if all fields are input", () => {
-        expect(() => dateDayChecker("11", "02", "1999", "dob")).not.toThrow();
-        expect(() => dateMonthChecker("11", "02", "1999", "dob")).not.toThrow();
-        expect(() => dateYearChecker("11", "02", "1999", "dob")).not.toThrow();
+    it("should return true for valid day, month, and year", () => {
+        expect(dateDayChecker("01", "12", "2023", "dob")).toBe(true);
     });
 });
 
-describe("Valid data input tests", () => {
-    test("Error if year is greater than 9999", () => {
-        expect(() => validDataChecker("11", "11", "10000", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if year is greater than 9999", () => {
-        expect(() => validDataChecker("11", "11", "10000", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Error if year is less than 1000", () => {
-        expect(() => validDataChecker("11", "11", "999", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if year is less than 1000", () => {
-        expect(() => validDataChecker("11", "11", "999", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Error if month is greater than 12", () => {
-        expect(() => validDataChecker("11", "13", "1999", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if month is greater than 12", () => {
-        expect(() => validDataChecker("11", "13", "1999", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Error if day is not valid for month", () => {
-        expect(() => validDataChecker("30", "02", "1999", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if day is not valid for month", () => {
-        expect(() => validDataChecker("30", "02", "1999", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Error if date given is in the future", () => {
-        expect(() => validDataChecker("30", "11", "3490", "dob")).toThrow(new Error("dateInFuture"));
+describe("dateMonthChecker", () => {
+    it("should throw an error if day is not empty but month and year are empty", () => {
+        expect(() => dateMonthChecker("01", "", "", "dob")).toThrow("noMonthYear");
+        expect(() => dateMonthChecker("01", "", "", "change")).toThrow("noChangeDateMonthYear");
     });
 
-    test("Error if date given is in the future", () => {
-        expect(() => validDataChecker("30", "11", "3490", "change")).toThrow(new Error("dateInFutureChangeDate"));
+    it("should throw an error if day is not empty but month is empty", () => {
+        expect(() => dateMonthChecker("01", "", "2023", "dob")).toThrow("noMonth");
+        expect(() => dateMonthChecker("01", "", "2023", "change")).toThrow("noChangeDateMonthYear");
     });
 
-    test("Error if date given is more than 110 years ago", () => {
-        expect(() => validDataChecker("30", "11", "1490", "dob")).toThrow(new Error("tooChangeDateOld"));
+    it("should return true for valid day, month, and year", () => {
+        expect(dateMonthChecker("01", "12", "2023", "dob")).toBe(true);
     });
+});
 
-    test("Error if date given is less than 16 years ago", () => {
-        expect(() => validDataChecker("29", "01", "2024", "dob")).toThrow(new Error("tooYoung"));
+describe("dateYearChecker", () => {
+    it("should throw an error if day and month are not empty but year is empty", () => {
+        expect(() => dateYearChecker("01", "12", "", "dob")).toThrow("noYear");
+        expect(() => dateYearChecker("01", "12", "", "change")).toThrow("noChangeDateDayYear");
     });
 
-    test("Error if date given is non-numeric", () => {
-        expect(() => validDataChecker("a", "01", "2024", "dob")).toThrow(new Error("nonNumeric"));
+    it("should return true for valid day, month, and year", () => {
+        expect(dateYearChecker("01", "12", "2023", "dob")).toBe(true);
     });
+});
 
-    test("Error if date given is non-numeric", () => {
-        expect(() => validDataChecker("a", "01", "2024", "change")).toThrow(new Error("nonChangeDateNumeric"));
+describe("validDataChecker", () => {
+    it("should throw an error if day, month, or year are non-numeric", () => {
+        expect(() => validDataChecker("a", "12", "2023", "dob")).toThrow("nonNumeric");
+        expect(() => validDataChecker("01", "b", "2023", "change")).toThrow("nonChangeDateNumeric");
     });
 
-    test("No error for valid date within acceptable range", () => {
-        expect(() => validDataChecker("15", "07", "2000", "dob")).toBeTruthy();
+    it("should throw an error if month or year are out of range", () => {
+        expect(() => validDataChecker("01", "13", "2023", "dob")).toThrow("invalid");
+        expect(() => validDataChecker("01", "12", "99999", "change")).toThrow("invalidChangeDate");
     });
 
-    test("Error if day length is more than 2 digits", () => {
-        expect(() => validDataChecker("123", "01", "2024", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if day length is more than 2 digits", () => {
-        expect(() => validDataChecker("123", "01", "2024", "change")).toThrow(new Error("invalidChangeDate"));
+    it("should throw an error if day is invalid for the given month and year", () => {
+        expect(() => validDataChecker("32", "12", "2023", "dob")).toThrow("invalid");
+        expect(() => validDataChecker("30", "02", "2023", "change")).toThrow("invalidChangeDate");
     });
 
-    test("Error if month is zero", () => {
-        expect(() => validDataChecker("11", "00", "1999", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if month is zero", () => {
-        expect(() => validDataChecker("11", "00", "1999", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Error if day is zero", () => {
-        expect(() => validDataChecker("00", "01", "1999", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Error if day is zero", () => {
-        expect(() => validDataChecker("00", "01", "1999", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Leap year test: valid leap day", () => {
-        expect(() => validDataChecker("29", "02", "2020", "dob")).toBeTruthy();
-    });
-    test("Leap year test: valid leap day", () => {
-        expect(() => validDataChecker("29", "02", "2020", "change")).toBeTruthy();
-    });
-    test("Leap year test: invalid leap day", () => {
-        expect(() => validDataChecker("29", "02", "2019", "dob")).toThrow(new Error("invalid"));
-    });
-    test("Leap year test: invalid leap day", () => {
-        expect(() => validDataChecker("29", "02", "2019", "change")).toThrow(new Error("invalidChangeDate"));
-    });
-    test("Boundary test: 110 years ago exactly", () => {
-        const currentDate = new Date();
-        const year = currentDate.getFullYear() - 110;
-        const month = (`0${currentDate.getMonth() + 1}`).slice(-2);
-        const day = (`0${currentDate.getDate()}`).slice(-2);
-        expect(() => validDataChecker(day, month, year.toString(), "dob")).toBeTruthy();
+    it("should throw an error if the date is in the future", () => {
+        const futureYear = new Date().getFullYear() + 1;
+        expect(() => validDataChecker("01", "12", futureYear.toString(), "dob")).toThrow("dateInFuture");
+        expect(() => validDataChecker("01", "12", futureYear.toString(), "change")).toThrow("dateInFutureChangeDate");
     });
 
-    test("Boundary test: 16 years ago exactly", () => {
-        const currentDate = new Date();
-        const year = currentDate.getFullYear() - 16;
-        const month = (`0${currentDate.getMonth() + 1}`).slice(-2);
-        const day = (`0${currentDate.getDate()}`).slice(-2);
-        expect(() => validDataChecker(day, month, year.toString(), "dob")).toBeTruthy();
+    it("should throw an error if the age is too young for 'dob'", () => {
+        const currentYear = new Date().getFullYear();
+        expect(() => validDataChecker("01", "12", (currentYear - 15).toString(), "dob")).toThrow("tooYoung");
     });
 
-    test("Error if year has non-numeric characters", () => {
-        expect(() => validDataChecker("11", "01", "20a0", "dob")).toThrow(new Error("nonNumeric"));
+    it("should throw an error if the age is too old for 'dob'", () => {
+        const currentYear = new Date().getFullYear();
+        expect(() => validDataChecker("01", "12", (currentYear - 111).toString(), "dob")).toThrow("tooChangeDateOld");
     });
 
-    test("Error if month has non-numeric characters", () => {
-        expect(() => validDataChecker("11", "a1", "2020", "dob")).toThrow(new Error("nonNumeric"));
+    it("should throw an error if the date of change is more than a century old", () => {
+        const currentYear = new Date().getFullYear();
+        expect(() => validDataChecker("01", "12", (currentYear - 150).toString(), "change")).toThrow("tooChangeDateOld");
     });
 
-    test("Error if day has non-numeric characters", () => {
-        expect(() => validDataChecker("1a", "01", "2020", "dob")).toThrow(new Error("nonNumeric"));
+    it("should return true for valid day, month, and year", () => {
+        expect(validDataChecker("01", "12", "2000", "dob")).toBe(true);
+        expect(validDataChecker("01", "12", "2023", "change")).toBe(true);
     });
 });
+describe("Month and Day Adjustment Logic", () => {
+    it("should decrement deepDifference if the input date month is greater than the current date month", () => {
+        const currentDate = new Date(2025, 5, 15);
+        const inputDate = new Date(1925, 6, 15);
 
-describe("Age calculation tests", () => {
-    test("Calculate age - current date is greater than input date", () => {
-        const currentDate = new Date();
-        const inputDate = new Date("2000-07-01");
-        expect(isNotTooOld(inputDate.getDate(), inputDate.getMonth() + 1, inputDate.getFullYear())).toBe(true);
-    });
+        let deepDifference = currentDate.getFullYear() - inputDate.getFullYear();
 
-    test("Calculate age - current date is exactly 16 years ago", () => {
-        const currentDate = new Date();
-        const year = currentDate.getFullYear() - 16;
-        const month = (`0${currentDate.getMonth() + 1}`).slice(-2);
-        const day = (`0${currentDate.getDate()}`).slice(-2);
-        expect(isNotTooYoung(+day, +month, +year.toString())).toBe(true);
-    });
+        if (
+            currentDate.getMonth() < inputDate.getMonth() ||
+            (currentDate.getMonth() === inputDate.getMonth() && currentDate.getDate() < inputDate.getDate())
+        ) {
+            deepDifference--;
+        }
 
-    test("Calculate age - current date is less than 16 years ago", () => {
-        const currentDate = new Date();
-        const year = currentDate.getFullYear() - 15;
-        const month = (`0${currentDate.getMonth() + 1}`).slice(-2);
-        const day = (`0${currentDate.getDate()}`).slice(-2);
-        expect(isNotTooYoung(+day, +month, +year.toString())).toBe(false);
+        expect(deepDifference).toBe(99);
     });
-});
-
-describe("isNotTooOld", () => {
-    it("should increment age if current month is greater than input month", () => {
-        const currentDate = new Date();
-        const inputDate = new Date(currentDate.getFullYear() - 110, currentDate.getMonth() + 1, currentDate.getDate());
 
-        const result = isNotTooOld(inputDate.getDate(), inputDate.getMonth() + 1, inputDate.getFullYear());
+    it("should decrement deepDifference if the input date day is greater than the current date day in the same month", () => {
+        const currentDate = new Date(2025, 5, 15);
+        const inputDate = new Date(1925, 5, 20);
 
-        expect(result).toBe(true);
-    });
+        let deepDifference = currentDate.getFullYear() - inputDate.getFullYear();
 
-    it("should increment age if current month is equal to input month and current day is greater than input day", () => {
-        const currentDate = new Date();
-        const inputDate = new Date(currentDate.getFullYear() - 110, currentDate.getMonth() + 1, currentDate.getDate() + 1);
+        if (
+            currentDate.getMonth() < inputDate.getMonth() ||
+            (currentDate.getMonth() === inputDate.getMonth() && currentDate.getDate() < inputDate.getDate())
+        ) {
+            deepDifference--;
+        }
 
-        const result = isNotTooOld(inputDate.getDate(), inputDate.getMonth(), inputDate.getFullYear());
-
-        expect(result).toBe(true);
+        expect(deepDifference).toBe(99);
     });
 
-    it("should not increment age if current month is less than input month", () => {
-        const currentDate = new Date();
-        const inputDate = new Date(currentDate.getFullYear() - 110, currentDate.getMonth() + 1, currentDate.getDate());
-
-        const result = isNotTooOld(inputDate.getDate(), inputDate.getMonth() + 1, inputDate.getFullYear());
+    it("should not decrement deepDifference if the input date is earlier in the same year", () => {
+        const currentDate = new Date(2025, 5, 15);
+        const inputDate = new Date(1925, 4, 15);
 
-        expect(result).toBe(true);
-    });
+        let deepDifference = currentDate.getFullYear() - inputDate.getFullYear();
 
-    it("should not increment age if current month is equal to input month and current day is less than or equal to input day", () => {
-        const currentDate = new Date();
-        const inputDate = new Date(currentDate.getFullYear() - 110, currentDate.getMonth(), currentDate.getDate());
+        if (
+            currentDate.getMonth() < inputDate.getMonth() ||
+            (currentDate.getMonth() === inputDate.getMonth() && currentDate.getDate() < inputDate.getDate())
+        ) {
+            deepDifference--;
+        }
 
-        const result = isNotTooOld(inputDate.getDate(), inputDate.getMonth() + 1, inputDate.getFullYear());
-
-        expect(result).toBe(true);
+        expect(deepDifference).toBe(100);
     });
 
-    it("should return false if age is greater than 110 years", () => {
-        const currentDate = new Date();
-        const inputDate = new Date(currentDate.getFullYear() - 111, currentDate.getMonth(), currentDate.getDate());
+    it("should not decrement deepDifference if the input date is the same as the current date", () => {
+        const currentDate = new Date(2025, 5, 15);
+        const inputDate = new Date(1925, 5, 15);
+
+        let deepDifference = currentDate.getFullYear() - inputDate.getFullYear();
 
-        const result = isNotTooOld(inputDate.getDate(), inputDate.getMonth() + 1, inputDate.getFullYear());
+        if (
+            currentDate.getMonth() < inputDate.getMonth() ||
+            (currentDate.getMonth() === inputDate.getMonth() && currentDate.getDate() < inputDate.getDate())
+        ) {
+            deepDifference--;
+        }
 
-        expect(result).toBe(false);
+        expect(deepDifference).toBe(100);
     });
 });


Reviews:
user: login: ttingle-ch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T09:41:37Z
user: login: ttingle-ch
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T12:43:32Z
user: login: maliv1-ch
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T13:37:54Z

Review comments:
diff_hunk: @@ -88,6 +90,12 @@ const validateDobAge = (day: string, month: string, year: string): void => {
     }
 };
 
+export const validateDateOfChangeLimit = (day: string, month: string, year: string): void => {
path: src/validation/dateOfACSPUpdateDetailsChange.ts
user: login: ttingle-ch
body: As this is only used in this file it should not be an export
created_at: 2025-04-04T09:41:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,6 +127,16 @@ export const isNotTooOld = (day: number, month: number, year: number): boolean =
     return age <= 110;
 };
 
+export const notMoreThanACentury = (day: number, month: number, year: number): boolean => {
path: src/validation/dateOfACSPUpdateDetailsChange.ts
user: login: ttingle-ch
body: As this is only used in this file it should not be an export
created_at: 2025-04-04T09:41:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: ch-code-analysis
created_at: 2025-03-28T09:29:26Z
body: CI: No security warnings found
author_association: NONE
user: login: ch-code-analysis
created_at: 2025-03-28T12:56:15Z
body: CI: No security warnings found
author_association: NONE
user: login: ch-code-analysis
created_at: 2025-04-03T15:05:40Z
body: CI: No security warnings found
author_association: NONE
user: login: ch-code-analysis
created_at: 2025-04-04T09:05:11Z
body: CI: No security warnings found
author_association: NONE
user: login: ch-code-analysis
created_at: 2025-04-04T10:55:15Z
body: CI: No security warnings found
author_association: NONE
user: login: chsonarqubeprchecks[bot]
created_at: 2025-04-04T11:29:40Z
body: **SonarQube Quality Gate**

[![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](http://code-analysis.platform.aws.chdev.org:9000/dashboard?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734)

[![Bug](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/common/bug-16px.png 'Bug')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=BUG) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/RatingBadge/A-16px.png 'A')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=BUG) [0 Bugs](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=BUG)  
[![Vulnerability](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/common/vulnerability-16px.png 'Vulnerability')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=VULNERABILITY) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/RatingBadge/A-16px.png 'A')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/common/security_hotspot-16px.png 'Security Hotspot')](http://code-analysis.platform.aws.chdev.org:9000/security_hotspots?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/RatingBadge/A-16px.png 'A')](http://code-analysis.platform.aws.chdev.org:9000/security_hotspots?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734) [0 Security Hotspots](http://code-analysis.platform.aws.chdev.org:9000/security_hotspots?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734)  
[![Code Smell](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/common/code_smell-16px.png 'Code Smell')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=CODE_SMELL) [![A](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/RatingBadge/A-16px.png 'A')](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=CODE_SMELL) [0 Code Smells](http://code-analysis.platform.aws.chdev.org:9000/project/issues?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&resolved=false&types=CODE_SMELL)

[![96.9%](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/CoverageChart/90-16px.png '96.9%')](http://code-analysis.platform.aws.chdev.org:9000/component_measures?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&metric=new_coverage&view=list) [96.9% Coverage](http://code-analysis.platform.aws.chdev.org:9000/component_measures?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&metric=new_coverage&view=list)  
[![0.0%](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v86/checks/Duplications/3-16px.png '0.0%')](http://code-analysis.platform.aws.chdev.org:9000/component_measures?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](http://code-analysis.platform.aws.chdev.org:9000/component_measures?id=uk.gov.companieshouse%3Aacsp-web&pullRequest=734&metric=new_duplicated_lines_density&view=list)


author_association: NONE
user: login: ch-code-analysis
created_at: 2025-04-04T11:31:33Z
body: CI: No security warnings found
author_association: NONE