Implement input validation and supporting tests with automation of the tests via actions.

This commit is contained in:
Fred Boniface 2025-03-12 21:47:03 +00:00
parent d20d981190
commit c54e517700
6 changed files with 4032 additions and 6 deletions

View File

@ -0,0 +1,25 @@
name: Run Jest tests
on:
push:
jobs:
run-tests:
runs-on: ubuntu-22.04
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup up Node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install Dependencies
run: npm install
- name: Run Tests
run: npm test

6
jest.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
transform: {
"^.+\\.tsx?$": ["ts-jest", { useESM: true }]
},
extensionsToTreatAsEsm: [".ts"],
};

3833
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,9 @@
"version": "0.0.1",
"description": "TypeScript client for the OwlBoard API",
"main": "index.ts",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest"
},
"repository": {
"type": "git",
@ -13,6 +14,12 @@
"author": "Frederick Boniface",
"license": "GPL-3.0",
"devDependencies": {
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"ts-jest": "^29.2.6",
"typescript": "^5.6.3"
},
"dependencies": {
"uuid": "^11.1.0"
}
}

View File

@ -0,0 +1,110 @@
import { validateCrs,
validatePisCode,
validateReasonCode,
validateTiploc,
validateUuid } from "./inputValidation";
import { ValidationError } from "../errors";
describe("PIS Validation Tests", () => {
test("PIS inputs of the correct format should return true", () => {
expect(validatePisCode(1234)).toBe(true);
expect(validatePisCode("0999")).toBe(true);
expect(validatePisCode(9343)).toBe(true);
})
test("PIS inputs of an invalid format should throw a ValidationError", () => {
expect(() => validatePisCode(21)).toThrow(ValidationError);
expect(() => validatePisCode("hoo")).toThrow(ValidationError);
expect(() => validatePisCode({test: "Value"})).toThrow(ValidationError);
})
})
describe("TIPLOC Validation Tests", () => {
test("TIPLOC inputs of the correct format should return true", () => {
expect(validateTiploc("elyy")).toBe(true);
expect(validateTiploc("STROUD")).toBe(true);
expect(validateTiploc("GLOSTER")).toBe(true);
expect(validateTiploc("PaDtOn")).toBe(true);
})
test("TIPLOC inputs of an invalid format should throw a ValidationError", () => {
expect(() => validateTiploc("wey")).toThrow(ValidationError);
expect(() => validateTiploc("PMS")).toThrow(ValidationError);
expect(() => validateTiploc(89)).toThrow(ValidationError);
expect(() => validateTiploc(["STROUD", "GLOSTER"])).toThrow(ValidationError);
})
})
describe("CRS Validation Tests", () => {
test("CRS inputs of the correct format should return true", () => {
expect(validateCrs("bth")).toBe(true);
expect(validateCrs("BTH")).toBe(true);
expect(validateCrs("PoA")).toBe(true);
});
test("CRS inputs of an invalid format should throw a ValidationError", () => {
expect(() => validateCrs("BATHSPA")).toThrow(ValidationError);
expect(() => validateCrs("popo")).toThrow(ValidationError);
expect(() => validateCrs("BT")).toThrow(ValidationError);
})
test("CRS inputs that are not strings should throw ValidationError", () => {
expect(() => validateCrs(34)).toThrow(ValidationError);
expect(() => validateCrs([])).toThrow(ValidationError);
expect(() => validateCrs({})).toThrow(ValidationError);
expect(() => validateCrs(null)).toThrow(ValidationError);
expect(() => validateCrs(undefined)).toThrow(ValidationError);
})
})
describe("UUID Validation Tests", () => {
test("UUID inputs that are valid v4-UUIDs should return true", () => {
expect(validateUuid("5c5db50b-91c8-440c-80af-afc7bb375b4b")).toBe(true);
expect(validateUuid("9a9a3849-bbcb-452c-a0db-599a53cb27f3")).toBe(true);
})
test("UUID inputs that are valid v1-UUIDs should return true", () => {
expect(validateUuid("e7dd36f0-ff86-11ef-9cd2-0242ac120002")).toBe(true);
expect(validateUuid("e052e8b6-ff87-11ef-9cd2-0242ac120002")).toBe(true);
})
test("UUID inputs that are valid v7-UUIDs should return true", () => {
expect(validateUuid("01958c36-8b96-7ff1-be2c-d1488273f5e0")).toBe(true);
expect(validateUuid("01958c3a-f9d0-7c6a-b510-6de0dd36a49a")).toBe(true);
})
test("UUID inputs that are a nil UUID should return true", () => {
expect(validateUuid("00000000-0000-0000-0000-000000000000")).toBe(true);
})
test("UUID inputs that are actually not UUIDs should throw ValidationError", () => {
expect(() => validateUuid("thisisjustastring")).toThrow(ValidationError);
expect(() => validateUuid(789)).toThrow(ValidationError);
})
})
describe("Reason Code Validation Tests", () => {
test("Reason codes that are valid numbers or strings should return true", () => {
expect(validateReasonCode("999")).toBe(true);
expect(validateReasonCode("100")).toBe(true);
expect(validateReasonCode(402)).toBe(true);
expect(validateReasonCode(100)).toBe(true);
expect(validateReasonCode(999)).toBe(true);
})
test("Reason codes that are out of range numbers should throw ValidationError", () => {
expect(() => validateReasonCode(99)).toThrow(ValidationError);
expect(() => validateReasonCode("1000")).toThrow(ValidationError);
expect(() => validateReasonCode(1000)).toThrow(ValidationError);
expect(() => validateReasonCode("99")).toThrow(ValidationError);
})
test("Reason code inputs that are non numeric values should throw ValidationError", () => {
expect(() => validateReasonCode("19f")).toThrow(ValidationError);
expect(() => validateReasonCode(null)).toThrow(ValidationError);
expect(() => validateReasonCode(true)).toThrow(ValidationError);
expect(() => validateReasonCode([])).toThrow(ValidationError);
expect(() => validateReasonCode({})).toThrow(ValidationError);
})
})

