fix(validation): reject non-positive page/limit in pagination query schemas (#1271)

This commit is contained in:
Abhay Singh 2026-08-12 19:35:45 +05:30 committed by GitHub
parent 59b148e5b2
commit a7efd817ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 3 deletions

View file

@ -1,6 +1,11 @@
import { describe, expect, it } from "bun:test"
import { readFileSync } from "node:fs"
import { SearchRequestSchema, Searchv4RequestSchema } from "./api"
import {
DocumentsWithMemoriesQuerySchema,
ListMemoriesQuerySchema,
SearchRequestSchema,
Searchv4RequestSchema,
} from "./api"
describe("search threshold schemas", () => {
it("do not contain redundant number transforms or unreachable range guards", () => {
@ -80,3 +85,70 @@ describe("search threshold schemas", () => {
).toBe(false)
})
})
describe("pagination query schemas", () => {
it("preserve page/limit defaults", () => {
const listed = ListMemoriesQuerySchema.parse({})
expect(listed.page).toBe(1)
expect(listed.limit).toBe(10)
const docs = DocumentsWithMemoriesQuerySchema.parse({})
expect(docs.page).toBe(1)
expect(docs.limit).toBe(10)
})
it.each([
1, 50, 1100,
])("ListMemoriesQuerySchema accepts numeric limit %p", (limit) => {
expect(ListMemoriesQuerySchema.parse({ limit }).limit).toBe(limit)
})
it("ListMemoriesQuerySchema accepts numeric string page/limit", () => {
const parsed = ListMemoriesQuerySchema.parse({ page: "3", limit: "25" })
expect(parsed.page).toBe(3)
expect(parsed.limit).toBe(25)
})
it.each([
0, -5, 2.5,
])("ListMemoriesQuerySchema rejects non-positive or fractional numeric limit %p", (limit) => {
expect(ListMemoriesQuerySchema.safeParse({ limit }).success).toBe(false)
})
it.each([
0, -1, 1.5,
])("ListMemoriesQuerySchema rejects non-positive or fractional numeric page %p", (page) => {
expect(ListMemoriesQuerySchema.safeParse({ page }).success).toBe(false)
})
it("ListMemoriesQuerySchema still caps limit at 1100", () => {
expect(ListMemoriesQuerySchema.safeParse({ limit: 1101 }).success).toBe(
false,
)
})
it.each([
0, -1, 2.5,
])("DocumentsWithMemoriesQuerySchema rejects invalid page %p", (page) => {
expect(DocumentsWithMemoriesQuerySchema.safeParse({ page }).success).toBe(
false,
)
})
it.each([
0, -10, 2.5,
])("DocumentsWithMemoriesQuerySchema rejects invalid limit %p", (limit) => {
expect(DocumentsWithMemoriesQuerySchema.safeParse({ limit }).success).toBe(
false,
)
})
it("DocumentsWithMemoriesQuerySchema accepts a normal request", () => {
const parsed = DocumentsWithMemoriesQuerySchema.parse({
page: 2,
limit: 50,
})
expect(parsed.page).toBe(2)
expect(parsed.limit).toBe(50)
})
})

View file

@ -275,6 +275,9 @@ export const ListMemoriesQuerySchema = z
.regex(/^\d+$/)
.or(z.number())
.transform(Number)
.refine((value) => Number.isInteger(value) && value >= 1, {
message: "Limit must be a positive integer",
})
.refine((value) => value <= 1100, {
message: "Limit cannot be greater than 1100",
})
@ -292,6 +295,9 @@ export const ListMemoriesQuerySchema = z
.regex(/^\d+$/)
.or(z.number())
.transform(Number)
.refine((value) => Number.isInteger(value) && value >= 1, {
message: "Page must be a positive integer",
})
.default("1")
.openapi({ description: "Page number to fetch", example: "1" }),
sort: z
@ -1092,11 +1098,11 @@ export const DocumentsWithMemoriesResponseSchema = z
export const DocumentsWithMemoriesQuerySchema = z
.object({
page: z.number().default(1).openapi({
page: z.number().int().min(1).default(1).openapi({
description: "Page number to fetch",
example: 1,
}),
limit: z.number().default(10).openapi({
limit: z.number().int().min(1).default(10).openapi({
description: "Number of items per page",
example: 10,
}),