import { describe, expect, it } from "vitest"; import { getTerminalResumeOffset, getTerminalAttachRetryDelayMs, isTerminalAttachRetryableError, updateTerminalResumeOffset, withPromiseTimeout, } from "./terminal-attach"; describe("terminal-attach", () => { it("computes bounded exponential retry delays", () => { expect(getTerminalAttachRetryDelayMs({ attempt: 0 })).toBe(250); expect(getTerminalAttachRetryDelayMs({ attempt: 1 })).toBe(500); expect(getTerminalAttachRetryDelayMs({ attempt: 2 })).toBe(1_000); expect(getTerminalAttachRetryDelayMs({ attempt: 3 })).toBe(2_000); expect(getTerminalAttachRetryDelayMs({ attempt: 8 })).toBe(2_000); }); it("matches retryable attach errors", () => { expect( isTerminalAttachRetryableError({ message: "Terminal not found while attaching" }) ).toBe(true); expect( isTerminalAttachRetryableError({ message: "Network disconnected during attach" }) ).toBe(true); expect( isTerminalAttachRetryableError({ message: "stream ended before ack" }) ).toBe(true); expect( isTerminalAttachRetryableError({ message: "permission denied" }) ).toBe(false); }); it("reads and updates resume offsets monotonically", () => { const offsets = new Map(); const terminalId = "term-1"; const resumeOffsetStore = { get: ({ terminalId }: { terminalId: string }) => offsets.get(terminalId), set: ({ terminalId, offset }: { terminalId: string; offset: number }) => { offsets.set(terminalId, offset); }, }; expect( getTerminalResumeOffset({ terminalId, resumeOffsetStore, }) ).toBeUndefined(); updateTerminalResumeOffset({ terminalId, offset: 8, resumeOffsetStore, }); expect( getTerminalResumeOffset({ terminalId, resumeOffsetStore, }) ).toBe(8); // Stale offsets must not move resume backwards. updateTerminalResumeOffset({ terminalId, offset: 3, resumeOffsetStore, }); expect( getTerminalResumeOffset({ terminalId, resumeOffsetStore, }) ).toBe(8); }); it("resolves before timeout when promise completes", async () => { await expect( withPromiseTimeout({ promise: Promise.resolve("ok"), timeoutMs: 50, timeoutMessage: "timed out", }) ).resolves.toBe("ok"); }); it("rejects when timeout wins", async () => { await expect( withPromiseTimeout({ promise: new Promise(() => {}), timeoutMs: 10, timeoutMessage: "timed out", }) ).rejects.toThrow("timed out"); }); });