From 054ff40cf9280f97ca259823199fa207a8f7a066 Mon Sep 17 00:00:00 2001 From: Fred Boniface Date: Mon, 8 Sep 2025 20:29:03 +0100 Subject: [PATCH] Add input validation and tests for Station names --- src/clients/ReferenceClientV2.ts | 3 ++- src/inputValidation/inputValidation.test.ts | 26 ++++++++++++++++++++- src/inputValidation/inputValidation.ts | 10 ++++++++ src/types/reference/ReferenceTypesV2.ts | 2 +- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/clients/ReferenceClientV2.ts b/src/clients/ReferenceClientV2.ts index d99ab7c..07887ec 100644 --- a/src/clients/ReferenceClientV2.ts +++ b/src/clients/ReferenceClientV2.ts @@ -1,4 +1,4 @@ -import { validateCrs, validateNlc, validateReasonCode, validateStanox, validateTiploc } from "../inputValidation/inputValidation"; +import { validateCrs, validateNlc, validateReasonCode, validateStanox, validateTiploc, validateStation } from "../inputValidation/inputValidation"; import { ReferenceV2_LocationReferenceCodes, ReferenceV2_LocationReferenceCodeType, ReferenceV2_ReasonCode } from "../types/reference/ReferenceTypesV2"; import { BaseOwlBoardClient } from "./client"; @@ -21,6 +21,7 @@ export class ReferenceClientV2 { tiploc: validateTiploc, nlc: validateNlc, stanox: validateStanox, + station: validateStation, }; const validate = validators[type]; diff --git a/src/inputValidation/inputValidation.test.ts b/src/inputValidation/inputValidation.test.ts index 3d334ae..0e7a0ee 100644 --- a/src/inputValidation/inputValidation.test.ts +++ b/src/inputValidation/inputValidation.test.ts @@ -6,7 +6,8 @@ import { validateCrs, validateUuid, validateHeadcode, validateNlc, - validateStanox} from "./inputValidation"; + validateStanox, + validateStation} from "./inputValidation"; import { ValidationError } from "../errors"; describe("PIS Validation Tests", () => { @@ -118,6 +119,29 @@ describe("STANOX Validation Tests", () => { expect(() => validateStanox(false)).toThrow(ValidationError); expect(() => validateStanox("3543ab")).toThrow(ValidationError); }) + + test("Station inputs that should pass validation", () => { + expect(() => validateStation("Heathrow Terminal 5")).toBe(true); + expect(() => validateStation("King's Cross")).toBe(true); + expect(() => validateStation("St Budeaux Ferry Road/Victoria Road")).toBe(true); + expect(() => validateStation("Queenstown Road (Battersea)")).toBe(true); + expect(() => validateStation("Rhoose Cardiff International Airport")).toBe(true); + expect(() => validateStation("Chappel & Wakes Colne")).toBe(true); + expect(() => validateStation("Duncraig")).toBe(true); + }) + + test("Station inputs that contain unusul characters or invalid types should throw ValidationError", () => { + expect(() => validateStation([])).toThrow(ValidationError); + expect(() => validateStation({})).toThrow(ValidationError); + expect(() => validateStation(null)).toThrow(ValidationError); + expect(() => validateStation(undefined)).toThrow(ValidationError); + expect(() => validateStation(false)).toThrow(ValidationError); + expect(() => validateStation("Br*ra")).toThrow(ValidationError); + expect(() => validateStation("DROP TABLE stations;--")).toThrow(ValidationError); + expect(() => validateStation("King's Cross; DELETE FROM users;")).toThrow(ValidationError); + expect(() => validateStation("St Pancras' OR '1'='1")).toThrow(ValidationError); + expect(() => validateStation("")).toThrow(ValidationError); + }) }) describe("UUID Validation Tests", () => { diff --git a/src/inputValidation/inputValidation.ts b/src/inputValidation/inputValidation.ts index 120016b..89396c1 100644 --- a/src/inputValidation/inputValidation.ts +++ b/src/inputValidation/inputValidation.ts @@ -84,6 +84,16 @@ export function validateUuid(uuid: unknown): boolean { return true; } +export function validateStation(station: unknown): boolean { + if (typeof station !== "string") { + throw new ValidationError("Invalid input: The station name should be a string"); + } + if (!/^[A-Za-z0-9\s&'()-]+$/.test(station)) { + throw new ValidationError("Invalid input: Station name should include letters, spaces, ', '/', '-', '&' only"); + } + return true; +} + export function validateReasonCode(code: unknown): boolean { if (typeof code === "number") { // Ensure it's a 3-digit number (100-999) diff --git a/src/types/reference/ReferenceTypesV2.ts b/src/types/reference/ReferenceTypesV2.ts index 4bbb319..e32afe0 100644 --- a/src/types/reference/ReferenceTypesV2.ts +++ b/src/types/reference/ReferenceTypesV2.ts @@ -19,4 +19,4 @@ interface ReferenceV2_ReasonCodeObject { export type ReferenceV2_ReasonCode = ReferenceV2_ReasonCodeObject[] -export type ReferenceV2_LocationReferenceCodeType = "tiploc" | "crs" | "stanox" | "nlc" \ No newline at end of file +export type ReferenceV2_LocationReferenceCodeType = "tiploc" | "crs" | "stanox" | "nlc" | "station" \ No newline at end of file