2026-05-25 13:35:39 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
2026-05-30 15:46:51 +08:00
import {
buildOperationFeedbackPayload ,
normalizeOperationFeedbackContext
} from '../src/composables/useOperationFeedback.js'
2026-05-25 13:35:39 +08:00
import {
SESSION _TYPE _APPLICATION ,
SESSION _TYPE _EXPENSE ,
SESSION _TYPE _KNOWLEDGE ,
2026-05-30 15:46:51 +08:00
buildMessageMeta ,
2026-05-25 13:35:39 +08:00
buildWelcomeInsight ,
2026-05-30 15:46:51 +08:00
buildWelcomeMessage ,
createMessage ,
filterVisibleMessageMeta
2026-05-25 13:35:39 +08:00
} from '../src/views/scripts/travelReimbursementConversationModel.js'
const appShellRouteView = readFileSync (
fileURLToPath ( new URL ( '../src/views/AppShellRouteView.vue' , import . meta . url ) ) ,
'utf8'
)
const appShellComposable = readFileSync (
fileURLToPath ( new URL ( '../src/composables/useAppShell.js' , import . meta . url ) ) ,
'utf8'
)
2026-06-09 08:32:00 +00:00
const requestsComposable = readFileSync (
fileURLToPath ( new URL ( '../src/composables/useRequests.js' , import . meta . url ) ) ,
'utf8'
)
2026-05-25 13:35:39 +08:00
const assistantScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/TravelReimbursementCreateView.js' , import . meta . url ) ) ,
'utf8'
)
2026-06-22 11:58:53 +08:00
const assistantCreateStateScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementCreateViewState.js' , import . meta . url ) ) ,
'utf8'
)
const assistantCreateLifecycleScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementCreateViewLifecycle.js' , import . meta . url ) ) ,
'utf8'
)
const assistantCreateControlsScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementCreateViewControls.js' , import . meta . url ) ) ,
'utf8'
)
const assistantMessageActionsScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementMessageActions.js' , import . meta . url ) ) ,
'utf8'
)
2026-05-30 15:46:51 +08:00
const assistantSubmitComposerScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementSubmitComposer.js' , import . meta . url ) ) ,
'utf8'
)
2026-06-22 11:58:53 +08:00
const assistantSubmitResponseModelScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/travelReimbursementSubmitResponseModel.js' , import . meta . url ) ) ,
'utf8'
)
2026-06-03 09:25:23 +08:00
const assistantSessionStateScript = readFileSync (
fileURLToPath ( new URL ( '../src/views/scripts/useTravelReimbursementSessionState.js' , import . meta . url ) ) ,
'utf8'
)
2026-06-22 11:58:53 +08:00
const assistantSurface = [
assistantScript ,
assistantCreateStateScript ,
assistantCreateLifecycleScript ,
assistantCreateControlsScript ,
assistantMessageActionsScript ,
assistantSubmitComposerScript ,
assistantSubmitResponseModelScript ,
assistantSessionStateScript
] . join ( '\n' )
2026-05-25 13:35:39 +08:00
const assistantTemplate = readFileSync (
fileURLToPath ( new URL ( '../src/views/TravelReimbursementCreateView.vue' , import . meta . url ) ) ,
'utf8'
)
2026-05-30 15:46:51 +08:00
const messageItemTemplate = readFileSync (
fileURLToPath ( new URL ( '../src/components/travel/TravelReimbursementMessageItem.vue' , import . meta . url ) ) ,
'utf8'
)
const chatViewTemplate = readFileSync (
fileURLToPath ( new URL ( '../src/views/ChatView.vue' , import . meta . url ) ) ,
'utf8'
)
2026-05-25 13:35:39 +08:00
test ( 'application and reimbursement entries open the same financial assistant modal' , ( ) => {
assert . match ( appShellRouteView , /<TravelReimbursementCreateView[\s\S]*:entry-source="smartEntryContext\.source"/ )
assert . match ( appShellRouteView , /@create-request="openTravelCreate"/ )
assert . match ( appShellRouteView , /@create-application="openExpenseApplicationCreate"/ )
assert . match ( appShellRouteView , /@new-application="openExpenseApplicationCreate"/ )
assert . doesNotMatch ( appShellRouteView , /ExpenseApplicationDialog/ )
} )
2026-06-03 09:25:23 +08:00
test ( 'documents center reloads immediately when entered or clicked again' , ( ) => {
assert . match ( appShellRouteView , /:refresh-token="documentCenterRefreshToken"/ )
assert . match ( appShellRouteView , /@reload="reloadRequests"/ )
assert . match ( appShellComposable , /const documentCenterRefreshToken = ref\(0\)/ )
assert . match ( appShellComposable , /async function reloadDocumentCenterRequests\(\) \{[\s\S]*documentCenterRefreshToken\.value \+= 1[\s\S]*return reloadRequests\(\)/ )
assert . match ( appShellComposable , /if \(view === 'documents'\) \{[\s\S]*void reloadDocumentCenterRequests\(\)/ )
assert . match ( appShellComposable , /shouldRefreshCurrentDocumentCenter[\s\S]*route\.name === 'app-documents'[\s\S]*void reloadDocumentCenterRequests\(\)/ )
assert . match ( appShellComposable , /documentCenterRefreshToken,/ )
assert . match ( appShellComposable , /reloadDocumentCenterRequests,/ )
} )
2026-06-09 08:32:00 +00:00
test ( 'documents center uses the full request list instead of the global date-filtered list' , ( ) => {
assert . match (
appShellRouteView ,
/<DocumentsCenterView[\s\S]*:filtered-requests="requests"[\s\S]*:has-data="requests\.length > 0"/
)
assert . doesNotMatch (
appShellRouteView ,
/<DocumentsCenterView[\s\S]*:filtered-requests="filteredRequests"/
)
} )
test ( 'workbench summary merges approval inbox requests without polluting document center rows' , ( ) => {
2026-07-17 14:14:08 +08:00
assert . match (
appShellComposable ,
/import \{\s*REIMBURSEMENT_LIST_PREVIEW_PARAMS,\s*extractExpenseClaimItems,\s*fetchApprovalExpenseClaims,\s*fetchExpenseClaimDetail\s*\} from '\.\.\/services\/reimbursements\.js'/
)
2026-06-09 08:32:00 +00:00
assert . match ( appShellComposable , /const workbenchApprovalRequests = ref\(\[\]\)/ )
assert . match ( appShellComposable , /async function reloadWorkbenchApprovalRequests\(\)/ )
assert . match ( appShellComposable , /async function reloadWorkbenchRequests\(\)/ )
2026-07-17 14:14:08 +08:00
assert . match ( appShellComposable , /fetchApprovalExpenseClaims\(REIMBURSEMENT_LIST_PREVIEW_PARAMS\)/ )
assert . match ( appShellComposable , /extractExpenseClaimItems\(payload\)\.map\(\(item\) => mapExpenseClaimToRequest\(item\)\)/ )
assert . doesNotMatch ( appShellComposable , /fetchAllApprovalExpenseClaims\(\)/ )
2026-06-09 08:32:00 +00:00
assert . match ( appShellComposable , /Promise\.all\(\[[\s\S]*reloadRequests\(\{ silent: true \}\),[\s\S]*reloadWorkbenchApprovalRequests\(\)[\s\S]*\]\)/ )
assert . match ( appShellComposable , /if \(view === 'workbench'\) \{[\s\S]*void reloadWorkbenchRequests\(\)/ )
assert . match ( appShellComposable , /const workbenchRequests = computed\(\(\) =>[\s\S]*mergeWorkbenchRequests\(requests\.value, workbenchApprovalRequests\.value\)/ )
assert . match ( appShellComposable , /buildWorkbenchSummary\(workbenchRequests\.value, currentUser\.value\)/ )
assert . match ( appShellRouteView , /<DocumentsCenterView[\s\S]*:filtered-requests="requests"/ )
2026-06-21 22:49:58 +08:00
assert . doesNotMatch ( appShellRouteView , /<DocumentsCenterView(?:(?!\/>)[\s\S])*workbenchRequests/ )
2026-06-09 08:32:00 +00:00
} )
test ( 'workbench progress refreshes after homepage create or detail updates' , ( ) => {
assert . match ( appShellComposable , /async function handleDraftSaved\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)/ )
2026-06-18 22:12:24 +08:00
assert . match ( appShellComposable , /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)[\s\S]*await refreshSelectedRequestDetail\(claimId\)/ )
assert . doesNotMatch ( appShellComposable , /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadRequests\(\)[\s\S]*await refreshSelectedRequestDetail/ )
2026-06-09 08:32:00 +00:00
} )
test ( 'workbench progress refresh is silent to avoid homepage flashing' , ( ) => {
assert . match ( requestsComposable , /async function reload\(options = \{\}\) \{[\s\S]*const silent = Boolean\(options\?\.silent\)/ )
assert . match ( requestsComposable , /if \(!silent\) \{[\s\S]*loading\.value = true[\s\S]*error\.value = ''[\s\S]*\}/ )
assert . match ( requestsComposable , /catch \(nextError\) \{[\s\S]*if \(!silent\) \{[\s\S]*requests\.value = \[\][\s\S]*\}/ )
assert . match ( requestsComposable , /finally \{[\s\S]*if \(!silent\) \{[\s\S]*loading\.value = false[\s\S]*\}/ )
assert . match ( appShellComposable , /async function reloadWorkbenchRequests\(\) \{[\s\S]*reloadRequests\(\{ silent: true \}\)/ )
assert . match ( appShellComposable , /async function reloadDocumentCenterRequests\(\) \{[\s\S]*return reloadRequests\(\)/ )
} )
2026-06-03 15:46:56 +08:00
test ( 'document detail navigation preserves document center list query' , ( ) => {
assert . match (
appShellComposable ,
2026-06-20 14:42:04 +08:00
/function openRequestDetail\(request, options = \{\}\) \{[\s\S]*const requestId = resolveRequestDetailLookupId\(request\)[\s\S]*name: 'app-document-detail'[\s\S]*params: \{ requestId \},[\s\S]*query: buildDocumentDetailQuery\(options\)/
2026-06-03 15:46:56 +08:00
)
assert . match (
appShellComposable ,
2026-06-03 22:15:45 +08:00
/function closeRequestDetail\(\) \{[\s\S]*router\.push\(\{ name: 'app-documents', query: buildDocumentReturnQuery\(\) \}\)/
2026-06-03 15:46:56 +08:00
)
assert . match (
appShellComposable ,
2026-06-03 22:15:45 +08:00
/async function handleRequestDeleted\(payload = \{\}\) \{[\s\S]*router\.push\(\{ name: 'app-documents', query: buildDocumentReturnQuery\(\) \}\)/
2026-06-03 15:46:56 +08:00
)
} )
2026-06-03 22:15:45 +08:00
test ( 'document detail refreshes claim detail instead of relying on stale list cache' , ( ) => {
2026-07-17 14:14:08 +08:00
assert . match (
appShellComposable ,
/import \{\s*REIMBURSEMENT_LIST_PREVIEW_PARAMS,\s*extractExpenseClaimItems,\s*fetchApprovalExpenseClaims,\s*fetchExpenseClaimDetail\s*\} from '\.\.\/services\/reimbursements\.js'/
)
2026-06-03 22:15:45 +08:00
assert . match ( appShellComposable , /import \{ mapExpenseClaimToRequest, useRequests \} from '\.\/useRequests\.js'/ )
assert . match ( appShellComposable , /const snapshot = normalizeRequestForUi\(selectedRequestSnapshot\.value\)[\s\S]*if \(isSameRequestIdentity\(snapshot, requestId\)\) \{[\s\S]*return snapshot/ )
assert . match ( appShellComposable , /async function refreshSelectedRequestDetail\(requestOrId = selectedRequestSnapshot\.value\) \{[\s\S]*fetchExpenseClaimDetail\(lookupId\)[\s\S]*mapExpenseClaimToRequest\(payload\)[\s\S]*upsertRequestSnapshot\(mappedRequest\)/ )
2026-06-20 14:42:04 +08:00
assert . match ( appShellComposable , /function isDetailLookupOnlyPayload\(payload = \{\}\) \{[\s\S]*payload\?\.detailLookupOnly/ )
assert . match ( appShellComposable , /function openRequestDetail\(request, options = \{\}\) \{[\s\S]*selectedRequestSnapshot\.value = isDetailLookupOnlyRequest \? null : request \|\| null[\s\S]*void refreshSelectedRequestDetail\(isDetailLookupOnlyRequest \? requestId : request\)/ )
2026-06-18 22:12:24 +08:00
assert . match ( appShellComposable , /async function handleRequestUpdated\(payload = \{\}\) \{[\s\S]*await reloadWorkbenchRequests\(\)[\s\S]*await refreshSelectedRequestDetail\(claimId\)/ )
2026-06-03 22:15:45 +08:00
assert . match ( appShellComposable , /route\.name === 'app-document-detail'[\s\S]*void refreshSelectedRequestDetail\(String\(route\.params\.requestId \|\| ''\)\)/ )
} )
2026-05-25 13:35:39 +08:00
test ( 'application entry keeps its own assistant source without creating a separate dialog' , ( ) => {
assert . match ( appShellComposable , /const SMART_ENTRY_SOURCE_APPLICATION = 'application'/ )
assert . match ( appShellComposable , /function openExpenseApplicationCreate\(\) \{[\s\S]*openFinancialAssistantCreate\(SMART_ENTRY_SOURCE_APPLICATION\)/ )
assert . match ( appShellComposable , /function openTravelCreate\(\) \{[\s\S]*openFinancialAssistantCreate\(SMART_ENTRY_SOURCE_REIMBURSEMENT\)/ )
assert . match ( appShellComposable , /openExpenseApplicationCreate,/ )
2026-06-22 11:58:53 +08:00
assert . match ( assistantSurface , /activeSessionType\.value === SESSION_TYPE_APPLICATION[\s\S]*我想先申请一笔差旅费用/ )
2026-05-25 13:35:39 +08:00
} )
2026-06-03 09:25:23 +08:00
test ( 'application edit prefill opens assistant without auto submit' , ( ) => {
assert . match ( appShellRouteView , /:initial-prompt-auto-submit="smartEntryContext\.initialPromptAutoSubmit"/ )
assert . match ( appShellRouteView , /:initial-application-preview="smartEntryContext\.initialApplicationPreview"/ )
2026-06-26 22:42:29 +08:00
assert . match ( appShellRouteView , /:initial-draft-payload="smartEntryContext\.initialDraftPayload"/ )
2026-06-03 09:25:23 +08:00
assert . match ( appShellComposable , /initialPromptAutoSubmit:\s*true/ )
assert . match ( appShellComposable , /initialApplicationPreview:\s*null/ )
2026-06-26 22:42:29 +08:00
assert . match ( appShellComposable , /initialDraftPayload:\s*null/ )
2026-06-03 09:25:23 +08:00
assert . match ( appShellComposable , /initialPromptAutoSubmit:\s*payload\.initialPromptAutoSubmit !== false/ )
assert . match ( appShellComposable , /initialApplicationPreview:\s*payload\.applicationPreview && typeof payload\.applicationPreview === 'object'/ )
2026-06-26 22:42:29 +08:00
assert . match ( appShellComposable , /initialDraftPayload:\s*payload\.draftPayload && typeof payload\.draftPayload === 'object'/ )
2026-06-03 09:25:23 +08:00
assert . match (
assistantScript ,
/initialPromptAutoSubmit:\s*\{[\s\S]*type:\s*Boolean[\s\S]*default:\s*true/
)
assert . match (
assistantScript ,
/initialApplicationPreview:\s*\{[\s\S]*type:\s*Object[\s\S]*default:\s*null/
)
2026-06-26 22:42:29 +08:00
assert . match (
assistantScript ,
/initialDraftPayload:\s*\{[\s\S]*type:\s*Object[\s\S]*default:\s*null/
)
2026-06-03 09:25:23 +08:00
assert . match (
2026-06-22 11:58:53 +08:00
assistantSurface ,
2026-06-26 22:42:29 +08:00
/props\.initialApplicationPreview[\s\S]*normalizeApplicationPreview\(props\.initialApplicationPreview\)[\s\S]*const draftPayload = props\.initialDraftPayload[\s\S]*createMessage\('assistant', buildLocalApplicationPreviewMessage\(applicationPreview\)[\s\S]*draftPayload/
2026-06-03 09:25:23 +08:00
)
assert . match ( assistantSessionStateScript , /&& !props\.initialApplicationPreview/ )
assert . match (
2026-06-22 11:58:53 +08:00
assistantSurface ,
2026-06-03 09:25:23 +08:00
/if \(props\.initialPromptAutoSubmit !== false\) \{[\s\S]*submitComposer\(\)/
)
} )
2026-06-06 17:19:07 +08:00
test ( 'financial assistant toolbar renders isolated assistant sessions without steward entry' , ( ) => {
2026-06-22 11:58:53 +08:00
assert . match ( assistantSurface , /filterAssistantSessionModes\(ASSISTANT_SESSION_MODE_OPTIONS, currentUser\.value\)/ )
assert . match ( assistantSurface , /\.filter\(\(mode\) => mode\.key !== SESSION_TYPE_STEWARD\)/ )
assert . match ( assistantSurface , /mode\.key === SESSION_TYPE_BUDGET/ )
assert . match ( assistantSurface , /visibleModes\.map/ )
assert . match ( assistantSurface , /targetSessionType:\s*mode\.key/ )
assert . match ( assistantSurface , /active:\s*mode\.key === activeSessionType\.value/ )
2026-05-25 13:35:39 +08:00
assert . match ( assistantTemplate , /:class="\{ active: shortcut\.active \}"/ )
assert . match ( assistantTemplate , /:aria-pressed="shortcut\.active \? 'true' : 'false'"/ )
assert . match ( assistantTemplate , /:disabled="shortcut\.active \|\| submitting/ )
} )
2026-05-26 09:15:14 +08:00
test ( 'closing a busy assistant keeps the running instance recoverable' , ( ) => {
assert . match ( appShellRouteView , /:reopen-token="smartEntryRevealToken"/ )
assert . match ( appShellComposable , /const smartEntryRevealToken = ref\(0\)/ )
assert . match ( appShellComposable , /if \(smartEntryOpen\.value\) \{\s*smartEntryRevealToken\.value \+= 1\s*return\s*\}/ )
assert . match ( appShellComposable , /smartEntryRevealToken,/ )
assert . match ( assistantScript , /reopenToken:\s*\{\s*type:\s*Number/ )
2026-06-22 11:58:53 +08:00
assert . match ( assistantSurface , /closeAfterBusy\.value = false[\s\S]*workbenchVisible\.value = true/ )
assert . match ( assistantSurface , /function emitCloseAfterLeave\(\) \{\s*if \(workbenchVisible\.value\)/ )
2026-05-26 09:15:14 +08:00
} )
2026-05-25 13:35:39 +08:00
test ( 'financial assistant welcome copy differentiates application intent from reimbursement entry' , ( ) => {
const user = { name : '李文静' , username : 'wenjing.li' , grade : 'P5' }
const applicationWelcome = buildWelcomeMessage ( 'application' , null , SESSION _TYPE _APPLICATION , user )
const reimbursementWelcome = buildWelcomeMessage ( 'topbar' , null , SESSION _TYPE _EXPENSE , user )
const knowledgeWelcome = buildWelcomeMessage ( 'topbar' , null , SESSION _TYPE _KNOWLEDGE , user )
const applicationInsight = buildWelcomeInsight ( 'application' , null , SESSION _TYPE _APPLICATION , user )
assert . match ( applicationWelcome , /申请助手/ )
assert . match ( applicationWelcome , /费用申请、报销申请还是其他财务事项/ )
assert . match ( reimbursementWelcome , /报销助手/ )
assert . match ( reimbursementWelcome , /报销发起、票据识别、草稿归集、报销信息核对/ )
assert . match ( knowledgeWelcome , /财务知识助手/ )
assert . notEqual ( applicationWelcome , reimbursementWelcome )
assert . equal ( applicationInsight . metricValue , '申请助手' )
assert . equal ( applicationInsight . title , '申请助手' )
} )
2026-05-30 15:46:51 +08:00
test ( 'assistant message meta hides internal routing and permission chips' , ( ) => {
const meta = buildMessageMeta (
{
selected _agent : 'user_agent' ,
permission _level : 'draft_write' ,
run _id : 'run-001' ,
trace _summary : {
tool _count : 3 ,
degraded : true
} ,
requires _confirmation : true
} ,
[ 'invoice.pdf' ]
)
assert . deepEqual ( meta , [ '已降级' , '待确认' , '附件: 1' ] )
assert . deepEqual (
filterVisibleMessageMeta ( [ 'Agent: user_agent' , '权限: draft_write' , 'Run: run-001' , '工具: 3' , '等待确认' ] ) ,
[ '等待确认' ]
)
assert . deepEqual (
createMessage ( 'assistant' , '测试' , [ ] , { meta : [ 'Agent: user_agent' , '权限: draft_write' , '处理中' ] } ) . meta ,
[ '处理中' ]
)
assert . doesNotMatch ( messageItemTemplate , /message-meta-row|message-meta-chip/ )
assert . doesNotMatch ( chatViewTemplate , /agent-meta-row|agent-meta-chip/ )
} )
2026-06-06 17:19:07 +08:00
test ( 'assistant message action toolbar collects lightweight feedback' , ( ) => {
2026-05-30 15:46:51 +08:00
assert . doesNotMatch ( appShellRouteView , /<OperationFeedbackDialog/ )
assert . doesNotMatch ( appShellRouteView , /@operation-completed="handleOperationCompleted"/ )
assert . doesNotMatch ( appShellComposable , /useOperationFeedback/ )
2026-06-06 17:19:07 +08:00
assert . doesNotMatch ( messageItemTemplate , /<OperationFeedbackInlineCard/ )
assert . match ( messageItemTemplate , /class="message-action-toolbar"/ )
assert . match ( messageItemTemplate , /ui\.shouldShowAssistantMessageActions\(message\)/ )
assert . match ( messageItemTemplate , /ui\.copyAssistantMessage\(message\)/ )
assert . match ( messageItemTemplate , /ui\.speakAssistantMessage\(message\)/ )
assert . match ( messageItemTemplate , /rating:\s*5,\s*reason:\s*'thumbs_up'/ )
assert . match ( messageItemTemplate , /rating:\s*1,\s*reason:\s*'thumbs_down'/ )
assert . match ( messageItemTemplate , /mdi mdi-content-copy/ )
assert . match ( messageItemTemplate , /mdi mdi-volume-high/ )
assert . match ( messageItemTemplate , /mdi mdi-thumb-up-outline/ )
assert . match ( messageItemTemplate , /mdi mdi-thumb-down-outline/ )
2026-05-30 15:46:51 +08:00
assert . match ( assistantScript , /emits:\s*\['close', 'draft-saved', 'request-updated'\]/ )
assert . match ( appShellRouteView , /@request-updated="handleRequestUpdated"/ )
2026-06-22 11:58:53 +08:00
assert . match ( assistantSurface , /function buildMessageOperationFeedbackContext/ )
assert . match ( assistantSurface , /source:\s*'assistant_message_action'/ )
assert . match ( assistantSurface , /operation_type:\s*message\?\.stewardPlan \? 'steward_message' : 'assistant_message'/ )
assert . match ( assistantSurface , /function shouldShowAssistantMessageActions/ )
assert . match ( assistantSurface , /function copyAssistantMessage/ )
assert . match ( assistantSurface , /function speakAssistantMessage/ )
assert . match ( assistantSurface , /function isMessageFeedbackSelected/ )
assert . match ( assistantSurface , /function submitOperationFeedbackForMessage/ )
assert . match ( assistantSurface , /createOperationFeedback/ )
assert . match ( assistantSurface , /normalizeOperationFeedbackContext/ )
assert . match ( assistantSurface , /submitted:\s*true/ )
assert . match ( assistantSurface , /dismissed:\s*false/ )
assert . doesNotMatch ( assistantSurface , /emit\('operation-completed'/ )
2026-05-30 15:46:51 +08:00
assert . match ( assistantSubmitComposerScript , /emitOperationCompleted\?\.\(payload/ )
assert . match ( assistantSubmitComposerScript , /operationFeedback:\s*buildOperationFeedbackState/ )
2026-06-22 11:58:53 +08:00
assert . match ( assistantSubmitResponseModelScript , /rating:\s*0/ )
2026-05-30 15:46:51 +08:00
const context = normalizeOperationFeedbackContext (
{
run _id : 'run-001' ,
conversation _id : 'conv-001' ,
selected _agent : 'user_agent' ,
session _type : 'application' ,
operation _status : 'succeeded' ,
route _reason : 'model_route' ,
result : { answer : '处理完成' }
} ,
{ username : 'wenjing.li' }
)
const payload = buildOperationFeedbackPayload ( context , { rating : 2 , reason : '识别错了' } )
assert . equal ( context . runId , 'run-001' )
assert . equal ( context . userId , 'wenjing.li' )
assert . equal ( payload . run _id , 'run-001' )
assert . equal ( payload . conversation _id , 'conv-001' )
assert . equal ( payload . agent , 'user_agent' )
assert . equal ( payload . rating , 2 )
assert . equal ( payload . reason , '识别错了' )
assert . equal ( payload . context _json . low _rating , true )
} )