import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; const source = (path) => readFile(new URL(path, import.meta.url), 'utf8'); const idHelpers = await import('../src/services/personal-assistant-id.ts'); test('Snowflake ID 全程保留字符串精度并安全生成详情路径', () => { const hugeId = '1990000000000000000'; assert.equal(idHelpers.normalizeBackendId(hugeId), hugeId); assert.equal(idHelpers.personalItemRoute(hugeId), `/pages/user/assistant/item?id=${hugeId}`); assert.equal(idHelpers.personalItemApiPath(hugeId), `/api/aihr/personal-assistant/items/${hugeId}`); assert.equal(idHelpers.personalSessionApiPath(hugeId), `/api/aihr/personal-assistant/sessions/${hugeId}`); }); test('个人助理 API 固定使用登录态且不接收 owner 参数', async () => { const [client, api] = await Promise.all([ source('../src/services/personal-assistant.ts'), source('../src/services/api.ts') ]); assert.match(client, /apiRequest/); assert.match(client, /\/api\/aihr\/personal-assistant\/ask/); assert.match(client, /\/api\/aihr\/personal-assistant\/items\/file/); assert.match(client, /uni\.uploadFile/); assert.match(client, /sessionId\?: string/); assert.match(client, /itemIds\?: string\[\]/); assert.match(client, /loadPersonalItem = \(id: string\)/); assert.match(client, /loadPersonalSession = \(id: string\)/); assert.doesNotMatch(client, /ownerUserId|tenantId/); assert.doesNotMatch(client, /clearAuthOnUnauthorized:\s*false/); assert.doesNotMatch(api, /statusCode === 401 \|\| statusCode === 403/); assert.match(api, /statusCode === 401 \|\| code === 401/); }); test('问答首页默认只查询个人资料并展示真实引用域', async () => { const page = await source('../src/pages/user/assistant/index.vue'); assert.match(page, /ref\(\['PERSONAL'\]\)/); assert.match(page, /我的资料/); assert.match(page, /企业知识按岗位权限/); assert.match(page, /企业 SOP/); assert.match(page, /外部网页/); assert.match(page, /citation\.domain/); assert.match(page, /citation\.itemId/); assert.match(page, /citation\.sourceType/); assert.doesNotMatch(page, /loadPersonalItem/); assert.match(page, /answer\.value = undefined/); assert.match(page, /requestGeneration/); assert.match(page, /:disabled="asking"/); assert.match(page, /if \(asking\.value\) return/); assert.match(page, /scopeSnapshot/); assert.match(page, /scopeKey\(scopes\.value\)/); assert.match(page, /收藏资料/); assert.doesNotMatch(page, /ownerUserId|tenantId/); }); test('个人助理先生成可编辑大纲并在确认后冻结来源', async () => { const [page, client] = await Promise.all([ source('../src/pages/user/assistant/index.vue'), source('../src/services/personal-assistant.ts') ]); assert.match(client, /createPersonalOutline/); assert.match(client, /updatePersonalOutline/); assert.match(client, /confirmPersonalOutline/); assert.match(page, /生成汇报提纲/); assert.match(page, /确认大纲/); assert.match(page, /资料来源不可编辑/); assert.match(page, /answer\.citations\?\.length/); }); test('资料库和收藏页覆盖分页筛选以及三种真实收藏方式', async () => { const [library, capture] = await Promise.all([ source('../src/pages/user/assistant/library.vue'), source('../src/pages/user/assistant/capture.vue') ]); assert.match(library, /pageNum/); assert.match(library, /sourceType/); assert.match(library, /status/); assert.match(library, /dateFrom/); assert.match(library, /dateTo/); assert.match(library, /mode="date"/); assert.match(library, /dateFrom: dateFrom\.value/); assert.match(library, /dateTo: dateTo\.value/); assert.match(library, /loadPersonalItems/); assert.match(library, /requestGeneration/); assert.match(library, /pageSnapshot/); assert.match(library, /mergePersonalRows/); assert.match(library, /pageNum\.value = Math\.max/); assert.match(capture, /TEXT/); assert.match(capture, /FILE/); assert.match(capture, /URL/); assert.match(capture, /createTextItem/); assert.match(capture, /uploadPersonalFile/); assert.match(capture, /createUrlItem/); }); test('个人资料详情包含全部状态、重试、下载和明确删除提示', async () => { const page = await source('../src/pages/user/assistant/item.vue'); assert.match(page, /QUEUED/); assert.match(page, /PARSING/); assert.match(page, /READY/); assert.match(page, /FAILED/); assert.match(page, /DELETING/); assert.match(page, /DELETED/); assert.match(page, /retryPersonalItem/); assert.match(page, /deletePersonalItem/); assert.match(page, /downloadPersonalItem/); assert.match(page, /附件、解析正文和搜索索引/); assert.match(page, /normalizeBackendId\(String\(options\?\.id/); assert.doesNotMatch(page, /Number\(options\?\.id|parseInt\(options\?\.id|Number\.isSafeInteger/); }); test('扫描 PDF 详情展示识别进度、部分失败和失败页重试', async () => { const ocr = await import('../src/services/personal-assistant-ocr.ts'); const page = await source('../src/pages/user/assistant/item.vue'); assert.equal(ocr.ocrProgressText({ required: true, status: 'RUNNING', totalPages: 86, processedPages: 20, successPages: 20, failedPages: 0, failedPageNumbers: [] }), '正在识别扫描 PDF:20/86 页'); assert.equal(ocr.ocrProgressText({ required: true, status: 'PARTIAL', totalPages: 8, processedPages: 8, successPages: 6, failedPages: 2, failedPageNumbers: [3, 7] }), '已收录,2 页识别失败'); assert.match(page, /retryPersonalOcrPages/); assert.match(page, /识别失败页/); assert.match(page, /ocrProgressPercent/); assert.match(page, /setTimeout/); }); test('会话页覆盖列表、详情和删除且不伪造会话', async () => { const page = await source('../src/pages/user/assistant/sessions.vue'); assert.match(page, /loadPersonalSessions/); assert.match(page, /loadPersonalSession/); assert.match(page, /deletePersonalSession/); assert.match(page, /暂无历史会话/); assert.match(page, /chat\.role\.toLowerCase\(\) === 'user'/); }); test('个人助理路由已注册且企业 SOP 问答保留独立入口', async () => { const [pages, sop] = await Promise.all([ source('../src/pages.json'), source('../src/pages/user/sop/index.vue') ]); for (const route of ['index', 'library', 'capture', 'item', 'sessions']) { assert.match(pages, new RegExp(`pages/user/assistant/${route}`)); } assert.match(sop, /个人助理/); assert.match(sop, /我的资料/); assert.match(sop, /企业 SOP/); assert.match(sop, /\/pages\/user\/assistant\/index/); });