import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { buildExpenseSceneSelectionActions } from '../src/utils/expenseAssistantActions.js' import { buildExpenseSceneSelectionMessage } from '../src/views/scripts/travelReimbursementConversationModel.js' const aiMode = readFileSync( fileURLToPath(new URL('../src/components/business/PersonalWorkbenchAiMode.vue', import.meta.url)), 'utf8' ) test('expense scene selection message asks for type first and mentions application gate', () => { const text = buildExpenseSceneSelectionMessage('帮我发起一笔报销,并检查需要准备哪些票据材料。') assert.match(text, /先选|选择.*报销类型|报销场景/) assert.match(text, /差旅|招待|申请单|关联申请单/) }) test('expense scene actions mark travel and meal as requiring application', () => { const actions = buildExpenseSceneSelectionActions('帮我发起一笔报销,并检查需要准备哪些票据材料。') const travel = actions.find((action) => action.payload.expense_type === 'travel') const meal = actions.find((action) => action.payload.expense_type === 'meal') const transport = actions.find((action) => action.payload.expense_type === 'transport') assert.equal(travel.payload.requires_application_before_reimbursement, true) assert.equal(travel.payload.next_session_type, 'application') assert.equal(meal.payload.requires_application_before_reimbursement, true) assert.equal(meal.payload.next_session_type, 'application') assert.equal(transport.payload.requires_application_before_reimbursement, false) assert.equal(transport.payload.next_session_type, 'expense') }) test('AI mode quick reimbursement card opens scene selection before steward plan', () => { assert.match( aiMode, /function runAiModeAction\(item\) {[\s\S]{0,220}pushInlineExpenseSceneSelectionPrompt\(item\.prompt, item\.label\)/ ) }) test('AI mode expense scene selection stays in the inline conversation without opening the create view', () => { assert.match(aiMode, /actionType === 'select_expense_type'/) assert.doesNotMatch(aiMode, /emit\('open-assistant'/) }) test('AI mode offers an inline application shortcut when no candidate application exists', () => { assert.match(aiMode, /!candidates\.length/) assert.match(aiMode, /ai_application_start_inline/) assert.match(aiMode, /buildRequiredApplicationMissingText/) assert.match(aiMode, /function startAiApplicationDraft/) }) test('AI mode steward reimbursement action opens expense scene selection locally', () => { assert.match(aiMode, /buildExpenseSceneSelectionMessage/) assert.match(aiMode, /buildExpenseSceneSelectionActions/) assert.match(aiMode, /SESSION_TYPE_EXPENSE/) assert.match(aiMode, /function pushInlineExpenseSceneSelectionPrompt/) assert.match(aiMode, /payload\?\.session_type[\s\S]*SESSION_TYPE_EXPENSE/) assert.match(aiMode, /pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)/) assert.match( aiMode, /SESSION_TYPE_EXPENSE[\s\S]{0,140}pushInlineExpenseSceneSelectionPrompt\(carryText, action\.label\)[\s\S]{0,40}return/ ) }) test('AI mode attaches required application lookup result before steward planning', () => { assert.match(aiMode, /async function attachAiRequiredApplicationGate\(planRequest, prompt\)/) assert.match(aiMode, /fetchExpenseClaims\(\)/) assert.match(aiMode, /filterRequiredApplicationCandidates\(claims, 'travel', currentUser\.value \|\| \{\}\)/) assert.match(aiMode, /required_application_gate/) assert.match(aiMode, /await attachAiRequiredApplicationGate\(planRequest, prompt\)/) }) test('AI mode automatically continues required application gate decisions from steward plan', () => { assert.match(aiMode, /function continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/) assert.match(aiMode, /flow\.flowId === 'travel_application'[\s\S]*startAiApplicationDraft\('travel', '差旅费'/) assert.match(aiMode, /flow\.flowId === 'travel_reimbursement'[\s\S]*startAiExpenseDraft\('travel', '差旅费', true/) assert.match(aiMode, /continueAiRequiredApplicationGateFromPlan\(normalizedPlan\)/) })