Implement input validation and supporting tests with automation of the tests via actions.
This commit is contained in:
parent
d20d981190
commit
c54e517700
25
.gitea/workflows/run_tests.yaml
Normal file
25
.gitea/workflows/run_tests.yaml
Normal 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
6
jest.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
transform: {
|
||||||
|
"^.+\\.tsx?$": ["ts-jest", { useESM: true }]
|
||||||
|
},
|
||||||
|
extensionsToTreatAsEsm: [".ts"],
|
||||||
|
};
|
3833
package-lock.json
generated
3833
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,9 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "TypeScript client for the OwlBoard API",
|
"description": "TypeScript client for the OwlBoard API",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -13,6 +14,12 @@
|
|||||||
"author": "Frederick Boniface",
|
"author": "Frederick Boniface",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"ts-jest": "^29.2.6",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "^11.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
110
src/inputValidation/inputValidation.test.ts
Normal file
110
src/inputValidation/inputValidation.test.ts
Normal 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);
|
||||||
|
})
|
||||||
|
})
|
@ -1,6 +1,13 @@
|
|||||||
import { ValidationError } from "../errors";
|
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}$/;
|
const codeRegex = /^\d{4}$/;
|
||||||
if (!codeRegex.test(code)) {
|
if (!codeRegex.test(code)) {
|
||||||
throw new ValidationError("Invalid input: code must be a four-character string consisting of only numerals");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateTiploc(tiploc: string): boolean {
|
export function validateTiploc(tiploc: unknown): boolean {
|
||||||
const tiplocRegex = /[a-zA-z0-9]{4,7}/;
|
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)) {
|
if (!tiplocRegex.test(tiploc)) {
|
||||||
throw new ValidationError("Invalid input: TIPLOC must be between four and seven characters");
|
throw new ValidationError("Invalid input: TIPLOC must be between four and seven characters");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateCrs(crs: string): boolean {
|
export function validateCrs(crs: unknown): boolean {
|
||||||
const crsRegex = /[a-zA-z]{3}/;
|
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)) {
|
if (!crsRegex.test(crs)) {
|
||||||
throw new ValidationError("Invalid input: CRS/3ALPHA must be exactly three letters")
|
throw new ValidationError("Invalid input: CRS/3ALPHA must be exactly three letters")
|
||||||
}
|
}
|
||||||
return true;
|
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.");
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user