Files
Agent-System/web/src/hooks/useChatWindow.test.ts
T
weichenandCursor b6aebc8176 fix(web): improve chat window refresh state and user-scoped cache
Distinguish authorized-empty and forbidden phases; skip caching day
buckets with zero messages; bind window cache keys to user id.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 03:06:11 +00:00

132 lines
4.1 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mapRecordToMessage, mapRawBucket } from '../api/chatWindow';
import { resolveTurnAnchorId } from '../engine/anchorNavigator';
test('mapRecordToMessage: 正确映射服务端消息记录为前端 ChatMessage', () => {
const raw = {
id: 1024,
session_id: 'sess_test_001',
user_id: 101,
role: 'user' as const,
content: '立项便携超声设备',
intent: 'SUPPLEMENT_INFO',
branch: 'EXPECTATION_HIT',
metadata: {
author_name: '张工',
attachments: [{ name: 'spec.pdf', size: '2MB', type: 'pdf' }],
},
day_key: '2026-09-10',
category: 'consult',
created_at: '2026-09-10T09:12:00+08:00',
};
const msg = mapRecordToMessage(raw);
assert.equal(msg.id, 'msg-1024');
assert.equal(msg.serverId, 1024);
assert.equal(msg.sessionId, 'sess_test_001');
assert.equal(msg.role, 'user');
assert.equal(msg.authorName, '张工');
assert.equal(msg.content, '立项便携超声设备');
assert.equal(msg.intentTag, 'SUPPLEMENT_INFO');
assert.equal(msg.branchTag, 'EXPECTATION_HIT');
assert.equal(msg.dayKey, '2026-09-10');
assert.equal(msg.category, 'consult');
assert.equal(msg.attachments?.length, 1);
});
test('refresh window: 空 messages 日桶不算有效窗口', () => {
const emptyBucket = {
day_key: '2026-09-10',
count: 4,
loaded_count: 0,
truncated: false,
first_message_id: 1,
last_message_id: 4,
messages: [],
};
const hasMessages = emptyBucket.messages.length > 0;
assert.equal(hasMessages, false, 'must not treat stats-only day bucket as populated window');
});
test('mapRawBucket: 正确映射服务端按日分片桶', () => {
const rawBucket = {
day_key: '2026-09-10',
count: 38,
loaded_count: 38,
truncated: false,
first_message_id: 1024,
last_message_id: 1061,
messages: [
{
id: 1024,
session_id: 'sess_test_001',
user_id: 101,
role: 'user' as const,
content: '立项需求',
day_key: '2026-09-10',
created_at: '2026-09-10T09:12:00+08:00',
},
],
};
const bucket = mapRawBucket(rawBucket);
assert.equal(bucket.dayKey, '2026-09-10');
assert.equal(bucket.count, 38);
assert.equal(bucket.loadedCount, 38);
assert.equal(bucket.truncated, false);
assert.equal(bucket.firstMessageId, 1024);
assert.equal(bucket.lastMessageId, 1061);
assert.equal(bucket.messages.length, 1);
assert.equal(bucket.messages[0].serverId, 1024);
});
test('Category navigation: chip 选中后准确匹配目标分类最新轮次并触发归一化定位', () => {
const allMessages: any[] = [
{
id: 'm-1',
serverId: 101,
sessionId: 'sess_1',
role: 'user',
category: 'task',
content: '请开始声学仿真任务',
timestamp: '10:00',
},
{
id: 'm-2',
serverId: 102,
sessionId: 'sess_1',
role: 'assistant',
category: 'task',
content: '仿真任务启动,正在分析网格...',
timestamp: '10:01',
},
{
id: 'm-3',
serverId: 103,
sessionId: 'sess_1',
role: 'user',
category: 'consult',
content: 'CE 认证周期大概多久?',
timestamp: '10:02',
},
];
// 1. 查找 task 分类的最新一条消息
const taskMsgs = allMessages.filter((m) => m.category === 'task');
const latestTask = taskMsgs[taskMsgs.length - 1];
assert.equal(latestTask.id, 'm-2');
// 2. 方案 A 轮次归一化:由于最新一条是 assistant 回复,必须归一化至该轮用户的提问 (m-1)
const normalizedId = resolveTurnAnchorId(latestTask.serverId || latestTask.id, allMessages);
assert.equal(normalizedId, 101); // 用户提问 m-1 的 serverId
// 3. 验证 consult 分类
const consultMsgs = allMessages.filter((m) => m.category === 'consult');
const latestConsult = consultMsgs[consultMsgs.length - 1];
const consultNormalizedId = resolveTurnAnchorId(latestConsult.serverId || latestConsult.id, allMessages);
assert.equal(consultNormalizedId, 103); // 提问本身即为用户消息
});