import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' const routeState = vi.hoisted(() => ({ query: {} as Record, })) const routerPush = vi.hoisted(() => vi.fn()) const pollOrderStatus = vi.hoisted(() => vi.fn()) const verifyOrderPublic = vi.hoisted(() => vi.fn()) const verifyOrder = vi.hoisted(() => vi.fn()) const resolveOrderPublicByResumeToken = vi.hoisted(() => vi.fn()) vi.mock('vue-router', async () => { const actual = await vi.importActual('vue-router') return { ...actual, useRoute: () => routeState, useRouter: () => ({ push: routerPush }), } }) vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: () => ({ t: (key: string) => key, }), } }) vi.mock('@/stores/payment', () => ({ usePaymentStore: () => ({ pollOrderStatus, }), })) vi.mock('@/api/payment', () => ({ paymentAPI: { verifyOrderPublic, verifyOrder, resolveOrderPublicByResumeToken, }, })) import PaymentResultView from '../PaymentResultView.vue' import { PAYMENT_RECOVERY_STORAGE_KEY } from '@/components/payment/paymentFlow' const orderFactory = (status: string) => ({ id: 42, user_id: 9, amount: 88, pay_amount: 88, fee_rate: 0, payment_type: 'alipay', out_trade_no: 'sub2_20260420abcd1234', status, order_type: 'balance', created_at: '2026-04-20T12:00:00Z', expires_at: '2026-04-20T12:30:00Z', refund_amount: 0, }) describe('PaymentResultView', () => { beforeEach(() => { routeState.query = {} routerPush.mockReset() pollOrderStatus.mockReset() verifyOrderPublic.mockReset() verifyOrder.mockReset() resolveOrderPublicByResumeToken.mockReset() window.localStorage.clear() }) it('restores order id from a matching resume token and does not trust query success flags', async () => { routeState.query = { resume_token: 'resume-42', status: 'success', } window.localStorage.setItem(PAYMENT_RECOVERY_STORAGE_KEY, JSON.stringify({ orderId: 42, amount: 88, qrCode: '', expiresAt: '2099-01-01T00:10:00.000Z', paymentType: 'alipay', payUrl: 'https://pay.example.com/session/42', clientSecret: '', payAmount: 88, orderType: 'balance', paymentMode: 'redirect', resumeToken: 'resume-42', createdAt: Date.UTC(2099, 0, 1, 0, 0, 0), })) pollOrderStatus.mockResolvedValue(orderFactory('PENDING')) const wrapper = mount(PaymentResultView, { global: { stubs: { OrderStatusBadge: true, }, }, }) await flushPromises() expect(pollOrderStatus).toHaveBeenCalledWith(42) expect(verifyOrderPublic).not.toHaveBeenCalled() expect(wrapper.text()).toContain('payment.result.failed') expect(wrapper.text()).not.toContain('payment.result.success') }) it('keeps legacy out_trade_no verification as a fallback when no order context is available', async () => { routeState.query = { out_trade_no: 'legacy-123', trade_status: 'TRADE_SUCCESS', } verifyOrderPublic.mockResolvedValue({ data: orderFactory('PAID'), }) const wrapper = mount(PaymentResultView, { global: { stubs: { OrderStatusBadge: true, }, }, }) await flushPromises() expect(verifyOrderPublic).toHaveBeenCalledWith('legacy-123') expect(wrapper.text()).toContain('payment.result.success') }) it('resolves order by resume token when local recovery snapshot is missing', async () => { routeState.query = { resume_token: 'resume-77', } resolveOrderPublicByResumeToken.mockResolvedValue({ data: orderFactory('PAID'), }) const wrapper = mount(PaymentResultView, { global: { stubs: { OrderStatusBadge: true, }, }, }) await flushPromises() expect(resolveOrderPublicByResumeToken).toHaveBeenCalledWith('resume-77') expect(wrapper.text()).toContain('payment.result.success') expect(verifyOrderPublic).not.toHaveBeenCalled() }) })