View File

@ -1,6 +1,13 @@
import { ValidationError } from "../errors";
import { validate as uuidlibValidate, version as uuidlibVersion } from 'uuid'
export function validatePisCode(code: string): boolean {
export function validatePisCode(code: unknown): boolean {
if (typeof code == "number") {
code = code.toString();
}
if (typeof code !== "string") {
throw new ValidationError("Invalid input: code must be a four digit number or string")
}
const codeRegex = /^\d{4}$/;
if (!codeRegex.test(code)) {
throw new ValidationError("Invalid input: code must be a four-character string consisting of only numerals");
@ -8,18 +15,56 @@ export function validatePisCode(code: string): boolean {
return true;
}
export function validateTiploc(tiploc: string): boolean {
const tiplocRegex = /[a-zA-z0-9]{4,7}/;
export function validateTiploc(tiploc: unknown): boolean {
if (typeof tiploc !== "string") {
throw new ValidationError("Invalid input: TIPLOC must be a string");
}
const tiplocRegex = /[a-zA-Z0-9]{4,7}/;
if (!tiplocRegex.test(tiploc)) {
throw new ValidationError("Invalid input: TIPLOC must be between four and seven characters");
}
return true;
}
export function validateCrs(crs: string): boolean {
const crsRegex = /[a-zA-z]{3}/;
export function validateCrs(crs: unknown): boolean {
if (typeof crs !== "string") {
throw new ValidationError("Invalid input: CRS/3ALPHA must be a string")
}
const crsRegex = /^[a-zA-Z]{3}$/;
if (!crsRegex.test(crs)) {
throw new ValidationError("Invalid input: CRS/3ALPHA must be exactly three letters")
}
return true;
}
export function validateUuid(uuid: unknown): boolean {
if (typeof uuid !== "string") {
throw new ValidationError("Invalid input: The UUID/api_key should be a string");
}
if (!uuidlibValidate(uuid)) {
throw new ValidationError("Invalid input: The UUID/api_key is the expexted value")
}
return true;
}
export function validateReasonCode(code: unknown): boolean {
if (typeof code === "number") {
// Ensure it's a 3-digit number (100-999)
if (!Number.isInteger(code) || code < 100 || code > 999) {
throw new ValidationError("Invalid input: Reason code must be a three-digit number (100-999).");
}
return true;
}
if (typeof code === "string") {
// Ensure it consists of exactly 3 numeric characters
if (!/^\d{3}$/.test(code)) {
throw new ValidationError("Invalid input: Reason code must be a string of three digits.");
}
return true;
}
// If it's neither a number nor a string, throw an error
throw new ValidationError("Invalid input: Reason code must be a number or a string of three digits.");
}