Files
X-Financial/web/tests/workbench-ai-application-context-submit.test.mjs
caoxiaozhu 08f023243e fix(web): 多 task 草稿自动保存后继续推进下一 task
useWorkbenchAiApplicationPreviewFlow 在自动保存草稿分支透传 onApplicationActionCompleted,
确保草稿/提交完成后仍能按 remaining tasks 推进,修复多 task 报销场景后续步骤不启动。
更新 application-context-submit/intent-planner-model 测试,补充 bug 日志与开发日志。
2026-06-29 20:17:36 +08:00

234 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import test from 'node:test'
import { useWorkbenchAiApplicationPreviewFlow } from '../src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js'
function createRef(value) {
return { value }
}
function createInlineMessage(role, content, options = {}) {
return {
id: options.id || `msg-${Math.random().toString(16).slice(2)}`,
role,
content,
text: content,
paragraphs: String(content || '').split(/\n+/).filter(Boolean),
pending: Boolean(options.pending),
...options
}
}
function buildApplicationPreviewFlowHarness(messages, options = {}) {
const conversationMessages = createRef(messages)
const applicationSubmitConfirmOpen = createRef(false)
const applicationSubmitConfirmContext = createRef(null)
const persisted = createRef(0)
const flow = useWorkbenchAiApplicationPreviewFlow({
activateInlineConversation: () => {},
applicationPreviewEditor: createRef({}),
applicationSubmitConfirmContext,
applicationSubmitConfirmOpen,
assistantDraft: createRef(''),
cancelApplicationPreviewEditor: () => {},
clearAiModeFiles: () => {},
closeWorkbenchDatePicker: () => {},
commitApplicationPreviewEditor: async () => true,
conversationId: createRef('conversation-context-submit'),
conversationMessages,
conversationStarted: createRef(true),
createInlineMessage,
currentUser: createRef({ username: 'zhangsan@example.com', name: '张三' }),
handleApplicationPreviewEditorKeydown: () => {},
inlineConversationAutoScrollPinned: createRef(true),
isApplicationPreviewEditing: createRef(false),
openApplicationPreviewEditor: () => {},
persistCurrentConversation: () => { persisted.value += 1 },
pushInlineApplicationActionUserMessage: (text) => {
conversationMessages.value.push(createInlineMessage('user', text))
},
pushInlineUserMessage: (text) => {
conversationMessages.value.push(createInlineMessage('user', text))
},
refreshApplicationPreviewEstimate: async (preview) => preview,
removeWorkbenchDateTag: () => {},
replaceInlineMessage: (id, nextMessage) => {
const index = conversationMessages.value.findIndex((item) => item.id === id)
if (index >= 0) {
conversationMessages.value.splice(index, 1, nextMessage)
} else {
conversationMessages.value.push(nextMessage)
}
},
resolveApplicationPreviewEditorDateMax: () => '',
resolveApplicationPreviewEditorDateMin: () => '',
resolveApplicationPreviewEditorControl: () => null,
resolveApplicationPreviewEditorOptions: () => [],
resolveInlineThinkingEvents: (message) => message?.stewardPlan?.thinkingEvents || [],
resolveLatestInlineUserPrompt: () => '2026-02-20 至 2026-02-23去上海出差交通火车保存草稿',
scrollInlineConversationToBottom: () => {},
sending: createRef(false),
toast: () => {},
onApplicationActionCompleted: options.onApplicationActionCompleted
})
return {
applicationSubmitConfirmOpen,
conversationMessages,
flow,
persisted
}
}
test('workbench auto-saved application draft continues remaining steward task', async () => {
const originalFetch = globalThis.fetch
const requests = []
globalThis.fetch = async (url, options = {}) => {
const normalizedUrl = String(url)
if (normalizedUrl.includes('/reimbursements/application-preview-action')) {
const body = JSON.parse(String(options.body || '{}'))
requests.push({ url: normalizedUrl, body })
return {
ok: true,
async json() {
return {
status: 'succeeded',
result: {
draft_payload: {
claim_id: 'claim-auto-saved-draft',
claim_no: 'AEW2DDAFL',
status: 'draft'
}
}
}
}
}
}
throw new Error(`unexpected request: ${normalizedUrl}`)
}
try {
const continuedTasks = []
const remainingTasks = [{
task_id: 'task-reimbursement-2',
task_type: 'reimbursement',
assigned_agent: 'reimbursement_assistant',
summary: '报销昨天的业务招待费 2000 元',
ontology_fields: {
expense_type: 'entertainment',
amount: '2000元',
time_range: '2026-06-25',
reason: '业务招待费报销'
}
}]
const harness = buildApplicationPreviewFlowHarness([], {
onApplicationActionCompleted: (tasks, sourceMessage) => {
continuedTasks.push({ tasks, sourceMessage })
}
})
await harness.flow.startAiApplicationPreview('travel', '差旅费', '2月20-23日去上海出差3天服务国网服务器部署并且报销昨天的业务招待费2000元', {
autoSaveDraft: true,
stewardRemainingTasks: remainingTasks,
onApplicationActionCompleted: (tasks, sourceMessage) => {
continuedTasks.push({ tasks, sourceMessage, fromOptions: true })
}
})
assert.equal(requests.length, 1)
assert.equal(continuedTasks.length, 1)
assert.deepEqual(continuedTasks[0].tasks, remainingTasks)
assert.equal(continuedTasks[0].sourceMessage.stewardRemainingTasks, remainingTasks)
assert.equal(continuedTasks[0].fromOptions, true)
assert.doesNotMatch(harness.conversationMessages.value.at(-1).content, /继续处理费用报销/)
} finally {
globalThis.fetch = originalFetch
}
})
test('workbench saved application draft can be submitted by contextual text without re-planning', async () => {
const originalFetch = globalThis.fetch
const requests = []
globalThis.fetch = async (url, options = {}) => {
const normalizedUrl = String(url)
if (normalizedUrl.includes('/reimbursements/claims')) {
return {
ok: true,
async json() {
return { items: [] }
}
}
}
if (normalizedUrl.includes('/reimbursements/application-preview-action')) {
const body = JSON.parse(String(options.body || '{}'))
requests.push({ url: normalizedUrl, body })
return {
ok: true,
async json() {
return {
status: 'succeeded',
result: {
draft_payload: {
claim_id: 'claim-saved-draft',
claim_no: 'A20260220',
status: 'submitted',
approval_stage: '直属领导审批'
}
}
}
}
}
}
throw new Error(`unexpected request: ${normalizedUrl}`)
}
try {
const previewMessage = createInlineMessage('assistant', '申请核对表', {
id: 'application-preview-1',
applicationPreview: {
readyToSubmit: true,
fields: {
applicationType: '差旅费用申请',
time: '2026-02-20 至 2026-02-23',
location: '上海',
reason: '辅助国网仿生产服务器部署',
days: '4天',
transportMode: '火车',
amount: '1200元'
},
missingFields: [],
validationIssues: []
},
draftPayload: {
claim_id: 'claim-saved-draft',
claim_no: 'A20260220',
status: 'draft'
}
})
const harness = buildApplicationPreviewFlowHarness([
createInlineMessage('user', '2026-02-20 至 2026-02-23去上海出差交通火车保存草稿'),
previewMessage,
createInlineMessage('assistant', '### 申请草稿已保存', {
draftPayload: previewMessage.draftPayload
})
])
const handled = harness.flow.handleInlineApplicationPreviewTextAction(
'提交这个单据',
createRef(false)
)
assert.equal(handled, true)
await new Promise((resolve) => setTimeout(resolve, 0))
assert.equal(harness.applicationSubmitConfirmOpen.value, false)
assert.equal(requests.length, 1)
assert.equal(requests[0].body.context_json.application_edit_claim_id, 'claim-saved-draft')
assert.equal(requests[0].body.context_json.application_edit_mode, true)
assert.match(harness.conversationMessages.value.at(-1).content, /申请单据已生成/)
assert.ok(harness.persisted.value > 0)
} finally {
globalThis.fetch = originalFetch
}
})