Files
Agent-System/web/src/data/storage.windowCache.test.ts
T
weichenandCursor fc32ba3dc3 fix(web): version chat window cache and cover cross-user session list
Drop window-cache payloads that lack a schema field. Prove a non-owner listing another user's sessions is forbidden. Correct the stale test schema note in CURRENT.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 09:28:11 +00:00

112 lines
3.4 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
saveSessionWindowCache,
loadSessionWindowCache,
clearSessionWindowCache,
} from './storage';
import type { ChatDayBucket } from '../types/workbench';
function installMemoryLocalStorage(): { store: Map<string, string>; restore: () => void } {
const store = new Map<string, string>();
const originalWindow = (globalThis as { window?: unknown }).window;
const localStorage = {
getItem(key: string) {
return store.has(key) ? store.get(key)! : null;
},
setItem(key: string, value: string) {
store.set(key, value);
},
removeItem(key: string) {
store.delete(key);
},
};
(globalThis as { window: unknown }).window = { localStorage };
return {
store,
restore: () => {
if (originalWindow === undefined) {
delete (globalThis as { window?: unknown }).window;
} else {
(globalThis as { window: unknown }).window = originalWindow;
}
},
};
}
function sampleBucket(dayKey: string): ChatDayBucket {
return {
dayKey,
count: 1,
loadedCount: 1,
truncated: false,
firstMessageId: 1,
lastMessageId: 1,
messages: [
{
id: `msg-${dayKey}`,
sessionId: 's',
role: 'user',
content: 'hi',
timestamp: '12:00',
},
],
};
}
test('window cache LRU: 驱逐带 userId 前缀的完整 cache key,不留孤儿条目', () => {
const { store, restore } = installMemoryLocalStorage();
try {
const userId = 42;
// MAX_CACHED_WINDOWS = 3;写入 4 个不同 session 应驱逐最早的带前缀 key
const sessions = ['sess-a', 'sess-b', 'sess-c', 'sess-d'];
for (const sid of sessions) {
saveSessionWindowCache(sid, [sampleBucket(`2026-09-18-${sid}`)], userId);
}
const prefixed = [...store.keys()].filter((k) => k.startsWith('smdf_chat_window_u'));
assert.equal(prefixed.length, 3, `expected 3 prefixed window keys, got ${prefixed.join(',')}`);
assert.equal(
loadSessionWindowCache('sess-a', userId),
null,
'oldest sess-a with user prefix must be evicted'
);
assert.ok(loadSessionWindowCache('sess-b', userId));
assert.ok(loadSessionWindowCache('sess-c', userId));
assert.ok(loadSessionWindowCache('sess-d', userId));
// 无前缀 key 不应被误认为存活缓存
assert.equal(store.has('smdf_chat_window_sess-a'), false);
assert.equal(store.has('smdf_chat_window_sess-d'), false);
clearSessionWindowCache('sess-d', userId);
assert.equal(loadSessionWindowCache('sess-d', userId), null);
} finally {
restore();
}
});
test('window cache: 无 schema 的旧载荷不当作有效缓存', () => {
const { store, restore } = installMemoryLocalStorage();
try {
store.set('smdf_chat_window_u7_sess-old', JSON.stringify([{ dayKey: 'stale' }]));
assert.equal(loadSessionWindowCache('sess-old', 7), null);
} finally {
restore();
}
});
test('window cache: 读写同一 userId 前缀,跨用户不串读', () => {
const { restore } = installMemoryLocalStorage();
try {
saveSessionWindowCache('sess-shared', [sampleBucket('2026-09-18')], 1);
saveSessionWindowCache('sess-shared', [sampleBucket('2026-09-19')], 2);
const u1 = loadSessionWindowCache('sess-shared', 1);
const u2 = loadSessionWindowCache('sess-shared', 2);
assert.equal(u1?.[0]?.dayKey, '2026-09-18');
assert.equal(u2?.[0]?.dayKey, '2026-09-19');
} finally {
restore();
}
});