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 }) })