fix(api): preserve auth status + guard pagination against NaN

- Catch block now preserves error.status so assertAgentAuth's 401
  surfaces as 401, not 500. Matches sibling /api/v1/stock-purchases
  pattern.
- page/perPage parsing falls back to documented defaults on
  non-numeric or zero input rather than passing NaN to the service.
- Add 2 tests covering the auth-status and pagination boundaries.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-08-03 14:38:51 +02:00
parent 8cb4e9a60c
commit 469b2c0460
2 changed files with 44 additions and 3 deletions

View file

@ -93,4 +93,38 @@ describe("/api/v1/stock-purchases/list", () => {
const body = await res.json();
expect(body.error).toBe("PB is down");
});
it("returns 401 when the request is not authenticated", async () => {
// No bearer header — assertAgentAuth throws with status: 401.
const req = new NextRequest("http://localhost/api/v1/stock-purchases/list");
const res = await GET(req);
expect(res.status).toBe(401);
expect(listStockPurchases).not.toHaveBeenCalled();
});
it("clamps page/perPage and falls back to defaults on malformed input", async () => {
listStockPurchases.mockResolvedValue({
items: [],
totalItems: 0,
page: 1,
perPage: 50,
totalPages: 0,
});
const cases: Array<[string, { status: ""; page: number; perPage: number }]> = [
["?page=0", { status: "", page: 1, perPage: 50 }],
["?page=abc", { status: "", page: 1, perPage: 50 }],
["?perPage=0", { status: "", page: 1, perPage: 50 }],
["?perPage=abc", { status: "", page: 1, perPage: 50 }],
["?perPage=999", { status: "", page: 1, perPage: 200 }],
];
for (const [qs, expected] of cases) {
listStockPurchases.mockClear();
await GET(
authedRequest(`http://localhost/api/v1/stock-purchases/list${qs}`),
);
expect(listStockPurchases).toHaveBeenCalledWith(expect.anything(), expected);
}
});
});

View file

@ -39,10 +39,16 @@ export async function GET(req: NextRequest) {
}
const status = rawStatus as StockPurchaseStatus | "";
const page = Math.max(1, Number(req.nextUrl.searchParams.get("page") || "1"));
const page = Math.max(
1,
Number(req.nextUrl.searchParams.get("page") || "1") || 1,
);
const perPage = Math.min(
200,
Math.max(1, Number(req.nextUrl.searchParams.get("perPage") || "50")),
Math.max(
1,
Number(req.nextUrl.searchParams.get("perPage") || "50") || 50,
),
);
const pb = await getAdminPocketBase();
@ -51,6 +57,7 @@ export async function GET(req: NextRequest) {
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to fetch stock-purchases";
return NextResponse.json({ error: message }, { status: 500 });
const status = (error as Error & { status?: number }).status || 500;
return NextResponse.json({ error: message }, { status });
}
}