Files
cc-web/scripts/session-metadata-unit.js

96 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { performance } = require('node:perf_hooks');
const { createSessionHistoryStore } = require('../lib/session-history-store');
const source = fs.readFileSync(path.join(__dirname, '..', 'server.js'), 'utf8');
function extract(name) {
const start = source.indexOf(`function ${name}(`);
assert(start >= 0, `缺少生产函数 ${name}`);
const end = source.indexOf('\nfunction ', start + 1);
return source.slice(start, end < 0 ? undefined : end);
}
const keys = source.slice(source.indexOf('const SESSION_META_PREVIEW_TOP_LEVEL_KEYS ='), source.indexOf('\nfunction isJsonWhitespace('));
const names = ['isJsonWhitespace', 'skipJsonWhitespace', 'readJsonStringEnd', 'readJsonContainerEnd', 'readJsonValueEnd',
'parseJsonTopLevelPrefixFields', 'skipJsonWhitespaceBackward', 'readJsonStringStart', 'readJsonValueStart',
'parseJsonTopLevelSuffixFields', 'readSessionPreviewTopLevelFields', 'previewStringField', 'previewBooleanField',
'previewCreatedFromKind', 'normalizeCreatedFromSourceSessionId', 'readSessionPreview', 'loadSessionMetaFromFile'];
let parses = 0;
const json = { parse(value) { parses += 1; return JSON.parse(value); } };
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ccweb-session-metadata-'));
const observations = [];
const trackedFs = new Proxy(fs, { get(target, name) {
if (!['readFileSync', 'readSync', 'writeFileSync', 'renameSync'].includes(name)) return target[name];
return (...args) => { observations.push({ name, file: String(args[0]) }); return target[name](...args); };
} });
const store = createSessionHistoryStore({ sessionsDir: root, fs: trackedFs });
const api = new Function('fs', 'path', 'sessionHistoryStore', 'JSON', `
const SESSION_META_FULL_PARSE_MAX_BYTES = 65536;
const SESSION_META_PREVIEW_BYTES = 131072;
const SESSION_LOAD_MAX_BYTES = 33554432;
function normalizeSession(value) { if (value.messages.length) throw Error('列表不应规范化历史正文'); return value; }
function normalizeAgent(value) { return value || 'claude'; }
function getSessionAgent(value) { return normalizeAgent(value.agent); }
function normalizeGiteaSource(value) { return value || null; }
function sessionCreatedFromKind(value) { return value.createdFrom?.kind; }
function sanitizeId(value) { return /^[a-zA-Z0-9_-]+$/.test(value) ? value : ''; }
function isCodexThreadGoalActive(value) { return value?.status === 'active'; }
function plog() {}
${keys}
${names.map(extract).join('\n')}
return { suffix: parseJsonTopLevelSuffixFields, preview: readSessionPreviewTopLevelFields, meta: loadSessionMetaFromFile };
`)(trackedFs, path, store, json);
const fields = { id: '真实会话', title: '带"引号"及\\反斜杠', cwd: '/home/项目', hasUnread: false,
createdFrom: { kind: 'mcp', sourceSessionId: 'parent', nested: { title: '不能混入顶层' } },
giteaSource: { title: '来源', number: 2 }, pinnedAt: null };
const nested = { id: '嵌套ID', title: '工具标题', cwd: '/错误目录', input: '复杂字符串\\\\\"尾部,}]' };
const doc = JSON.stringify({ messages: Array.from({ length: 8000 }, () => nested), ...fields });
const tail = doc.slice(-131072);
parses = 0;
const start = performance.now();
assert.deepEqual(api.suffix(tail), fields);
assert(parses < 40, `尾部解析不应随嵌套伪字段增加而重复调用JSON.parse,实际${parses}`);
console.log(`通过:128KB高密度嵌套伪字段只解析${parses}次,耗时${Math.round(performance.now() - start)}ms`);
const fieldsStart = doc.lastIndexOf(',"id":"真实会话"') + 1;
for (let cut = 0; cut < fieldsStart; cut += 1031) assert.deepEqual(api.suffix(doc.slice(cut)), fields);
assert.deepEqual(api.suffix('{"title":"旧","title":"新","messages":[]}'), { title: '新' });
assert.deepEqual(api.suffix(JSON.stringify({ messages: [nested] }).slice(6)), {});
assert.equal(api.preview({ head: '{"id":"boundary","cwd":', tail: '"/不能拼接","messages":[]}' }).cwd, undefined);
for (const value of ['尾部\\', '尾部\\\\', '引号"', '\\"\\\\"\n', '转义括号}]\\"']) {
const text = JSON.stringify({ messages: [nested], title: value, createdFrom: { kind: value } });
assert.deepEqual(api.suffix(text), { title: value, createdFrom: { kind: value } });
}
console.log('通过:截断边界、转义引号、重复键、嵌套键和对象元数据');
const id = 'archive-only';
store.save({ id, title: '归档最新标题', agent: 'codexapp', cwd: '/home/真实项目', hasUnread: true,
codexAppGoal: { status: 'active' }, messages: [{ id: 'message', role: 'assistant', content: '正文'.repeat(1000000) }] }, { complete: true });
observations.length = 0;
assert.equal(api.meta(path.join(root, `${id}.json`)).title, '归档最新标题');
assert(!fs.existsSync(path.join(root, `${id}.json`)), '列举归档不应生成预览快照');
fs.writeFileSync(path.join(root, `${id}.json`), '{这是陈旧且损坏的缓存');
const meta = api.meta(path.join(root, `${id}.json`));
assert.equal(meta.title, '归档最新标题');
assert.equal(meta.hasUnread, true);
assert.equal(meta.codexAppGoalActive, true);
const manifestReads = observations.filter(item => item.name === 'readFileSync' && /manifest\.json$/.test(item.file)).length;
api.meta(path.join(root, `${id}.json`));
assert.equal(observations.filter(item => item.name === 'readFileSync' && /manifest\.json$/.test(item.file)).length, manifestReads,
'修订未变时列表不能反复解析归档清单');
assert(!observations.some(item => /chunks/.test(item.file)), '列表不得读取任何正文块');
assert(!observations.some(item => /write|rename/.test(item.name)), '列表不得重建快照或写文件');
assert(!observations.some(item => item.name === 'readFileSync' && item.file === path.join(root, `${id}.json`)), '有归档时不得解析快照');
const latest = store.loadMetadata(id);
latest.title = '修订后的标题';
latest.hasUnread = false;
store.save(latest);
assert.equal(api.meta(path.join(root, `${id}.json`)).title, '修订后的标题');
assert.equal(api.meta(path.join(root, `${id}.json`)).hasUnread, false);
fs.writeFileSync(path.join(root, 'legacy.json'), JSON.stringify({ id: 'legacy', messages: [nested], padding: 'x'.repeat(140000), ...fields }));
assert.equal(api.meta(path.join(root, 'legacy.json')).title, fields.title);
console.log('通过:归档列表不读正文、不写快照,缺失/损坏快照与旧格式均可列举');