ai-chatbot/tests/integration/markdown-image-streaming.test.ts

66 lines
3.4 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { advancePastAtomicRanges, scanMarkdownImageStreaming, stableStreamingMarkdown } from "../../lib/canvas/markdown-image-streaming";
import { splitMarkdownImages } from "../../lib/canvas/markdown-image-segments";
const url = "https://scqny.jiansun.vip/ps-skill-api/results/job_test/01-%E6%A0%B7%E4%BE%8B.jpg";
const wrapped = `[![01-样例.jpg](${url})](${url})`;
const other = `[![缩略图](${url})](https://example.com/original.jpg)`;
test("every live network prefix withholds the entire unfinished linked image", () => {
const before = "## 生成图\n";
for (let n = 1; n < wrapped.length; n += 1) {
assert.equal(stableStreamingMarkdown(before + wrapped.slice(0, n), true), before, `prefix ${n}`);
}
assert.equal(stableStreamingMarkdown(before + wrapped, true), before + wrapped);
// An interrupted / completed response keeps malformed original text visible.
for (const suffix of ["[", "[!", "[![name](https://example.com/a.jpg)]("]) {
assert.equal(stableStreamingMarkdown(before + suffix, false), before + suffix);
}
});
test("every synthetic typewriter cursor exposes complete image markup or none", () => {
const content = `生成图\n${wrapped}\n${other}\n可编辑文件`;
const ranges = scanMarkdownImageStreaming(content).ranges;
assert.equal(ranges.length, 2);
let cursor = 0;
while (cursor < content.length) {
cursor = advancePastAtomicRanges(content.length, cursor + 1, ranges);
const visible = content.slice(0, cursor);
for (const range of ranges) {
assert.ok(cursor <= range.start || cursor >= range.end, `split at ${cursor}`);
}
if (cursor >= ranges[0].end) {
assert.ok(splitMarkdownImages(visible).some((item) => item.type === "image"));
}
}
assert.equal(cursor, content.length);
assert.deepEqual(splitMarkdownImages(other), [{ type: "text", value: other }]);
});
test("handles ordinary images, URL brackets, escaped text and code safely", () => {
const simple = "![图](https://example.com/a_(1).jpg)";
for (let n = 1; n < simple.length; n += 1) {
assert.equal(stableStreamingMarkdown("前文 " + simple.slice(0, n), true), "前文 ", `prefix ${n}`);
}
const literal = "`![code](https://example.com/a` and \\![literal] and [ordinary](https://example.com)";
assert.equal(stableStreamingMarkdown(literal, true), literal);
const fence = "~~~md\n[![sample](https://example.com\n~~~\n";
assert.equal(stableStreamingMarkdown(fence, true), fence);
assert.deepEqual(scanMarkdownImageStreaming(fence + simple).ranges, [{ start: fence.length, end: fence.length + simple.length }]);
const invalid = "![bad](https://example.com/a b) and later text";
assert.equal(stableStreamingMarkdown(invalid, true), invalid);
});
test("does not change normal links or security-sensitive complete markup", () => {
const values = ["[普通链接](https://example.com/a)", "[![x](https://example.com/x.jpg)](javascript:alert(1))", other];
for (const value of values) assert.equal(stableStreamingMarkdown(value, true), value);
});
test("coalesces overlapping typewriter table and image ranges without looping", () => {
const ranges = [{ start: 0, end: 20 }, { start: 10, end: 35 }, { start: 35, end: 40 }];
assert.equal(advancePastAtomicRanges(50, 1, ranges), 35);
assert.equal(advancePastAtomicRanges(50, 36, ranges), 40);
assert.equal(advancePastAtomicRanges(50, 50, ranges), 50);
});