115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { createTokenSmoother } from "../../src/routes/chatTokenSmoother.js";
|
|
|
|
function createOutput(options: { enabled?: boolean; delayMs?: number; maxWaitMs?: number } = {}) {
|
|
const chunks: string[] = [];
|
|
const smoother = createTokenSmoother({
|
|
enabled: true, delayMs: 1, locale: "zh", sessionId: "s", ...options,
|
|
write: (_event, data) => { chunks.push(String(data.content)); },
|
|
});
|
|
return { chunks, smoother };
|
|
}
|
|
|
|
describe("token output lifecycle", () => {
|
|
it("releases short Chinese narration during an upstream pause", async () => {
|
|
const { chunks, smoother } = createOutput({ maxWaitMs: 25 });
|
|
try {
|
|
smoother.writeToken("正在核查,请稍候。");
|
|
await Bun.sleep(60);
|
|
expect(chunks.join("")).toBe("正在核查,请稍候。");
|
|
} finally { await smoother.flush(); }
|
|
});
|
|
|
|
it("continues streaming after a stage flush", async () => {
|
|
const { chunks, smoother } = createOutput();
|
|
smoother.writeToken("发布界面前。");
|
|
await smoother.flush();
|
|
smoother.writeToken("发布界面后继续解释。");
|
|
await smoother.flush();
|
|
expect(chunks).toEqual(["发布界面前。", "发布界面后继续解释。"]);
|
|
});
|
|
|
|
it("keeps releasing a burst in bounded chunks beyond the short-tail deadline", async () => {
|
|
const { chunks, smoother } = createOutput({ delayMs: 5, maxWaitMs: 25 });
|
|
const text = "核查管道压力与流量。".repeat(30);
|
|
try {
|
|
smoother.writeToken(text);
|
|
await Bun.sleep(60);
|
|
expect(chunks.length).toBeGreaterThan(1);
|
|
expect(chunks.every((chunk) => Array.from(chunk).length <= 18)).toBe(true);
|
|
} finally { await smoother.flush(); }
|
|
expect(chunks.join("")).toBe(text);
|
|
});
|
|
|
|
it("paces a stage flush instead of dumping the remaining paragraph", async () => {
|
|
const chunks: string[] = [];
|
|
const times: number[] = [];
|
|
const smoother = createTokenSmoother({
|
|
enabled: true, delayMs: 10, locale: "zh", sessionId: "s",
|
|
write: (_event, data) => { chunks.push(String(data.content)); times.push(performance.now()); },
|
|
});
|
|
const text = "核查管道压力与流量。".repeat(10);
|
|
smoother.writeToken(text);
|
|
await smoother.flush();
|
|
expect(chunks.length).toBeGreaterThan(2);
|
|
expect(chunks.every((chunk) => Array.from(chunk).length <= 23)).toBe(true);
|
|
expect(times.at(-1)! - times[0]).toBeGreaterThanOrEqual(30);
|
|
expect(chunks.join("")).toBe(text);
|
|
});
|
|
|
|
it("keeps later text behind an in-progress stage boundary", async () => {
|
|
const { chunks, smoother } = createOutput({ delayMs: 5 });
|
|
const before = "管道压力核查。".repeat(8);
|
|
smoother.writeToken(before);
|
|
const boundary = smoother.flush();
|
|
smoother.writeToken("组件后的解释。");
|
|
await boundary;
|
|
expect(chunks.join("")).toBe(before);
|
|
await smoother.close();
|
|
expect(chunks.join("")).toBe(`${before}组件后的解释。`);
|
|
});
|
|
|
|
it("interrupts a paced drain immediately without duplicate late output", async () => {
|
|
const { chunks, smoother } = createOutput({ delayMs: 1000 });
|
|
const text = "管道压力核查。".repeat(8);
|
|
smoother.writeToken(text);
|
|
const draining = smoother.flush();
|
|
await smoother.close({ immediate: true });
|
|
await draining;
|
|
await Bun.sleep(30);
|
|
expect(chunks).toEqual([text]);
|
|
});
|
|
|
|
it("does not postpone the first release when new Chinese text keeps arriving", async () => {
|
|
const { chunks, smoother } = createOutput({ maxWaitMs: 25 });
|
|
try {
|
|
for (let i = 0; i < 8; i++) {
|
|
smoother.writeToken("核查");
|
|
await Bun.sleep(10);
|
|
}
|
|
expect(chunks.join("").length).toBeGreaterThan(0);
|
|
} finally { await smoother.flush(); }
|
|
expect(chunks.join("")).toBe("核查".repeat(8));
|
|
});
|
|
|
|
it.each([true, false])("closes once and ignores late writes with smoothing=%s", async (enabled) => {
|
|
const { chunks, smoother } = createOutput({ enabled });
|
|
smoother.writeToken("已经收到的文字。");
|
|
await smoother.close();
|
|
smoother.writeToken("关闭后不能追加。");
|
|
await smoother.close();
|
|
await Bun.sleep(35);
|
|
expect(chunks.join("")).toBe("已经收到的文字。");
|
|
});
|
|
|
|
it("preserves Unicode and order across repeated flushes without delayed duplicates", async () => {
|
|
const { chunks, smoother } = createOutput({ maxWaitMs: 25 });
|
|
smoother.writeToken("供水💧");
|
|
await smoother.flush();
|
|
smoother.writeToken("核查完成。");
|
|
await smoother.flush();
|
|
await Bun.sleep(60);
|
|
expect(chunks.join("")).toBe("供水💧核查完成。");
|
|
});
|
|
});
|