168 lines
5.4 KiB
TypeScript
168 lines
5.4 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); // 提问本身即为用户消息
|
||
});
|
||
|
||
test('mapRecordToMessage: restores task phase snapshot into card fields', () => {
|
||
const raw = {
|
||
id: 2048,
|
||
session_id: 'sess_task',
|
||
user_id: 1,
|
||
role: 'assistant' as const,
|
||
content: '已完成用户初始需求收集表',
|
||
intent: 'TRIGGER_TASK',
|
||
metadata: {
|
||
run_id: 'run_snap_1',
|
||
task_status: 'completed',
|
||
artifact: { title: '用户初始需求收集表', quality_score: 91 },
|
||
task_phase_snapshot: {
|
||
title: '用户初始需求收集表',
|
||
duration_ms: 42000,
|
||
label: '质量门禁通过',
|
||
status: 'completed',
|
||
steps: [
|
||
{ step: 'squad_assembly', icon: '👥', label: '组建小队', status: 'completed' },
|
||
{ step: 'quality_gate', icon: '🛡️', label: '质量门禁(95%)', status: 'completed', detail: '门禁' },
|
||
],
|
||
},
|
||
},
|
||
day_key: '2026-09-21',
|
||
created_at: '2026-09-21T10:00:00+08:00',
|
||
};
|
||
const msg = mapRecordToMessage(raw);
|
||
assert.equal(msg.taskRunId, 'run_snap_1');
|
||
assert.equal(msg.status, 'completed');
|
||
assert.equal(msg.taskDurationMs, 42000);
|
||
assert.equal(msg.livePhaseLabel, '质量门禁通过');
|
||
assert.equal(msg.phaseEvents?.length, 2);
|
||
assert.equal(msg.phaseSnapshot?.title, '用户初始需求收集表');
|
||
assert.equal(msg.phaseEvents?.[1].detail, '门禁');
|
||
});
|