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)}`)); });