Files
peoplelib/src/_test/note-assets.test.js
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 3ccd044527 feat: 内置阅读器、批注笔记与 AI 助手,发布 1.3.0
新增 PDF/EPUB/MOBI/AZW 内置阅读器,PDF 分段读取支持超大文件,
批注、读书与画布笔记、封面生成与本地导入。AI 助手支持三种协议、
图像上下文与安全 Markdown 渲染,上下文范围改为 选中/当前页/全文,
页面与全文无需选中文本即可发送,全文会提示可能超出模型限制。

便携版输出目录固定为 PeopleLib-windows-x64,不再随版本号变化,
避免升级后 data/ 被遗留在旧目录。

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-03 12:13:02 +08:00

97 lines
3.5 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
function fresh() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'peoplelib-note-assets-'));
delete require.cache[require.resolve('../reader/note-assets')];
const assets = require('../reader/note-assets');
assets.init(root);
return { root, assets };
}
function pdf(file, suffix = '') {
fs.writeFileSync(file, `%PDF-1.4\n${suffix}\n%%EOF\n`);
}
test('PDF 底版选择令牌绑定渲染进程且只能由保存流程解析', () => {
const { root, assets } = fresh();
const file = path.join(root, 'paper.pdf');
pdf(file, 'page one');
const staged = assets.stagePdf(file, 101);
assert.match(staged.token, /^[0-9a-f-]{36}$/);
assert.strictEqual(staged.name, 'paper.pdf');
assert.throws(() => assets.readDraft(staged.token, 202), /选择已失效/);
assert.match(assets.readDraft(staged.token, 101).subarray(0, 5).toString(), /^%PDF-/);
const canvasContent = {
version: 1,
pages: [{
id: 'pg_one',
width: 612,
height: 792,
background: { type: 'pdf', page: 1, draftToken: staged.token },
objects: []
}]
};
const resolved = assets.resolveDrafts(canvasContent, 101);
assert.match(resolved.content.pages[0].background.assetId, /^pdf_[a-f0-9]{64}$/);
assert.ok(!Object.prototype.hasOwnProperty.call(
resolved.content.pages[0].background,
'draftToken'
));
assert.deepStrictEqual(resolved.tokens, [staged.token]);
assets.commitTokens(resolved.tokens);
assert.throws(() => assets.readDraft(staged.token, 101), /选择已失效/);
assert.match(assets.readAsset(resolved.content.pages[0].background.assetId).toString(), /page one/);
});
test('相同 PDF 复用内容资源并按引用集合清理孤儿', () => {
const { root, assets } = fresh();
const first = path.join(root, 'first.pdf');
const copy = path.join(root, 'copy.pdf');
const other = path.join(root, 'other.pdf');
pdf(first, 'same bytes');
fs.copyFileSync(first, copy);
pdf(other, 'different');
const a = assets.stagePdf(first, 1);
const b = assets.stagePdf(copy, 1);
const c = assets.stagePdf(other, 1);
const resolve = (token) => assets.resolveDrafts({
version: 1,
pages: [{
id: 'pg_one',
width: 612,
height: 792,
background: { type: 'pdf', page: 1, draftToken: token },
objects: []
}]
}, 1);
const ar = resolve(a.token);
const br = resolve(b.token);
const cr = resolve(c.token);
assert.strictEqual(
ar.content.pages[0].background.assetId,
br.content.pages[0].background.assetId
);
assert.notStrictEqual(
ar.content.pages[0].background.assetId,
cr.content.pages[0].background.assetId
);
assets.commitTokens([...ar.tokens, ...br.tokens, ...cr.tokens]);
assert.strictEqual(assets.cleanup([ar.content.pages[0].background.assetId]), 1);
assert.doesNotThrow(() => assets.readAsset(ar.content.pages[0].background.assetId));
assert.throws(() => assets.readAsset(cr.content.pages[0].background.assetId));
});
test('PDF 底版拒绝伪造文件、非法资源 ID 和不存在资源', () => {
const { root, assets } = fresh();
const fake = path.join(root, 'fake.pdf');
fs.writeFileSync(fake, 'not a pdf');
assert.throws(() => assets.stagePdf(fake, 1), /不是有效 PDF/);
assert.throws(() => assets.readAsset('../escape'), /资源标识无效/);
assert.throws(() => assets.readAsset(`pdf_${'f'.repeat(64)}`));
});