import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' import { createMemoryHistory, createRouter } from 'vue-router' import zhCN from '@/i18n/locales/zh-CN' import type { BookingProcessingRunView } from '@/types/bookingContracts' import ReservationBookingProcessingRunView from '@/views/reservation/ReservationBookingProcessingRunView.vue' vi.mock('@/services/reservationService', async (importOriginal) => { const actual = await importOriginal() return { ...actual, fetchBookingProcessingRun: vi.fn(), confirmBookingProcessingRun: vi.fn(), retryBookingProcessingRun: vi.fn(), } }) const service = await import('@/services/reservationService') describe('ReservationBookingProcessingRunView', () => { beforeEach(() => { vi.mocked(service.fetchBookingProcessingRun).mockReset() vi.mocked(service.confirmBookingProcessingRun).mockReset() vi.mocked(service.retryBookingProcessingRun).mockReset() }) it('shows the safe confirmation projection and freezes corrected values without a PMS action', async () => { const initial = awaitingRun() const confirmed = { ...initial, status: 'CONFIRMED' as const, confirmation_projection: { ...initial.confirmation_projection!, projection_status: 'AWAITING_CONFIRMATION' as const, processing_status: 'CONFIRMED' as const, }, } vi.mocked(service.fetchBookingProcessingRun).mockResolvedValue(initial) vi.mocked(service.confirmBookingProcessingRun).mockResolvedValue({ confirmed: true, processing_run: confirmed, }) const wrapper = await mountView() await flushPromises() expect(wrapper.text()).toContain('等待确认') expect(wrapper.text()).toContain('RATE_CODE_REQUIRED') await wrapper.find('select').setValue('FIT') await wrapper.find('input[type="text"]').setValue('QBD-850') await wrapper.find('form').trigger('submit') await flushPromises() expect(service.confirmBookingProcessingRun).toHaveBeenCalledWith('booking-001', { items: [ { confirmation_item_id: 'confirmation-fact-1', field_overrides: { booking_type: 'FIT', rate_code: 'QBD-850', }, }, ], }) expect(wrapper.text()).toContain('参数已冻结') expect(wrapper.text()).toContain('已确认') }) it('retries only a failed processing run and resumes safe polling', async () => { const failed = { ...awaitingRun(), status: 'FAILED' as const, result_disposition: 'RISK_NOTIFICATION' as const, retry_count: 0, error_code: 'BOOKING_ORCHESTRATION_FAILED', error_summary: 'Booking processing failed before confirmation.', confirmation_projection: null, } const retried = { ...failed, status: 'CONTEXT_READY' as const, retry_count: 1, error_code: null, error_summary: null, } vi.mocked(service.fetchBookingProcessingRun).mockResolvedValue(failed) vi.mocked(service.retryBookingProcessingRun).mockResolvedValue(retried) const wrapper = await mountView() await flushPromises() const retryButton = wrapper.findAll('button').find((button) => button.text().includes('重新处理')) expect(retryButton).toBeDefined() await retryButton!.trigger('click') await flushPromises() expect(service.retryBookingProcessingRun).toHaveBeenCalledWith('booking-001') expect(wrapper.text()).toContain('上下文已组装') expect(wrapper.text()).toContain('已重新进入处理队列') }) }) async function mountView() { const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/reservation/booking-processing-runs/:runId', component: ReservationBookingProcessingRunView, }, { path: '/reservation/email-intake', component: { template: '
Intake
' } }, ], }) await router.push('/reservation/booking-processing-runs/booking-001') await router.isReady() return mount(ReservationBookingProcessingRunView, { global: { plugins: [ router, createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': zhCN }, }), ], }, }) } function awaitingRun(): BookingProcessingRunView { return { processing_run_id: 'booking-001', source_message_id: 'manual-eml-001', revision_number: 1, status: 'AWAITING_CONFIRMATION', result_disposition: 'IN_SCOPE_TASKS', duplicate: false, retry_count: 0, error_code: null, error_summary: null, started_at: '2026-08-08T00:00:00Z', completed_at: null, confirmation_projection: { contract_meta: { contract_version: 'booking-contracts-v1', catalog_version: 'booking-catalog-v20260808', parser_version: 'booking-email-parser-v0.1', agent_profile_version: 'agent-not-invoked', processing_run_id: 'booking-001', source_message_id: 'manual-eml-001', source_revision: 0, }, evidence_refs: [], projection_status: 'REVIEW_REQUIRED', confirmation_items: [ { confirmation_item_id: 'confirmation-fact-1', target_decision_id: 'fact-1', disposition: 'IN_SCOPE_TASKS', display_fields: [ { field_code: 'booking_type', value: null, editable: true, allowed_values: ['FIT', 'GROUP'], evidence_refs: [], issues: [], }, ], blocked_fields: [ { field_code: 'rate_code', value: null, editable: true, allowed_values: [], evidence_refs: [], issues: [ { code: 'RATE_CODE_REQUIRED', severity: 'BLOCKER', scope: 'FIELD', message_key: 'booking.validator.rate_code_required', evidence_refs: [], retryable: false, }, ], }, ], linked_actions: [], safe_evidence_refs: [], }, ], issues: [], processing_status: 'AWAITING_CONFIRMATION', }, } }