openhands / src /utils /transcript-export /load-complete-events.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3201ca6 verified
Raw
History Blame Contribute Delete
4.75 kB
import { describe, expect, it, vi } from "vitest";
import type { MessageEvent } from "#/types/agent-server/core";
import {
loadCompleteTranscriptEvents,
TRANSCRIPT_HISTORY_PAGE_SIZE,
} from "./load-complete-events";
const timestamp = "2026-07-10T12:34:56.000Z";
const makeMessage = (index: number): MessageEvent => ({
id: `event-${index.toString().padStart(3, "0")}`,
timestamp: new Date(Date.UTC(2026, 6, 10, 0, 0, index)).toISOString(),
source: "user",
llm_message: {
role: "user",
content: [{ type: "text", text: `Message ${index}` }],
},
activated_skills: [],
extended_content: [],
});
describe("loadCompleteTranscriptEvents", () => {
it("paginates beyond the 50 events initially loaded by the chat", async () => {
const allEvents = Array.from({ length: 225 }, (_, index) =>
makeMessage(index),
);
const loadedEvents = allEvents.slice(-50);
const descendingEvents = allEvents.slice().reverse();
const searchEvents = vi.fn(
async ({ limit, pageId }: { limit: number; pageId?: string }) => {
const offset = Number(pageId ?? 0);
const items = descendingEvents.slice(offset, offset + limit);
const nextOffset = offset + items.length;
return {
items,
next_page_id:
nextOffset < descendingEvents.length ? String(nextOffset) : null,
};
},
);
const result = await loadCompleteTranscriptEvents(
loadedEvents,
searchEvents,
);
expect(result).toEqual(allEvents);
expect(searchEvents).toHaveBeenCalledTimes(3);
expect(searchEvents).toHaveBeenNthCalledWith(1, {
limit: TRANSCRIPT_HISTORY_PAGE_SIZE,
sortOrder: "TIMESTAMP_DESC",
strictPagination: true,
});
});
it("rejects a repeated full page when completeness cannot be proven", async () => {
const page = Array.from(
{ length: TRANSCRIPT_HISTORY_PAGE_SIZE },
(_, index) => makeMessage(index),
).reverse();
const searchEvents = vi.fn().mockResolvedValue({ items: page });
await expect(
loadCompleteTranscriptEvents(page, searchEvents),
).rejects.toThrow("cannot prove that all events were loaded");
expect(searchEvents).toHaveBeenCalledTimes(1);
});
it("uses cursors without dropping events at a shared timestamp boundary", async () => {
const allEvents = Array.from({ length: 125 }, (_, index) => ({
...makeMessage(index),
timestamp,
}));
const descendingEvents = allEvents.slice().reverse();
const searchEvents = vi.fn(
async ({ limit, pageId }: { limit: number; pageId?: string }) => {
const offset = Number(pageId ?? 0);
const items = descendingEvents.slice(offset, offset + limit);
return {
items,
next_page_id:
offset + items.length < descendingEvents.length
? String(offset + items.length)
: null,
};
},
);
const result = await loadCompleteTranscriptEvents([], searchEvents);
expect(result).toEqual(allEvents);
expect(searchEvents).toHaveBeenNthCalledWith(2, {
limit: TRANSCRIPT_HISTORY_PAGE_SIZE,
pageId: "100",
sortOrder: "TIMESTAMP_DESC",
strictPagination: true,
});
});
it("does not let live-only store events mask unfetched persisted history", async () => {
const persistedEvents = Array.from({ length: 150 }, (_, index) =>
makeMessage(index),
);
const liveEvents = Array.from({ length: 50 }, (_, index) =>
makeMessage(index + 150),
);
const searchEvents = vi.fn(
async ({
limit,
timestampLt,
}: {
limit: number;
timestampLt?: string;
}) => ({
items: persistedEvents
.filter((event) =>
timestampLt ? event.timestamp < timestampLt : true,
)
.slice()
.reverse()
.slice(0, limit),
next_page_id: null,
}),
);
const result = await loadCompleteTranscriptEvents(
liveEvents,
searchEvents,
persistedEvents.length,
);
expect(result).toEqual([...persistedEvents, ...liveEvents]);
expect(searchEvents).toHaveBeenCalledTimes(2);
});
it("rejects malformed history pages", async () => {
await expect(
loadCompleteTranscriptEvents([], async () => ({ items: null as never })),
).rejects.toThrow("expected page.items to be an array");
});
it("rejects a partial export when the server reports more events", async () => {
await expect(
loadCompleteTranscriptEvents(
[],
async () => ({ items: [makeMessage(1)], next_page_id: null }),
2,
),
).rejects.toThrow("Transcript history is incomplete");
});
});