166 lines
7.5 KiB
JavaScript
166 lines
7.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import test, { after } from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createServer } from 'vite';
|
|
|
|
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8');
|
|
|
|
const vite = await createServer({
|
|
root: fileURLToPath(new URL('..', import.meta.url)),
|
|
configFile: false,
|
|
optimizeDeps: { noDiscovery: true },
|
|
server: { middlewareMode: true, hmr: false },
|
|
appType: 'custom'
|
|
});
|
|
|
|
after(async () => vite.close());
|
|
|
|
const { mergeKnowledgeSnippets } = await vite.ssrLoadModule('/src/services/knowledge.ts');
|
|
|
|
test('个人助理客户端使用确认式记忆接口且确认请求携带稳定幂等键', async () => {
|
|
const service = await read('../src/services/personal-assistant.ts');
|
|
const card = await read('../src/components/chat/MemoryCandidateCard.vue');
|
|
|
|
assert.match(service, /\/api\/aihr\/personal-assistant\/memory-candidates/);
|
|
assert.match(service, /\$\{candidateBase\}\/\$\{candidateId\}\/confirm/);
|
|
assert.match(service, /\$\{candidateBase\}\/\$\{candidateId\}\/dismiss/);
|
|
assert.match(service, /\/api\/aihr\/service-memories/);
|
|
assert.match(service, /\/api\/aihr\/personal-assistant\/assistant-captures/);
|
|
assert.match(card, /const idempotencyKey = ref\(createMemoryIdempotencyKey\(props\.candidate\.id\)\)/);
|
|
assert.match(card, /if \(submitting\.value \|\| completed\.value\) return/);
|
|
assert.match(card, /expectedVersion: props\.candidate\.version/);
|
|
assert.match(card, /enableReminder: reminderEnabled\.value/);
|
|
assert.match(card, /saveScope: saveScope \|\| undefined/);
|
|
assert.match(card, /\.\.\.value/);
|
|
for (const field of ['projectCode', 'title', 'detail', 'status']) assert.match(card, new RegExp(field));
|
|
});
|
|
|
|
test('统一采集确认卡把缺失字段作为可选提示并让用户选择保存范围', async () => {
|
|
const card = await read('../src/components/chat/MemoryCandidateCard.vue');
|
|
|
|
assert.match(card, /candidate\.status === 'NEEDS_INPUT'/);
|
|
assert.match(card, /candidate\.status === 'DRAFT'/);
|
|
assert.match(card, /const reminderEnabled = ref\(false\)/);
|
|
assert.match(card, />修改</);
|
|
assert.match(card, />暂不保存</);
|
|
assert.match(card, />确认保存</);
|
|
assert.match(card, />仅自己保存</);
|
|
assert.match(card, />提交公司处理</);
|
|
assert.match(card, /可继续补充:/);
|
|
assert.doesNotMatch(card, />还需要补充信息</);
|
|
assert.match(card, /v-if="reminderEnabled"/);
|
|
for (const field of ['buildingName', 'unitName', 'roomNo']) {
|
|
assert.match(card, new RegExp(`value: draft\\.value\\.${field}`));
|
|
}
|
|
assert.match(card, /filter\(\(item\) => !item\.value\?\.trim\(\)\)\.map/);
|
|
assert.match(card, /missingProjectLocationLabels\.value\.length/);
|
|
assert.match(card, /还需补充:\$\{missingProjectLocationLabels\.value\.join\('、'\)\}/);
|
|
});
|
|
|
|
test('工作助手页面展示任务过期后不会消费待执行查询或数据工具', async () => {
|
|
const page = await read('../src/pages/user/sop/index.vue');
|
|
|
|
assert.match(page, /const isCurrentShow = \(\) => generation === showGeneration && accountKey === currentAccountKey\(\)/);
|
|
assert.match(page, /const pending = uni\.getStorageSync\(pendingSopQueryKey\);\s*if \(!isCurrentShow\(\)\) return;/);
|
|
assert.match(page, /const pendingTool = uni\.getStorageSync\(pendingToolKey\);[\s\S]*?if \(!isCurrentShow\(\)\) return;/);
|
|
assert.match(page, /if \(!isCurrentShow\(\)\) return;\s*uni\.removeStorageSync\(pendingToolKey\)/);
|
|
assert.match(page, /if \(!isCurrentShow\(\)\) return;\s*uni\.removeStorageSync\(pendingSopQueryKey\)/);
|
|
assert.match(page, /const requestShowGeneration = showGeneration/);
|
|
assert.match(page, /const requestAccountKey = currentAccountKey\(\)/);
|
|
assert.match(page, /ensureEmployeePosition\('\/pages\/user\/sop\/index'\)\) \|\| !isCurrentQuestion\(\)/);
|
|
});
|
|
|
|
test('我的资料与记忆展示个人记录和公司流转状态', async () => {
|
|
const [page, service] = await Promise.all([
|
|
read('../src/pages/user/assistant/memories.vue'),
|
|
read('../src/services/personal-assistant.ts')
|
|
]);
|
|
|
|
assert.match(page, /getAssistantCaptures/);
|
|
assert.match(page, />我的记录</);
|
|
assert.match(page, /仅自己保存的内容不会提交公司/);
|
|
assert.match(page, /deliveryLabel\(item\.deliveryStatus\)/);
|
|
assert.match(service, /assistant-captures\?limit=50/);
|
|
});
|
|
|
|
test('工作助手保留原问答链并只提供显式查全网入口', async () => {
|
|
const [page, header, knowledge] = await Promise.all([
|
|
read('../src/pages/user/sop/index.vue'),
|
|
read('../src/components/chat/AskSourceHeader.vue'),
|
|
read('../src/services/knowledge.ts')
|
|
]);
|
|
|
|
assert.match(header, />\s*工作助手\s*<\/button>/);
|
|
assert.match(header, />\s*查全网\s*<\/button>/);
|
|
assert.match(page, /@select-web="openWebAi"/);
|
|
assert.match(page, /<MemoryCandidateCard/);
|
|
assert.match(page, /msg\.result\.answer \|\| msg\.result\.snippets\?\.length \|\| !msg\.result\.memoryCandidate/);
|
|
assert.match(page, /msg\.result\.noEvidence/);
|
|
assert.match(page, /@click="openWebAi">去查全网/);
|
|
assert.match(knowledge, /url: '\/api\/knowledge\/query'/);
|
|
assert.match(knowledge, /memoryCandidate: response\.memoryCandidate \|\| legacy\.memoryCandidate/);
|
|
assert.match(page, /visibleAccountKey !== accountKey/);
|
|
assert.match(page, /accountKey !== currentAccountKey\(\)/);
|
|
assert.match(page, /const generation = \+\+showGeneration/);
|
|
assert.match(page, /generation === showGeneration && accountKey === currentAccountKey\(\)/);
|
|
assert.match(page, /onHide\(\(\) => \{\s*showGeneration \+= 1/);
|
|
assert.match(page, /onUnload\(\(\) => \{\s*showGeneration \+= 1/);
|
|
assert.match(page, /项目记录/);
|
|
assert.match(page, /serviceMemoryStatusLabel/);
|
|
assert.match(page, /!nextResult\.answer && !nextResult\.snippets\?\.length && !nextResult\.memoryCandidate/);
|
|
assert.doesNotMatch(page, /setStorageSync\([^\n]*web[^\n]*query/i);
|
|
});
|
|
|
|
test('企业与项目引用合并去重并保留项目状态和时间', () => {
|
|
const snippets = mergeKnowledgeSnippets([
|
|
{
|
|
docTitle: '住户服务规范',
|
|
content: '先确认需求。',
|
|
fragmentId: 7,
|
|
sourceType: 'DOCUMENT',
|
|
domain: 'ENTERPRISE'
|
|
},
|
|
{
|
|
docTitle: '3栋2单元1201跟进记录',
|
|
content: '等待回访。',
|
|
docId: '901',
|
|
sourceType: 'PROJECT_SERVICE',
|
|
domain: 'PROJECT_SERVICE',
|
|
status: 'IN_PROGRESS',
|
|
occurredAt: '2026-07-18T14:00:00+08:00',
|
|
updatedAt: '2026-07-19T09:20:00+08:00'
|
|
}
|
|
], [
|
|
{
|
|
docTitle: '住户服务规范',
|
|
content: '先确认需求。',
|
|
fragmentId: 7,
|
|
spaceCode: 'enterprise-sop'
|
|
}
|
|
]);
|
|
|
|
assert.equal(snippets.length, 2);
|
|
assert.equal(snippets[0].domain, 'ENTERPRISE');
|
|
assert.equal(snippets[0].spaceCode, 'enterprise-sop');
|
|
assert.equal(snippets[1].domain, 'PROJECT_SERVICE');
|
|
assert.equal(snippets[1].status, 'IN_PROGRESS');
|
|
assert.equal(snippets[1].updatedAt, '2026-07-19T09:20:00+08:00');
|
|
});
|
|
|
|
test('我的页面和路由提供首版资料与记忆入口', async () => {
|
|
const [profile, memories, pages] = await Promise.all([
|
|
read('../src/pages/user/profile/index.vue'),
|
|
read('../src/pages/user/assistant/memories.vue'),
|
|
read('../src/pages.json')
|
|
]);
|
|
|
|
assert.match(profile, /我的资料与记忆/);
|
|
assert.match(profile, /openMemories/);
|
|
assert.match(memories, /待确认/);
|
|
assert.match(memories, /项目跟进/);
|
|
assert.match(memories, /仅展示当前账号有权限查看的项目服务记录/);
|
|
assert.match(memories, /项目:\{\{ item\.projectCode \}\}/);
|
|
assert.match(pages, /pages\/user\/assistant\/memories/);
|
|
});
|