fix(admin): resolve preload detail lifecycle
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ApiResponseError, http } from '@/shared/api/http'
|
||||
import { getPreloadPlan } from '../api/platformLoadingLists'
|
||||
import PreloadPlanDetailView from './PreloadPlanDetailView.vue'
|
||||
|
||||
const routerReplace = vi.hoisted(() => vi.fn())
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({ params: { planId: '1900000000000000001' }, query: { warehouseId: '1900000000000000002' } }),
|
||||
useRouter: () => ({ replace: routerReplace, push: vi.fn(), back: vi.fn() }),
|
||||
}))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
||||
vi.mock('../api/platformLoadingLists', () => ({
|
||||
confirmPreloadPlan: vi.fn(),
|
||||
getPreloadPlan: vi.fn(),
|
||||
updateLoadingItemWarehouseRemark: vi.fn(),
|
||||
updatePreloadTransport: vi.fn(),
|
||||
}))
|
||||
|
||||
async function mountView() {
|
||||
const wrapper = shallowMount(PreloadPlanDetailView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AdminPage: { template: '<main><slot /></main>' },
|
||||
LoadingListDetailDrawer: { name: 'LoadingListDetailDrawer', props: ['error', 'loading'], template: '<section />' },
|
||||
OperationErrorToast: true,
|
||||
AppDialog: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
await flushPromises()
|
||||
return wrapper
|
||||
}
|
||||
|
||||
function drawerError(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
return wrapper.findComponent({ name: 'LoadingListDetailDrawer' }).props('error')
|
||||
}
|
||||
|
||||
function lifecycle(detailResource: 'PRELOAD' | 'LOADING_LIST', planStatus: string) {
|
||||
return { code: '200', message: 'success', data: { planId: '1900000000000000001', detailResource, planStatus } }
|
||||
}
|
||||
|
||||
describe('PreloadPlanDetailView lifecycle handoff', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.clearAllMocks()
|
||||
routerReplace.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('replaces the current tab after authoritative lifecycle resolution', async () => {
|
||||
vi.spyOn(http, 'get').mockResolvedValue(lifecycle('LOADING_LIST', 'COMPLETED'))
|
||||
const message = vi.spyOn(ElMessage, 'success').mockImplementation(() => undefined as never)
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/platform/v1/first-leg/loading-plans/1900000000000000001/lifecycle',
|
||||
{ params: { warehouseId: '1900000000000000002' } },
|
||||
)
|
||||
expect(getPreloadPlan).not.toHaveBeenCalled()
|
||||
expect(routerReplace).toHaveBeenCalledTimes(1)
|
||||
expect(routerReplace).toHaveBeenCalledWith({
|
||||
name: 'loading-list-detail',
|
||||
params: { planId: '1900000000000000001' },
|
||||
query: { warehouseId: '1900000000000000002' },
|
||||
})
|
||||
expect(message).toHaveBeenCalledWith('装柜已完成,已切换至装柜清单详情')
|
||||
expect(drawerError(wrapper)).toBeNull()
|
||||
expect(wrapper.findComponent({ name: 'LoadingListDetailDrawer' }).props('loading')).toBe(false)
|
||||
})
|
||||
|
||||
it('loads preload detail only while the lifecycle remains PRELOAD', async () => {
|
||||
vi.spyOn(http, 'get').mockResolvedValue(lifecycle('PRELOAD', 'LOADING'))
|
||||
vi.mocked(getPreloadPlan).mockResolvedValue({
|
||||
summary: {
|
||||
status: 'LOADING', containerNo: null,
|
||||
readiness: { preloadPushed: true, pdaTaskStatus: null },
|
||||
},
|
||||
items: [], histories: [],
|
||||
} as never)
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(getPreloadPlan).toHaveBeenCalledWith('1900000000000000001', '1900000000000000002')
|
||||
expect(routerReplace).not.toHaveBeenCalled()
|
||||
expect(drawerError(wrapper)).toBeNull()
|
||||
})
|
||||
|
||||
it('shows A0404 only when lifecycle resolution confirms the plan is absent', async () => {
|
||||
vi.spyOn(http, 'get').mockRejectedValue(new ApiResponseError('装柜计划不存在', 'A0404', 404, undefined, 'request-lifecycle', 'trace-lifecycle'))
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(getPreloadPlan).not.toHaveBeenCalled()
|
||||
expect(routerReplace).not.toHaveBeenCalled()
|
||||
expect(drawerError(wrapper)).toMatchObject({ code: 'A0404', requestId: 'request-lifecycle', traceId: 'trace-lifecycle', message: '装柜计划不存在' })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['A0403', 403, 'request-forbidden', 'trace-forbidden'],
|
||||
['S0503', 503, 'request-unavailable', 'trace-unavailable'],
|
||||
])('does not swallow a lifecycle %s error', async (code, status, requestId, traceId) => {
|
||||
vi.spyOn(http, 'get').mockRejectedValue(new ApiResponseError('装柜计划解析失败', code, status, undefined, requestId, traceId))
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(getPreloadPlan).not.toHaveBeenCalled()
|
||||
expect(routerReplace).not.toHaveBeenCalled()
|
||||
expect(drawerError(wrapper)).toMatchObject({ code, requestId, traceId })
|
||||
})
|
||||
|
||||
it('surfaces a network failure without guessing from another detail endpoint', async () => {
|
||||
vi.spyOn(http, 'get').mockRejectedValue(new Error('Network Error'))
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(getPreloadPlan).not.toHaveBeenCalled()
|
||||
expect(routerReplace).not.toHaveBeenCalled()
|
||||
expect(drawerError(wrapper)).toMatchObject({ message: '预装清单详情加载失败,请稍后重试' })
|
||||
})
|
||||
|
||||
it('does not redirect for an internally inconsistent lifecycle payload', async () => {
|
||||
vi.spyOn(http, 'get').mockResolvedValue(lifecycle('LOADING_LIST', 'LOADING'))
|
||||
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(routerReplace).not.toHaveBeenCalled()
|
||||
expect(drawerError(wrapper)).toMatchObject({ code: 'MODE_STATUS_MISMATCH' })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user