import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { APPLICATION_WELCOME_QUICK_ACTIONS, APPROVAL_WELCOME_QUICK_ACTIONS, ASSISTANT_SESSION_MODE_OPTIONS, EXPENSE_WELCOME_QUICK_ACTIONS, SESSION_TYPE_APPLICATION, SESSION_TYPE_APPROVAL, SESSION_TYPE_EXPENSE, SESSION_TYPE_KNOWLEDGE, buildWelcomeQuickActions } from '../src/views/scripts/travelReimbursementConversationModel.js' import { GUIDED_ACTION_CONFIRM_REIMBURSEMENT_REVIEW, GUIDED_ACTION_CONTINUE_FILLING, GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR, GUIDED_ACTION_START_APPLICATION, GUIDED_ACTION_PROCESS_INTERRUPTION, GUIDED_ACTION_SELECT_EXPENSE_TYPE, GUIDED_ACTION_SELECT_REQUIRED_APPLICATION, GUIDED_ACTION_SELECT_QUERY_MODE, GUIDED_ACTION_SELECT_QUERY_STATUS, GUIDED_ACTION_START_REIMBURSEMENT, GUIDED_ACTION_START_STATUS_QUERY, GUIDED_FLOW_MODE_REIMBURSEMENT, GUIDED_FLOW_MODE_STATUS_QUERY, applyGuidedReimbursementAnswer, buildGuidedExpenseTypeActions, buildGuidedInterruptionActions, buildGuidedQueryModeActions, buildGuidedQueryStatusActions, buildGuidedReimbursementSummaryText, buildGuidedReviewConfirmationActions, buildGuidedReviewSubmitOptions, buildGuidedStatusQueryText, buildGuidedStepPromptText, createEmptyGuidedFlowState, createGuidedReimbursementState, createGuidedStatusQueryState, isGuidedReimbursementReadyForReview, normalizeGuidedFlowState, selectGuidedRequiredApplication, selectGuidedExpenseType, selectGuidedQueryMode, shouldConfirmGuidedInterruption, waitForGuidedApplicationSelection } from '../src/views/scripts/travelReimbursementGuidedFlowModel.js' import { buildRequiredApplicationActions, buildRequiredApplicationMissingText, buildRequiredApplicationSelectionText, filterRequiredApplicationCandidates, requiresApplicationBeforeReimbursement } from '../src/views/scripts/travelReimbursementApplicationLinkModel.js' import { SKIP_REQUIRED_APPLICATION_LINK_ACTION, buildReimbursementAssociationActions, buildReimbursementAssociationMissingText, buildReimbursementAssociationQueryPayload, buildReimbursementAssociationSelectionText, buildReimbursementAssociationSubmitOptions, buildReimbursementAssociationThinkingEvents } from '../src/views/scripts/travelReimbursementAssociationGateModel.js' import { ASSISTANT_SCOPE_ACTION_SWITCH, resolveAssistantScopeGuard } from '../src/utils/assistantSessionScope.js' const createViewScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/TravelReimbursementCreateView.js', import.meta.url)), 'utf8' ) const guidedFlowScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementGuidedFlow.js', import.meta.url)), 'utf8' ) const suggestedActionsScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSuggestedActions.js', import.meta.url)), 'utf8' ) const guidedModelScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/travelReimbursementGuidedFlowModel.js', import.meta.url)), 'utf8' ) const sessionStateScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSessionState.js', import.meta.url)), 'utf8' ) const submitComposerScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)), 'utf8' ) const messageHandlersScript = readFileSync( fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementCreateViewMessageHandlers.js', import.meta.url)), 'utf8' ) test('assistant session modes expose independent quick actions', () => { assert.deepEqual( ASSISTANT_SESSION_MODE_OPTIONS.map((item) => item.label), ['小财管家', '申请助手', '报销助手', '审核助手', '财务知识助手', '预算编制助手'] ) assert.deepEqual( EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.label), ['快速发起报销', '查询单据状态', '差旅计算器'] ) assert.deepEqual( EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.action), [ GUIDED_ACTION_START_REIMBURSEMENT, GUIDED_ACTION_START_STATUS_QUERY, GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR ] ) assert.ok(EXPENSE_WELCOME_QUICK_ACTIONS.every((item) => !item.prompt)) assert.equal(buildWelcomeQuickActions(SESSION_TYPE_EXPENSE).length, 3) assert.deepEqual( buildWelcomeQuickActions(SESSION_TYPE_APPLICATION).map((item) => item.label), APPLICATION_WELCOME_QUICK_ACTIONS.map((item) => item.label) ) assert.equal(buildWelcomeQuickActions(SESSION_TYPE_APPLICATION)[0].action, GUIDED_ACTION_START_APPLICATION) assert.ok(!buildWelcomeQuickActions(SESSION_TYPE_APPLICATION)[0].prompt) assert.ok( buildWelcomeQuickActions(SESSION_TYPE_APPLICATION).every((item) => item.action !== GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR) ) assert.ok( buildWelcomeQuickActions(SESSION_TYPE_APPROVAL).every((item) => item.action !== GUIDED_ACTION_OPEN_TRAVEL_CALCULATOR) ) assert.match(guidedFlowScript, /GUIDED_ACTION_START_APPLICATION/) assert.match(guidedFlowScript, /buildApplicationTemplatePreview/) assert.deepEqual( buildWelcomeQuickActions(SESSION_TYPE_APPROVAL).map((item) => item.label), APPROVAL_WELCOME_QUICK_ACTIONS.map((item) => item.label) ) assert.notDeepEqual( buildWelcomeQuickActions(SESSION_TYPE_KNOWLEDGE).map((item) => item.label), EXPENSE_WELCOME_QUICK_ACTIONS.map((item) => item.label), 'knowledge hot questions should stay independent' ) }) test('assistant session scope guard keeps business boundaries isolated', () => { const expenseInApplication = resolveAssistantScopeGuard('我想报销的士票', SESSION_TYPE_APPLICATION) assert.equal(expenseInApplication.targetSessionType, SESSION_TYPE_EXPENSE) assert.match(expenseInApplication.text, /申请助手/) assert.match(expenseInApplication.text, /报销助手/) assert.equal(expenseInApplication.suggestedActions[0].action_type, ASSISTANT_SCOPE_ACTION_SWITCH) assert.equal(expenseInApplication.suggestedActions[0].payload.session_type, SESSION_TYPE_EXPENSE) assert.equal(expenseInApplication.suggestedActions[0].payload.carry_text, '我想报销的士票') assert.equal(resolveAssistantScopeGuard('我想发起一笔费用申请', SESSION_TYPE_APPLICATION), null) assert.equal( resolveAssistantScopeGuard('去北京出差3天,支撑国网仿生产环境部署', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_APPLICATION ) assert.equal( resolveAssistantScopeGuard('去国网出差3天,协助仿生产环境部署', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_APPLICATION ) assert.equal( resolveAssistantScopeGuard('下周去上海支撑客户系统上线,预计3天', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_APPLICATION ) assert.equal( resolveAssistantScopeGuard('安排去深圳客户现场验收项目', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_APPLICATION ) assert.equal( resolveAssistantScopeGuard('我要报销去北京出差的费用', SESSION_TYPE_APPLICATION).targetSessionType, SESSION_TYPE_EXPENSE ) assert.equal( resolveAssistantScopeGuard('帮我查询待我审核的单据', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_APPROVAL ) assert.equal( resolveAssistantScopeGuard('差旅住宿标准是多少', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_KNOWLEDGE ) assert.equal( resolveAssistantScopeGuard('报销标准是多少', SESSION_TYPE_EXPENSE).targetSessionType, SESSION_TYPE_KNOWLEDGE ) assert.equal( resolveAssistantScopeGuard('解释这张单据酒店超标风险', SESSION_TYPE_EXPENSE, { hasActiveReviewPayload: true }), null ) }) test('guided reimbursement asks type first and walks travel fields in order', () => { const typeActions = buildGuidedExpenseTypeActions() assert.deepEqual( typeActions.map((action) => action.label), ['差旅费', '交通费', '住宿费', '业务招待费', '办公用品费', '其他费用'] ) assert.ok(typeActions.every((action) => action.action_type === GUIDED_ACTION_SELECT_EXPENSE_TYPE)) let state = createGuidedReimbursementState() assert.equal(state.mode, GUIDED_FLOW_MODE_REIMBURSEMENT) assert.equal(state.stepKey, 'expense_type') state = selectGuidedExpenseType(state, 'travel') assert.equal(state.stepKey, 'reason') assert.match(buildGuidedStepPromptText(state), /第 1 步:事由/) state = applyGuidedReimbursementAnswer(state, '去上海支持上海电力部署项目') assert.equal(state.stepKey, 'location') state = applyGuidedReimbursementAnswer(state, '上海') assert.equal(state.stepKey, 'time_range') state = applyGuidedReimbursementAnswer(state, '2026-05-20 至 2026-05-23,出差 3 天') assert.equal(state.stepKey, 'amount') state = applyGuidedReimbursementAnswer(state, '待核算') assert.equal(state.stepKey, 'attachments') state = applyGuidedReimbursementAnswer(state, '稍后上传') assert.ok(isGuidedReimbursementReadyForReview(state)) assert.match(buildGuidedReimbursementSummaryText(state), /已完成“差旅费”的引导填写/) assert.deepEqual( buildGuidedReviewConfirmationActions().map((action) => action.action_type), [GUIDED_ACTION_CONFIRM_REIMBURSEMENT_REVIEW] ) const submitOptions = buildGuidedReviewSubmitOptions(state) assert.equal(submitOptions.systemGenerated, true) assert.equal(submitOptions.extraContext.expense_scene_selection.expense_type, 'travel') assert.equal(submitOptions.extraContext.review_form_values.expense_type, '差旅费') assert.match(submitOptions.rawText, /事由:去上海支持上海电力部署项目/) assert.match(submitOptions.rawText, /出差时间\/天数:2026-05-20 至 2026-05-23,出差 3 天/) }) test('guided reimbursement requires application selection for travel and entertainment', () => { assert.equal(requiresApplicationBeforeReimbursement('travel'), true) assert.equal(requiresApplicationBeforeReimbursement('meal'), true) assert.equal(requiresApplicationBeforeReimbursement('transport'), false) const claimsPayload = { items: [ { id: 'app-travel', claim_no: 'AP-202605-001', employee_name: '张小青', expense_type: 'travel_application', reason: '去上海支持项目部署', location: '上海', amount: 1800, occurred_at: '2026-05-20T08:00:00Z', status: 'approved', created_at: '2026-06-02T00:58:00Z', risk_flags_json: [{ source: 'application_detail', application_detail: { application_business_time: '2026-05-20 至 2026-05-23', days: '4 天', transport_mode: '火车', lodging_daily_cap: '600元/天', subsidy_daily_cap: '120元/天', transport_policy: '按真实票据复核', policy_estimate: '住宿 2,400元 + 补贴 480元' } }] }, { id: 'app-meal', claim_no: 'AP-202605-002', employee_name: '张小青', expense_type: 'expense_application', reason: '客户招待沟通项目', location: '武汉', amount: 600, status: 'approved', created_at: '2026-05-21T08:00:00Z' }, { id: 'app-draft', claim_no: 'AP-202605-003', employee_name: '张小青', expense_type: 'travel_application', reason: '草稿出差申请', status: 'draft' }, { id: 'app-submitted', claim_no: 'AP-202605-005', employee_name: '张小青', expense_type: 'travel_application', reason: '审批中的出差申请', status: 'submitted' }, { id: 'app-archived-stale-key', claim_no: 'AP-202605-007', employee_name: '张小青', expense_type: 'travel_application', reason: '已归档申请单', status: 'archived', approvalKey: 'completed' }, { id: 'app-linked', claim_no: 'AP-202605-006', employee_name: '张小青', expense_type: 'travel_application', reason: '已生成报销草稿的出差申请', status: 'approved' }, { id: 're-linked-draft', claim_no: 'RE-202605-006', employee_name: '张小青', expense_type: 'travel', reason: '已关联申请单的报销草稿', status: 'draft', risk_flags_json: [{ source: 'application_link', application_claim_id: 'app-linked', application_claim_no: 'AP-202605-006' }] }, { id: 'app-other-user', claim_no: 'AP-202605-004', employee_name: '李四', expense_type: 'travel_application', reason: '其他员工出差申请', status: 'approved' } ] } const currentUser = { name: '张小青', username: 'xiaoqing.zhang' } const travelApplications = filterRequiredApplicationCandidates(claimsPayload, 'travel', currentUser) assert.deepEqual(travelApplications.map((item) => item.claim_no), ['AP-202605-001']) assert.match(buildRequiredApplicationSelectionText('travel', travelApplications), /需要先关联对应的申请单/) assert.match(buildRequiredApplicationMissingText('meal'), /不能继续这类报销流程/) const mealApplications = filterRequiredApplicationCandidates(claimsPayload, 'meal', currentUser) assert.deepEqual(mealApplications.map((item) => item.claim_no), ['AP-202605-002']) const actions = buildRequiredApplicationActions(travelApplications, GUIDED_ACTION_SELECT_REQUIRED_APPLICATION) assert.equal(actions[0].action_type, GUIDED_ACTION_SELECT_REQUIRED_APPLICATION) assert.equal(actions[0].payload.application_claim_no, 'AP-202605-001') const associationActions = buildReimbursementAssociationActions(travelApplications, '我要报销') assert.equal(associationActions[0].action_type, 'select_required_application') assert.equal(associationActions[0].payload.application_claim_no, 'AP-202605-001') assert.equal(associationActions.at(-1).action_type, SKIP_REQUIRED_APPLICATION_LINK_ACTION) assert.match(buildReimbursementAssociationSelectionText(travelApplications), /单独新建报销单/) assert.match(buildReimbursementAssociationSelectionText(travelApplications), /ai-document-card-list/) assert.match(buildReimbursementAssociationSelectionText(travelApplications), /ai-document-card--application/) assert.match(buildReimbursementAssociationMissingText(), /单独新建报销单/) const associationQueryPayload = buildReimbursementAssociationQueryPayload(travelApplications) assert.equal(associationQueryPayload.selectionMode, 'reimbursement_application_association') assert.equal(associationQueryPayload.records[0].claimNo, 'AP-202605-001') const completedThinking = buildReimbursementAssociationThinkingEvents('completed', { candidateCount: 1 }) assert.equal(completedThinking[0].title, '判断用户意图') assert.equal(completedThinking.at(-1).status, 'completed') const associationSubmitOptions = buildReimbursementAssociationSubmitOptions( associationActions[0].payload, '我要报销' ) assert.equal(associationSubmitOptions.skipDraftAssociationPrompt, true) assert.equal(associationSubmitOptions.extraContext.expense_scene_selection.application_claim_no, 'AP-202605-001') assert.equal(associationSubmitOptions.extraContext.review_form_values.application_business_time, '2026-05-20 至 2026-05-23') let state = waitForGuidedApplicationSelection(createGuidedReimbursementState(), 'travel', travelApplications) assert.equal(state.stepKey, 'application_selection') assert.equal(state.applicationCandidates[0].claim_no, 'AP-202605-001') state = selectGuidedRequiredApplication(state, actions[0].payload) assert.equal(state.stepKey, 'summary') assert.equal(isGuidedReimbursementReadyForReview(state), true) assert.equal(state.values.application_claim_no, 'AP-202605-001') assert.equal(state.values.application_business_time, '2026-05-20 至 2026-05-23') assert.equal(state.values.application_days, '4 天') assert.equal(state.values.application_transport_mode, '火车') assert.equal(state.values.application_lodging_daily_cap, '600元/天') assert.equal(state.values.application_subsidy_daily_cap, '120元/天') const summaryText = buildGuidedReimbursementSummaryText(state) assert.match(summaryText, /关联申请单:AP-202605-001/) assert.match(summaryText, /草稿详情中上传对应票据/) assert.doesNotMatch(summaryText, /事由:待补充/) const submitOptions = buildGuidedReviewSubmitOptions(state) assert.equal(submitOptions.extraContext.review_action, 'save_draft') assert.equal(submitOptions.extraContext.review_form_values.application_claim_no, 'AP-202605-001') assert.equal(submitOptions.extraContext.review_form_values.reason, '去上海支持项目部署') assert.equal(submitOptions.extraContext.review_form_values.location, '上海') assert.equal(submitOptions.extraContext.review_form_values.amount, '') assert.equal(submitOptions.extraContext.review_form_values.application_amount, '1800') assert.equal(submitOptions.extraContext.review_form_values.application_business_time, '2026-05-20 至 2026-05-23') assert.equal(submitOptions.extraContext.review_form_values.application_days, '4 天') assert.equal(submitOptions.extraContext.review_form_values.transport_mode, '火车') assert.equal(submitOptions.extraContext.review_form_values.application_transport_mode, '火车') assert.equal(submitOptions.extraContext.review_form_values.reimbursement_type, undefined) assert.equal(submitOptions.extraContext.review_form_values.reason_value, undefined) assert.equal(submitOptions.extraContext.review_form_values.business_time, undefined) assert.equal(submitOptions.extraContext.review_form_values.business_location, undefined) assert.equal(submitOptions.extraContext.review_form_values.application_lodging_daily_cap, '600元/天') assert.equal(submitOptions.extraContext.review_form_values.application_subsidy_daily_cap, '120元/天') assert.equal(submitOptions.extraContext.expense_scene_selection.application_claim_no, 'AP-202605-001') assert.match(submitOptions.rawText, /关联申请单:AP-202605-001/) assert.doesNotMatch(submitOptions.rawText, /事由:待补充/) }) test('guided reimbursement interrupts suspicious questions before expensive flow', () => { const state = selectGuidedExpenseType(createGuidedReimbursementState(), 'transport') assert.equal(shouldConfirmGuidedInterruption('送客户去机场', state), false) assert.equal(shouldConfirmGuidedInterruption('帮我查询一下上周的报销状态?', state), true) assert.deepEqual( buildGuidedInterruptionActions().map((action) => action.action_type), [GUIDED_ACTION_CONTINUE_FILLING, GUIDED_ACTION_PROCESS_INTERRUPTION] ) }) test('status query guide collects a query mode before calling existing query flow', () => { let state = createGuidedStatusQueryState() assert.equal(state.mode, GUIDED_FLOW_MODE_STATUS_QUERY) assert.equal(state.stepKey, 'query_mode') assert.ok(buildGuidedQueryModeActions().every((action) => action.action_type === GUIDED_ACTION_SELECT_QUERY_MODE)) state = selectGuidedQueryMode(state, 'status') assert.equal(state.stepKey, 'status_value') assert.ok(buildGuidedQueryStatusActions().every((action) => action.action_type === GUIDED_ACTION_SELECT_QUERY_STATUS)) assert.equal(buildGuidedStatusQueryText(state, '已归档'), '帮我查询已归档的报销单据,筛选最近的 5 条记录') const keywordState = selectGuidedQueryMode(createGuidedStatusQueryState(), 'keyword') assert.equal(keywordState.stepKey, 'query_value') assert.equal( buildGuidedStatusQueryText(keywordState, '上海电力'), '帮我查询地点或事由包含“上海电力”的报销单据状态,筛选最近的 5 条记录' ) }) test('guided flow state is serializable and restored through session state', () => { const empty = createEmptyGuidedFlowState() assert.deepEqual(normalizeGuidedFlowState({ mode: 'bad' }), empty) assert.deepEqual( normalizeGuidedFlowState({ mode: GUIDED_FLOW_MODE_REIMBURSEMENT, stepKey: 'amount', expenseType: 'travel', values: { amount: 200, attachment_names: ['a.pdf', '', 'a.pdf'] }, pendingInterruptionText: '查询状态?' }), { mode: GUIDED_FLOW_MODE_REIMBURSEMENT, stepKey: 'amount', expenseType: 'travel', values: { amount: '200', attachment_names: ['a.pdf'] }, pendingInterruptionText: '查询状态?', applicationCandidates: [] } ) assert.match(sessionStateScript, /guidedFlowState:\s*normalizeGuidedFlowState\(state\.guidedFlowState\)/) assert.match(sessionStateScript, /runtimeRefs\.guidedFlowState\?\.value/) assert.match(sessionStateScript, /guidedFlowState,\s*\n\s*insightPanelCollapsed/) assert.match(sessionStateScript, /function refreshWelcomeQuickActions/) assert.match(sessionStateScript, /buildWelcomeQuickActions\(/) assert.match(sessionStateScript, /resolveAccessibleSessionTypes\(\)\.reduce/) assert.match(sessionStateScript, /props\.entrySource === 'application' \? SESSION_TYPE_APPLICATION : SESSION_TYPE_EXPENSE/) assert.match(sessionStateScript, /const canRestorePersistedInitialState =[\s\S]*shouldPersistLocalSnapshot/) }) test('guided flow is local until final confirmation or collected query handoff', () => { assert.doesNotMatch(guidedFlowScript, /runOrchestrator/) assert.doesNotMatch(guidedFlowScript, /startExpenseClaimDraftFlowStep/) assert.match(guidedModelScript, /review_action:\s*['"]save_draft['"]/) assert.match(guidedFlowScript, /fetchExpenseClaims/) assert.match(guidedFlowScript, /GUIDED_ACTION_SELECT_REQUIRED_APPLICATION/) assert.match(guidedFlowScript, /isGuidedReimbursementReadyForReview\(guidedFlowState\.value\)[\s\S]*pushReimbursementSummary\(\)/) assert.match(guidedFlowScript, /isGuidedReimbursementReadyForReview\(currentState\) && fileNames\.length[\s\S]*buildGuidedReviewSubmitOptions\(currentState, mergedFiles\)[\s\S]*skipDraftAssociationPrompt:\s*true[\s\S]*skipUserMessage:\s*true[\s\S]*submitExistingComposer\(submitOptions\)/) assert.doesNotMatch(guidedFlowScript, /amount:\s*applicationAmount/) assert.match(guidedFlowScript, /amount:\s*''/) assert.match(guidedFlowScript, /if \(!applications\.length\) \{[\s\S]*guidedFlowState\.value = createEmptyGuidedFlowState\(\)[\s\S]*meta: \['缺少可关联申请单'\][\s\S]*\}\)/) assert.doesNotMatch(guidedFlowScript, /meta: \['缺少可关联申请单'\],[\s\S]{0,120}suggestedActions: buildGuidedExpenseTypeActions\(\)/) assert.match(guidedFlowScript, /handleSceneSelectionApplicationGate/) assert.match(createViewScript, /handleSceneSelectionApplicationGate/) assert.match(createViewScript, /submitComposerFromMessageHandlers/) assert.match(messageHandlersScript, /if \(await handleGuidedComposerSubmit\(options\)\) return null[\s\S]*return submitComposerInternal\(options\)/) assert.match(suggestedActionsScript, /ASSISTANT_SCOPE_ACTION_SWITCH/) assert.match(suggestedActionsScript, /actionPayload\.carry_text/) assert.match(suggestedActionsScript, /targetSessionType === SESSION_TYPE_EXPENSE[\s\S]*carryText === '我要报销'[\s\S]*pushExpenseAssociationGatePrompt\(carryText\)/) assert.match(suggestedActionsScript, /actionType === SKIP_REQUIRED_APPLICATION_LINK_ACTION[\s\S]*pushExpenseSceneSelectionPrompt/) assert.match(suggestedActionsScript, /actionType === 'confirm_expense_intent'[\s\S]*pushExpenseAssociationGatePrompt\(originalMessage\)/) assert.match(suggestedActionsScript, /pushReimbursementAssociationPromptMessage\(\{[\s\S]*skipDraftCheck: Boolean\(options\.skipDraftCheck\)/) assert.match(submitComposerScript, /waitForExpenseSceneSelection[\s\S]*pushReimbursementAssociationPromptMessage\(\{[\s\S]*rawText/) assert.match(submitComposerScript, /fetchExpenseClaims[\s\S]*currentUser/) assert.doesNotMatch(submitComposerScript, /if \(waitForExpenseSceneSelection\) \{[\s\S]{0,260}buildExpenseSceneSelectionMessage/) assert.match(submitComposerScript, /resolveAssistantScopeGuard/) assert.match(submitComposerScript, /skipScopeGuard/) assert.match(guidedFlowScript, /submitExistingComposer\(submitOptions\)/) assert.match(guidedFlowScript, /submitExistingComposer\(\{[\s\S]*pendingText:\s*'正在查询单据状态\.\.\.'/) })