接入前端P0页面真实接口

This commit is contained in:
andy committed 2026-07-08 16:20:06 +08:00
1 parent 5c5b8e33b0
commit 66a613d6cd
45 files changed
+8719

No files matched your search

@@ -0,0 +1,183 @@
import { flushPromises, mount, type VueWrapper } 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 ReservationTaskDetailPanel from '@/components/reservation/ReservationTaskDetailPanel.vue'
import type {
ReservationTaskAuditLogResult,
ReservationTaskDetailResult,
ReservationTaskPayloadMutationResult,
} from '@/types/reservation'
vi.mock('@/services/reservationService', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/services/reservationService')>()
return {
...actual,
fetchReservationTaskDetail: vi.fn(),
fetchReservationTaskAudits: vi.fn(),
saveReservationTaskDraft: vi.fn(),
confirmReservationTask: vi.fn(),
}
})
const service = await import('@/services/reservationService')
function createTaskDetail(): ReservationTaskDetailResult {
return {
task_id: '10001',
order_id: '20001',
source_message_id: '30001',
source_subject: 'Booking Request',
source_sender_summary: 'guest@example.test',
source_received_at: '2026-07-08T03:00:00Z',
external_conversation_id: 'thread-30001',
conversation_message_count: 2,
system_task_type: 'NEW_BOOKING',
task_card_type: 'NEW_BOOKING',
task_status: 'PENDING_CONFIRM',
field_contract_version: '20260708-3.0',
draft_payload: null,
confirmed_payload: null,
availability: {
blocked: false,
read_only: false,
editable: true,
confirmable: true,
executable: false,
blocked_by_task_id: null,
blocked_reason: null,
},
fields: [],
opera_operations: [],
}
}
function createMutationResult(taskStatus = 'PENDING_CONFIRM'): ReservationTaskPayloadMutationResult {
return {
task_id: '10001',
order_id: '20001',
task_status: taskStatus,
draft_payload: {},
confirmed_payload: taskStatus === 'READY' ? {} : null,
opera_operations: [],
}
}
function createAudit(auditId: string, action: string): ReservationTaskAuditLogResult {
return {
audit_id: auditId,
order_id: '20001',
task_id: '10001',
operation_id: null,
actor_type: 'USER',
actor_id: 'andy',
action,
reason: null,
before_snapshot: null,
after_snapshot: null,
occurred_at: '2026-07-08T03:00:00Z',
}
}
async function mountPanel() {
const i18n = createI18n({
legacy: false,
locale: 'zh-CN',
messages: {
'zh-CN': zhCN,
},
})
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/reservation/tasks/:taskId', component: { template: '<div />' } },
{ path: '/reservation/orders/:orderId', component: { template: '<div />' } },
{ path: '/reservation/source-messages/:sourceMessageId/conversation', component: { template: '<div />' } },
],
})
await router.push('/reservation/tasks/10001')
await router.isReady()
const wrapper = mount(ReservationTaskDetailPanel, {
props: {
taskId: '10001',
},
global: {
plugins: [i18n, router],
stubs: {
RouterLink: true,
},
},
})
await flushPromises()
return wrapper
}
function findButton(wrapper: VueWrapper, label: string) {
const button = wrapper.findAll('button').find((item) => item.text().includes(label))
if (!button) {
throw new Error(`Button ${label} was not found.`)
}
return button
}
describe('ReservationTaskDetailPanel', () => {
beforeEach(() => {
vi.mocked(service.fetchReservationTaskDetail).mockReset()
vi.mocked(service.fetchReservationTaskAudits).mockReset()
vi.mocked(service.saveReservationTaskDraft).mockReset()
vi.mocked(service.confirmReservationTask).mockReset()
vi.mocked(service.fetchReservationTaskDetail).mockResolvedValue(createTaskDetail())
vi.mocked(service.fetchReservationTaskAudits).mockResolvedValue({
task_id: '10001',
items: [],
})
})
it('shows draft save errors instead of leaving an unhandled rejection', async () => {
vi.mocked(service.saveReservationTaskDraft).mockRejectedValue(new Error('Draft save failed'))
const wrapper = await mountPanel()
await findButton(wrapper, '保存草稿').trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('Draft save failed')
expect(findButton(wrapper, '保存草稿').attributes('disabled')).toBeUndefined()
})
it('shows confirm errors instead of leaving an unhandled rejection', async () => {
vi.mocked(service.confirmReservationTask).mockRejectedValue(new Error('Confirm failed'))
const wrapper = await mountPanel()
await findButton(wrapper, '确认').trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('Confirm failed')
expect(findButton(wrapper, '确认').attributes('disabled')).toBeUndefined()
})
it('refreshes audit records after saving a draft', async () => {
vi.mocked(service.fetchReservationTaskAudits)
.mockResolvedValueOnce({
task_id: '10001',
items: [createAudit('audit-1', 'TASK_LOADED')],
})
.mockResolvedValueOnce({
task_id: '10001',
items: [createAudit('audit-2', 'DRAFT_SAVED')],
})
vi.mocked(service.saveReservationTaskDraft).mockResolvedValue(createMutationResult())
const wrapper = await mountPanel()
expect(wrapper.text()).toContain('TASK_LOADED')
await findButton(wrapper, '保存草稿').trigger('click')
await flushPromises()
expect(service.fetchReservationTaskAudits).toHaveBeenCalledTimes(2)
expect(wrapper.text()).toContain('DRAFT_SAVED')
expect(wrapper.text()).not.toContain('TASK_LOADED')
})
})