import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, expect, it, vi } from 'vitest' import CargoVerificationForm from './CargoVerificationForm.vue' import { getCargoVerification, saveCargoVerification, type CargoVerificationContext } from './cargoVerificationApi' import { ApiResponseError } from '@/shared/api/http' const permissions = vi.hoisted(() => ({ canWrite: true })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: (permission: string) => !permission.endsWith('fba-appointment') || permissions.canWrite }) })) vi.mock('element-plus', () => ({ ElMessage: { warning: vi.fn(), success: vi.fn() }, ElMessageBox: { confirm: vi.fn() } })) vi.mock('./cargoVerificationApi', () => ({ getCargoVerification: vi.fn(), saveCargoVerification: vi.fn() })) const source = { sourceKey: 'ORDER:9007199254740993', customerId: 'customer-1', destinationKey: 'FBA:ONT8' } const context: CargoVerificationContext = { source: { ...source, orderId: '9007199254740993', inboundCode: 'IN-TEST-001', bookingNo: null, containerNo: 'TEST-CN-001', originWarehouseId: 'warehouse-1', originWarehouseName: '测试仓', destinationWarehouseCode: 'ONT8', packages: null, sourceVersion: null, canFollowup: true, blockReasons: [] }, verification: null, allocatedPackages: 0, unknownAllocationCount: 0, effectivePackages: null, availablePackages: null, sourceChanged: false, allowedActions: ['VERIFY_CARGO'], blockReasons: [], } function render() { return mount(CargoVerificationForm, { props: { source }, global: { stubs: { ElButton: { props: ['nativeType'], template: '' }, LoadErrorState: { props: ['message'], template: '
{{ message }}
' }, ApiErrorMeta: true } } }) } beforeEach(() => { vi.clearAllMocks(); permissions.canWrite = true; vi.mocked(getCargoVerification).mockResolvedValue(context) vi.mocked(saveCargoVerification).mockResolvedValue({ version: 1, sourceVersion: null, destinationKey: source.destinationKey, sourcePackages: null, packageCount: 20, shipmentReferences: ['FBA-TEST'], remark: null, operatorName: '测试调度', verifiedAt: '2026-10-15T03:00:00Z' }) }) it('saves independent cargo verification before any appointment exists, preserving unknown source values', async () => { const wrapper = render(); await flushPromises() expect(wrapper.text()).toContain('待核实'); expect(wrapper.text()).not.toContain('9007199254740993') await wrapper.get('input[type=number]').setValue('20'); await wrapper.get('textarea[aria-label="本票货件号"]').setValue('FBA-TEST\nFBA-TEST') await wrapper.get('form').trigger('submit'); await flushPromises() expect(saveCargoVerification).toHaveBeenCalledWith(source, expect.objectContaining({ expectedSourceVersion: null, expectedVersion: 0, packageCount: 20, shipmentReferences: ['FBA-TEST'] })) expect(vi.mocked(saveCargoVerification).mock.calls[0]![1]).not.toHaveProperty('appointmentStatus') expect(wrapper.emitted('saved')).toHaveLength(1); wrapper.unmount() }) it('uses the same key and payload after an unknown save outcome', async () => { vi.mocked(saveCargoVerification).mockRejectedValueOnce(new Error('network lost')) const wrapper = render(); await flushPromises() await wrapper.get('input[type=number]').setValue('20'); await wrapper.get('form').trigger('submit'); await flushPromises() const first = vi.mocked(saveCargoVerification).mock.calls[0]![1] expect(wrapper.get('fieldset').attributes('disabled')).toBeDefined() await wrapper.get('form').trigger('submit'); await flushPromises() expect(saveCargoVerification).toHaveBeenNthCalledWith(2, source, first); wrapper.unmount() }) it('refreshes source conflicts without deleting the entered quantity or shipment numbers', async () => { vi.mocked(saveCargoVerification).mockRejectedValueOnce(new ApiResponseError('原来源已变化', 'A0409', 409)) const wrapper = render(); await flushPromises() await wrapper.get('input[type=number]').setValue('20'); await wrapper.get('textarea[aria-label="本票货件号"]').setValue('FBA-KEEP') await wrapper.get('form').trigger('submit'); await flushPromises() expect(getCargoVerification).toHaveBeenCalledTimes(2) expect((wrapper.get('input[type=number]').element as HTMLInputElement).value).toBe('20') expect((wrapper.get('textarea[aria-label="本票货件号"]').element as HTMLTextAreaElement).value).toBe('FBA-KEEP'); wrapper.unmount() }) it('blocks invalid allocation bounds and denies writes for list-only users', async () => { vi.mocked(getCargoVerification).mockResolvedValue({ ...context, source: { ...context.source, packages: 50 }, allocatedPackages: 30, availablePackages: 20 }) const wrapper = render(); await flushPromises() await wrapper.get('input[type=number]').setValue('20'); await wrapper.get('form').trigger('submit') expect(saveCargoVerification).not.toHaveBeenCalled(); wrapper.unmount() permissions.canWrite = false; const denied = render(); await flushPromises() await denied.get('form').trigger('submit'); expect(saveCargoVerification).not.toHaveBeenCalled(); denied.unmount() })