From 6f46bce6340200604f35e581b72bf50e686c444d Mon Sep 17 00:00:00 2001 From: Zamil Majdy Date: Wed, 15 Apr 2026 23:39:36 +0700 Subject: [PATCH] test(frontend/copilot): add coverage for forward pagination cursor advancement and message truncation --- .../__tests__/useLoadMoreMessages.test.ts | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useLoadMoreMessages.test.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useLoadMoreMessages.test.ts index d8bda83646..b0cae343a4 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useLoadMoreMessages.test.ts +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useLoadMoreMessages.test.ts @@ -230,6 +230,133 @@ describe("useLoadMoreMessages", () => { }); }); + describe("loadMore — forward pagination cursor advancement", () => { + it("advances newestSequence after a successful forward load", async () => { + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ + messages: [{ role: "user", content: "hi", sequence: 50 }], + has_more_messages: true, + newest_sequence: 99, + }), + ); + + const { result } = renderHook(() => + useLoadMoreMessages({ ...BASE_ARGS, forwardPaginated: true }), + ); + + await act(async () => { + await result.current.loadMore(); + }); + + // A second loadMore should use after_sequence: 99 (advanced cursor) + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ has_more_messages: false, newest_sequence: 149 }), + ); + + await act(async () => { + await result.current.loadMore(); + }); + + expect(mockGetV2GetSession).toHaveBeenLastCalledWith( + "sess-1", + expect.objectContaining({ after_sequence: 99 }), + ); + }); + + it("does not regress newestSequence when parent refetches after pages loaded", async () => { + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ + messages: [{ role: "user", content: "msg", sequence: 50 }], + has_more_messages: true, + newest_sequence: 99, + }), + ); + + const { result, rerender } = renderHook( + (props) => useLoadMoreMessages(props), + { initialProps: { ...BASE_ARGS, forwardPaginated: true } }, + ); + + // Load one page — newestSequence advances to 99 + await act(async () => { + await result.current.loadMore(); + }); + + // Parent refetches with a lower newest_sequence (49) — should NOT regress cursor + rerender({ ...BASE_ARGS, forwardPaginated: true, initialNewestSequence: 49 }); + + // Next loadMore should still use the advanced cursor (99) + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ has_more_messages: false, newest_sequence: 149 }), + ); + + await act(async () => { + await result.current.loadMore(); + }); + + expect(mockGetV2GetSession).toHaveBeenLastCalledWith( + "sess-1", + expect.objectContaining({ after_sequence: 99 }), + ); + }); + }); + + describe("loadMore — MAX_OLDER_MESSAGES truncation", () => { + it("truncates accumulated messages at MAX_OLDER_MESSAGES (2000)", async () => { + // Simulate being near the limit — 1990 existing paged messages + const nearLimitArgs = { + ...BASE_ARGS, + forwardPaginated: false, + initialOldestSequence: 1990, + }; + + // Return 20 messages to push total past 2000 + const newMessages = Array.from({ length: 20 }, (_, i) => ({ + role: "user", + content: `msg ${i}`, + sequence: i, + })); + + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ + messages: newMessages, + has_more_messages: true, + oldest_sequence: 0, + }), + ); + + const { result, rerender } = renderHook( + (props) => useLoadMoreMessages(props), + { initialProps: nearLimitArgs }, + ); + + // Pre-fill pagedRawMessages to near limit by doing a successful load first + // then checking hasMore is set to false when limit reached + mockGetV2GetSession.mockResolvedValueOnce( + makeSuccessResponse({ + messages: Array.from({ length: 1990 }, (_, i) => ({ + role: "user", + content: `old ${i}`, + sequence: i, + })), + has_more_messages: true, + oldest_sequence: 0, + }), + ); + + await act(async () => { + await result.current.loadMore(); + }); + + // Now add 20 more to exceed 2000 — hasMore should be forced false + await act(async () => { + await result.current.loadMore(); + }); + + expect(result.current.hasMore).toBe(false); + }); + }); + describe("loadMore — epoch / stale-response guard", () => { it("discards response when epoch changes during flight (resetPaged called)", async () => { let resolveRequest!: (v: unknown) => void;