64 lines
3.4 KiB
TypeScript
64 lines
3.4 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import test from "node:test"
|
|
import { getTableName } from "drizzle-orm"
|
|
import { drizzle } from "drizzle-orm/pg-proxy"
|
|
import * as schema from "../db/schema"
|
|
import { mcpTools } from "../src/mcp/registry"
|
|
import { limitFromArgs, offsetFromArgs, paginatedResult } from "../src/mcp/pagination"
|
|
|
|
const tables = Object.values(schema).filter((value: any) => {
|
|
try { return typeof getTableName(value) === "string" } catch { return false }
|
|
})
|
|
|
|
for (const tool of mcpTools.filter((tool) => (tool.inputSchema.properties as any)?.offset)) {
|
|
test(`${tool.name}: retrieves 205 records across stable, bounded pages`, async () => {
|
|
const db = drizzle(async (sql, params) => {
|
|
if (sql.includes('from "tenants"')) return { rows: [["skr03"]] }
|
|
const tableName = sql.match(/from "(\w+)"/)?.[1]
|
|
const table = tables.find((table: any) => getTableName(table) === tableName) as any
|
|
assert.ok(table, sql)
|
|
assert.match(sql, /order by .*"id"/)
|
|
if (table.tenant || table.tenantId) {
|
|
assert.match(sql, /where .*"tenant(?:_id)?" = \$\d+/)
|
|
assert.ok(params.includes(7), "active tenant must be bound")
|
|
}
|
|
if (table.archived) assert.match(sql, /"archived" = \$\d+/)
|
|
const limitParam = sql.match(/limit \$(\d+)/)
|
|
const offsetParam = sql.match(/offset \$(\d+)/)
|
|
assert.ok(limitParam, "query must be bounded")
|
|
const limit = Number(params[Number(limitParam[1]) - 1])
|
|
const offset = offsetParam ? Number(params[Number(offsetParam[1]) - 1]) : 0
|
|
assert.equal(limit, 101, "fetch one extra row for hasMore")
|
|
const selected = sql.slice(7, sql.indexOf(" from ")).split(", ")
|
|
const idIndex = selected.findIndex((column) => /"id"$/.test(column))
|
|
assert.ok(idIndex >= 0)
|
|
return { rows: Array.from({ length: Math.min(limit, Math.max(0, 205 - offset)) }, (_, i) => {
|
|
const values = selected.map(() => null as unknown)
|
|
values[idIndex] = offset + i + 1
|
|
return values
|
|
}) }
|
|
}, { schema })
|
|
const context = { server: { db }, tenantId: 7, userId: "test" } as any
|
|
const ids: unknown[] = []
|
|
let offset = 0
|
|
for (let page = 0; page < 3; page++) {
|
|
const result: any = await tool.handler(context, { id: 1, limit: 100, offset })
|
|
assert.equal(result.rows.length, page < 2 ? 100 : 5)
|
|
assert.equal(result.pagination.hasMore, page < 2)
|
|
assert.equal(result.pagination.nextOffset, page < 2 ? offset + 100 : null)
|
|
ids.push(...result.rows.map((row: any) => Number(row.id)))
|
|
offset = result.pagination.nextOffset
|
|
}
|
|
assert.deepEqual(ids, Array.from({ length: 205 }, (_, i) => i + 1))
|
|
})
|
|
}
|
|
|
|
test("pagination validates input and handles empty and exactly full final pages", () => {
|
|
for (const limit of [-1, 0, 101, 1.5, "10", Infinity]) assert.throws(() => limitFromArgs({ limit }))
|
|
for (const offset of [-1, 1.5, "10", Infinity]) assert.throws(() => offsetFromArgs({ offset }))
|
|
assert.equal(limitFromArgs({}), 25)
|
|
assert.equal(limitFromArgs({}, 50), 50)
|
|
assert.equal(paginatedResult([1, 2], { limit: 2 }).pagination.nextOffset, null)
|
|
assert.deepEqual(paginatedResult([], { offset: 300 }).pagination, { limit: 25, offset: 300, hasMore: false, nextOffset: null })
|
|
})
|