diff --git a/src/controllers/issueDetailsController.js b/src/controllers/issueDetailsController.js index ed3661fa..7409145a 100644 --- a/src/controllers/issueDetailsController.js +++ b/src/controllers/issueDetailsController.js @@ -11,7 +11,7 @@ import { fetchOne } from '../middleware/middleware.builders.js' const validateParams = validateQueryParams({ schema: v.object({ - pageNumber: v.optional(v.pipe(v.string(), v.transform(parseInt), v.minValue(1)), '1') + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.number(), v.integer(), v.minValue(1)), '1') }) }) diff --git a/src/controllers/resultsController.js b/src/controllers/resultsController.js index 1775e015..6bb10011 100644 --- a/src/controllers/resultsController.js +++ b/src/controllers/resultsController.js @@ -555,7 +555,7 @@ export function getFileNameOrUrlAndCheckedTime (req, res, next) { const validateParams = validateQueryParams({ schema: v.object({ - pageNumber: v.optional(v.pipe(v.string(), v.transform(parseInt), v.minValue(1)), '1') + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.number(), v.integer(), v.minValue(1)), '1') }) }) diff --git a/src/middleware/dataset-failed-expectation-entry.middleware.js b/src/middleware/dataset-failed-expectation-entry.middleware.js index 73023917..57960725 100644 --- a/src/middleware/dataset-failed-expectation-entry.middleware.js +++ b/src/middleware/dataset-failed-expectation-entry.middleware.js @@ -41,7 +41,7 @@ const subPath = (organisation, dataset) => { export const validateExpectationParams = validateQueryParams({ schema: v.object({ expectation: ExpectationPathParams, - pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.minValue(1)), '1') + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.number(), v.integer(), v.minValue(1)), '1') }) }) diff --git a/src/middleware/dataview.middleware.js b/src/middleware/dataview.middleware.js index 2a520de5..26554586 100644 --- a/src/middleware/dataview.middleware.js +++ b/src/middleware/dataview.middleware.js @@ -32,7 +32,7 @@ import { splitByLeading } from '../utils/table.js' export const dataviewQueryParams = v.object({ lpa: v.string(), dataset: v.string(), - pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.minValue(1)), '1'), + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.number(), v.integer(), v.minValue(1)), '1'), resourceId: v.optional(v.string()) }) diff --git a/src/middleware/entryIssueDetails.middleware.js b/src/middleware/entryIssueDetails.middleware.js index e3e90b8e..06d202a8 100644 --- a/src/middleware/entryIssueDetails.middleware.js +++ b/src/middleware/entryIssueDetails.middleware.js @@ -9,7 +9,7 @@ export const IssueDetailsQueryParams = v.object({ dataset: v.string(), issue_type: v.string(), issue_field: v.string(), - pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.minValue(1)), '1'), + pageNumber: v.optional(v.pipe(v.string(), v.transform(s => parseInt(s, 10)), v.number(), v.integer(), v.minValue(1)), '1'), resourceId: v.optional(v.string()) }) diff --git a/test/unit/middleware/dataviewQueryParams.test.js b/test/unit/middleware/dataviewQueryParams.test.js new file mode 100644 index 00000000..d4720953 --- /dev/null +++ b/test/unit/middleware/dataviewQueryParams.test.js @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest' +import * as v from 'valibot' +import { dataviewQueryParams } from '../../../src/middleware/dataview.middleware.js' + +// Regression: a non-numeric :pageNumber segment (e.g. /data/abc) used to parse +// to NaN, slip past v.minValue(1), and crash later in getSetDataRange. The +// schema now includes v.integer(), so invalid page numbers are rejected here. +describe('dataviewQueryParams pageNumber', () => { + const base = { lpa: 'x', dataset: 'y' } + + it('defaults to 1 when absent', () => { + expect(v.parse(dataviewQueryParams, base).pageNumber).toBe(1) + }) + + it('coerces a valid numeric string to a number', () => { + expect(v.parse(dataviewQueryParams, { ...base, pageNumber: '3' }).pageNumber).toBe(3) + }) + + // 'abc'/'' -> NaN; '0'/'-1' -> below the minimum. All must be rejected here + // rather than crashing downstream. (Note '2.5' parses to 2 and is valid.) + it.each(['abc', '', '0', '-1'])('rejects invalid pageNumber %j', (pageNumber) => { + expect(() => v.parse(dataviewQueryParams, { ...base, pageNumber })).toThrow() + }) +})