2026-08-04 21:01:39 +08:00
|
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import { ApiResponseError, http } from '@/shared/api/http'
|
|
|
|
|
import {
|
|
|
|
|
adjustPublishedInboundFee,
|
|
|
|
|
createFeeAdjustmentIdempotencyKey,
|
|
|
|
|
getLatestInboundFee,
|
|
|
|
|
} from './api'
|
|
|
|
|
|
|
|
|
|
vi.mock('@/shared/api/http', async () => {
|
|
|
|
|
const actual = await vi.importActual<typeof import('@/shared/api/http')>('@/shared/api/http')
|
|
|
|
|
return { ...actual, http: { get: vi.fn(), put: vi.fn() } }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const snapshot = {
|
|
|
|
|
feeId: '9007199254740993',
|
|
|
|
|
feeNo: 'FBAF-88-2',
|
|
|
|
|
feeVersion: 2,
|
|
|
|
|
feeStatus: 'SENT',
|
|
|
|
|
inboundId: '88',
|
|
|
|
|
totalAmount: 1280,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('inbound fee API', () => {
|
|
|
|
|
beforeEach(() => vi.clearAllMocks())
|
|
|
|
|
|
|
|
|
|
it('loads the latest immutable fee snapshot with string IDs', async () => {
|
|
|
|
|
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: snapshot })
|
|
|
|
|
|
|
|
|
|
await expect(getLatestInboundFee('10001', '88')).resolves.toEqual(snapshot)
|
|
|
|
|
expect(http.get).toHaveBeenCalledWith('/platform/v1/inbounds/88/fees/latest', {
|
|
|
|
|
params: { tenantId: '10001' },
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('adjusts a published snapshot with the required idempotency key', async () => {
|
|
|
|
|
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: { ...snapshot, feeVersion: 3 } })
|
2026-08-14 14:27:03 +08:00
|
|
|
const payload = { expectedFeeVersion: 2, chargeableQuantity: 140, unitPrice: 8.2, additionalAmount: 80, reason: '补充已确认的附加费' }
|
2026-08-04 21:01:39 +08:00
|
|
|
|
|
|
|
|
await expect(adjustPublishedInboundFee('10001', '88', payload, 'fee-adjust-1'))
|
|
|
|
|
.resolves.toMatchObject({ feeVersion: 3 })
|
|
|
|
|
expect(http.put).toHaveBeenCalledWith(
|
|
|
|
|
'/platform/v1/inbounds/88/fees/published',
|
|
|
|
|
payload,
|
|
|
|
|
{ params: { tenantId: '10001' }, headers: { 'Idempotency-Key': 'fee-adjust-1' } },
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('preserves backend conflict metadata for stale-version recovery', async () => {
|
|
|
|
|
vi.mocked(http.put).mockResolvedValue({
|
|
|
|
|
code: 'A0409', message: '费用已被其他操作员修改,请刷新后重试', data: null,
|
|
|
|
|
requestId: 'request-fee-409', traceId: 'trace-fee-409',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const error = await adjustPublishedInboundFee(
|
|
|
|
|
'10001',
|
|
|
|
|
'88',
|
2026-08-14 14:27:03 +08:00
|
|
|
{ expectedFeeVersion: 2, chargeableQuantity: 140, unitPrice: 8.2, additionalAmount: 80, reason: '价格修正' },
|
2026-08-04 21:01:39 +08:00
|
|
|
'fee-adjust-2',
|
|
|
|
|
).catch((reason: unknown) => reason)
|
|
|
|
|
|
|
|
|
|
expect(error).toBeInstanceOf(ApiResponseError)
|
|
|
|
|
expect(error).toMatchObject({ code: 'A0409', requestId: 'request-fee-409', traceId: 'trace-fee-409' })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('creates bounded unique keys for adjustment retries', () => {
|
|
|
|
|
const first = createFeeAdjustmentIdempotencyKey()
|
|
|
|
|
const second = createFeeAdjustmentIdempotencyKey()
|
|
|
|
|
expect(first).toMatch(/^fee-adjust-/)
|
|
|
|
|
expect(first.length).toBeLessThanOrEqual(128)
|
|
|
|
|
expect(second).not.toBe(first)
|
|
|
|
|
})
|
|
|
|
|
})
|