Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
import type { ThreadMessage } from "@rakazo/contracts";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { loadPeerHistory } from "./peer-history";
|
|
|
|
function message(id: string, seq: number): ThreadMessage {
|
|
return { id, seq } as ThreadMessage;
|
|
}
|
|
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
describe("peer history loading", () => {
|
|
it("collects paginated messages in chronological page order", async () => {
|
|
const loadPage = vi
|
|
.fn()
|
|
.mockResolvedValueOnce({ messages: [message("new", 2)], olderCursor: 2 })
|
|
.mockResolvedValueOnce({ messages: [message("old", 1)], olderCursor: null });
|
|
|
|
const messages = await loadPeerHistory({
|
|
signal: new AbortController().signal,
|
|
loadPage,
|
|
});
|
|
|
|
expect(messages.map(({ id }) => id)).toEqual(["old", "new"]);
|
|
expect(loadPage.mock.calls.map(([before]) => before)).toEqual([undefined, 2]);
|
|
});
|
|
|
|
it("aborts a page that never settles before the full-history deadline", async () => {
|
|
vi.useFakeTimers();
|
|
const loadPage = vi.fn(
|
|
(_before: number | undefined, signal: AbortSignal) =>
|
|
new Promise<never>((_resolve, reject) => {
|
|
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
}),
|
|
);
|
|
|
|
const loading = loadPeerHistory({
|
|
signal: new AbortController().signal,
|
|
loadPage,
|
|
pageTimeoutMs: 100,
|
|
totalTimeoutMs: 1_000,
|
|
});
|
|
const rejected = expect(loading).rejects.toMatchObject({ name: "TimeoutError" });
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
|
|
await rejected;
|
|
});
|
|
|
|
it("bounds the full-history scan independently of the per-page timeout", async () => {
|
|
vi.useFakeTimers();
|
|
const loadPage = vi.fn(
|
|
(_before: number | undefined, signal: AbortSignal) =>
|
|
new Promise<never>((_resolve, reject) => {
|
|
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
}),
|
|
);
|
|
|
|
const loading = loadPeerHistory({
|
|
signal: new AbortController().signal,
|
|
loadPage,
|
|
pageTimeoutMs: 1_000,
|
|
totalTimeoutMs: 100,
|
|
});
|
|
const rejected = expect(loading).rejects.toMatchObject({ name: "TimeoutError" });
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
|
|
await rejected;
|
|
});
|
|
|
|
it("aborts an in-flight page when the viewer closes", async () => {
|
|
const lifecycle = new AbortController();
|
|
const loadPage = vi.fn(
|
|
(_before: number | undefined, signal: AbortSignal) =>
|
|
new Promise<never>((_resolve, reject) => {
|
|
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
}),
|
|
);
|
|
|
|
const loading = loadPeerHistory({ signal: lifecycle.signal, loadPage });
|
|
const rejected = expect(loading).rejects.toMatchObject({ name: "AbortError" });
|
|
lifecycle.abort();
|
|
|
|
await rejected;
|
|
});
|
|
});
|