This commit is contained in:
2026-09-29 17:56:00 +08:00
parent 4ead51a5fe
commit 2bf4353b1b
46 changed files with 481 additions and 638 deletions
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ApiResponseError } from '@/shared/api/http'
import { overseasHttp } from '@/shared/api/overseasHttp'
import { ApiResponseError, http } from '@/shared/api/http'
import {
changeExchangeRateStatus, changeQuoteStatus, changeVehicleStatus, createExchangeRate, createQuote, createVehicle,
exportExchangeRates, exportQuotes, exportVehicles, getExchangeRate, getExchangeRateSummary, getQuote, getVehicle,
@@ -8,7 +8,7 @@ import {
type QuoteCreate, type QuoteUpdate,
} from './overseasMasterData'
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn() } }))
vi.mock('@/shared/api/http', async (importOriginal) => ({ ...(await importOriginal<typeof import('@/shared/api/http')>()), http: { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn() } }))
const ok = (data: unknown) => ({ code: '200', message: 'success', data })
const longId = '9223372036854775807'
const quoteSummary = {
@@ -29,7 +29,7 @@ beforeEach(() => vi.clearAllMocks())
describe('overseas master data API', () => {
it('normalizes quote page/detail decimals and preserves long string IDs', async () => {
vi.mocked(overseasHttp.get)
vi.mocked(http.get)
.mockResolvedValueOnce(ok({ records: [quoteSummary], page: '1', size: '20', total: '1', pages: '1' }))
.mockResolvedValueOnce(ok(quoteDetail))
.mockResolvedValueOnce(ok([{ warehouseId: quoteSummary.warehouseId, warehouseCode: 'US-LAX', warehouseName: '洛杉矶合作仓', countryCode: 'US' }]))
@@ -44,9 +44,9 @@ describe('overseas master data API', () => {
expect(detail.transitPrices[0]).toMatchObject({ estimatedMiles: 12.5, price: 18.75, minimumCharge: null })
await listQuoteWarehouseOptions()
expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/master-data/overseas-quotes', { params: { page: 1, size: 20, keyword: 'V1' } })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, `/platform/v1/last-leg/master-data/overseas-quotes/${longId}`)
expect(overseasHttp.get).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/master-data/overseas-quotes/warehouse-options')
expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/master-data/overseas-quotes', { params: { page: 1, size: 20, keyword: 'V1' } })
expect(http.get).toHaveBeenNthCalledWith(2, `/platform/v1/last-leg/master-data/overseas-quotes/${longId}`)
expect(http.get).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/master-data/overseas-quotes/warehouse-options')
})
it('keeps quote create/update DTOs distinct and sends recordVersion on writes', async () => {
@@ -60,24 +60,24 @@ describe('overseas master data API', () => {
name: '报价-编辑', providerName: '服务商', currency: 'USD', effectiveStart: '2026-08-01', effectiveEnd: '2026-12-31',
warehouseFees: create.warehouseFees, transitPrices: [], remark: null, recordVersion: 7,
}
vi.mocked(overseasHttp.post).mockResolvedValue(ok(quoteDetail))
vi.mocked(overseasHttp.put).mockResolvedValue(ok({ ...quoteDetail, summary: { ...quoteSummary, recordVersion: '8' } }))
vi.mocked(overseasHttp.patch).mockResolvedValue(ok({ ...quoteDetail, summary: { ...quoteSummary, status: 'ACTIVE', recordVersion: '9' } }))
vi.mocked(http.post).mockResolvedValue(ok(quoteDetail))
vi.mocked(http.put).mockResolvedValue(ok({ ...quoteDetail, summary: { ...quoteSummary, recordVersion: '8' } }))
vi.mocked(http.patch).mockResolvedValue(ok({ ...quoteDetail, summary: { ...quoteSummary, status: 'ACTIVE', recordVersion: '9' } }))
await createQuote(create)
const updated = await updateQuote(longId, update)
await changeQuoteStatus(longId, 'ACTIVE', 8)
expect(updated.summary.recordVersion).toBe(8)
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/master-data/overseas-quotes', create)
expect(overseasHttp.put).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/overseas-quotes/${longId}`, update)
expect(overseasHttp.patch).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/overseas-quotes/${longId}/status`, { status: 'ACTIVE', recordVersion: 8 })
expect(http.post).toHaveBeenCalledWith('/platform/v1/last-leg/master-data/overseas-quotes', create)
expect(http.put).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/overseas-quotes/${longId}`, update)
expect(http.patch).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/overseas-quotes/${longId}/status`, { status: 'ACTIVE', recordVersion: 8 })
expect(update).not.toHaveProperty('warehouseId')
expect(update).not.toHaveProperty('versionCode')
})
it('normalizes exchange-rate decimal/version and uses month as the stable key', async () => {
vi.mocked(overseasHttp.get)
vi.mocked(http.get)
.mockResolvedValueOnce(ok({ records: [exchangeRate], page: '1', size: '20', total: '1', pages: '1' }))
.mockResolvedValueOnce(ok(exchangeRate))
.mockResolvedValueOnce(ok({ latestMonth: '2026-08', latestUsdToCnyAverage: '7.12345678', updatedBy: 'admin', updatedAt: '2026-08-24T10:00:00' }))
@@ -88,17 +88,17 @@ describe('overseas master data API', () => {
expect(detail).toMatchObject({ month: '2026-08', usdToCnyAverage: 7.12345678, recordVersion: 3 })
expect(summary.latestUsdToCnyAverage).toBe(7.12345678)
vi.mocked(overseasHttp.post).mockResolvedValue(ok(exchangeRate))
vi.mocked(overseasHttp.put).mockResolvedValue(ok({ ...exchangeRate, recordVersion: '4' }))
vi.mocked(overseasHttp.patch).mockResolvedValue(ok({ ...exchangeRate, status: 'INACTIVE', recordVersion: '5' }))
vi.mocked(http.post).mockResolvedValue(ok(exchangeRate))
vi.mocked(http.put).mockResolvedValue(ok({ ...exchangeRate, recordVersion: '4' }))
vi.mocked(http.patch).mockResolvedValue(ok({ ...exchangeRate, status: 'INACTIVE', recordVersion: '5' }))
await createExchangeRate({ month: '2026-08', usdToCnyAverage: 7.12345678, remark: null })
await updateExchangeRate('2026-08', { recordVersion: 3, usdToCnyAverage: 7.2, remark: null })
await changeExchangeRateStatus('2026-08', 'INACTIVE', 4, '停用')
expect(overseasHttp.patch).toHaveBeenLastCalledWith('/platform/v1/last-leg/master-data/exchange-rates/2026-08/status', { status: 'INACTIVE', recordVersion: 4, remark: '停用' })
expect(http.patch).toHaveBeenLastCalledWith('/platform/v1/last-leg/master-data/exchange-rates/2026-08/status', { status: 'INACTIVE', recordVersion: 4, remark: '停用' })
})
it('normalizes vehicle decimals/version while preserving long string IDs', async () => {
vi.mocked(overseasHttp.get)
vi.mocked(http.get)
.mockResolvedValueOnce(ok({ records: [vehicle], page: '1', size: '20', total: '1', pages: '1' }))
.mockResolvedValueOnce(ok(vehicle))
const result = await listVehicles({ page: 1, size: 20, ownership: 'OWNED' })
@@ -107,18 +107,18 @@ describe('overseas master data API', () => {
expect(detail.id).toBe(longId)
const editable = { plateNumber: 'CA-01', model: 'Truck', fuelConsumptionPer100Km: 12.5, mileageKm: 101, driverOrDispatchCompany: null, phone: null, region: 'OVERSEAS' as const, ownership: 'OWNED' as const, remark: null }
vi.mocked(overseasHttp.post).mockResolvedValue(ok(vehicle))
vi.mocked(overseasHttp.put).mockResolvedValue(ok({ ...vehicle, recordVersion: '5' }))
vi.mocked(overseasHttp.patch).mockResolvedValue(ok({ ...vehicle, status: 'INACTIVE', recordVersion: '6' }))
vi.mocked(http.post).mockResolvedValue(ok(vehicle))
vi.mocked(http.put).mockResolvedValue(ok({ ...vehicle, recordVersion: '5' }))
vi.mocked(http.patch).mockResolvedValue(ok({ ...vehicle, status: 'INACTIVE', recordVersion: '6' }))
await createVehicle({ ...editable, status: 'IN_SERVICE' })
await updateVehicle(longId, { ...editable, recordVersion: 4 })
await changeVehicleStatus(longId, 'INACTIVE', 5)
expect(overseasHttp.put).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/vehicles/${longId}`, { ...editable, recordVersion: 4 })
expect(overseasHttp.patch).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/vehicles/${longId}/status`, { status: 'INACTIVE', recordVersion: 5 })
expect(http.put).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/vehicles/${longId}`, { ...editable, recordVersion: 4 })
expect(http.patch).toHaveBeenCalledWith(`/platform/v1/last-leg/master-data/vehicles/${longId}/status`, { status: 'INACTIVE', recordVersion: 5 })
})
it('preserves A0409 metadata instead of translating it into success', async () => {
vi.mocked(overseasHttp.put).mockResolvedValue({ code: 'A0409', message: '记录版本已变化', data: null, requestId: 'request-conflict', traceId: 'trace-conflict' })
vi.mocked(http.put).mockResolvedValue({ code: 'A0409', message: '记录版本已变化', data: null, requestId: 'request-conflict', traceId: 'trace-conflict' })
const error = await updateExchangeRate('2026-08', { recordVersion: 1, usdToCnyAverage: 7.2, remark: null }).catch((reason: unknown) => reason)
expect(error).toBeInstanceOf(ApiResponseError)
expect(error).toMatchObject({ code: 'A0409', message: '记录版本已变化', requestId: 'request-conflict', traceId: 'trace-conflict' })
@@ -126,7 +126,7 @@ describe('overseas master data API', () => {
it('downloads real CSV Blobs and rejects JSON error Blobs with trace metadata', async () => {
const csv = new Blob(['id,status\n1,ACTIVE\n'], { type: 'text/csv;charset=UTF-8' })
vi.mocked(overseasHttp.get)
vi.mocked(http.get)
.mockResolvedValueOnce(csv)
.mockResolvedValueOnce(csv)
.mockResolvedValueOnce(csv)
@@ -138,8 +138,8 @@ describe('overseas master data API', () => {
const error = await exportVehicles({}).catch((reason: unknown) => reason)
expect(error).toBeInstanceOf(ApiResponseError)
expect(error).toMatchObject({ code: 'A0409', message: '导出条件已变化', requestId: 'request-export' })
expect(overseasHttp.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/master-data/overseas-quotes/export', { params: { status: 'ACTIVE' }, responseType: 'arraybuffer' })
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/master-data/exchange-rates/export', { params: {}, responseType: 'arraybuffer' })
expect(overseasHttp.get).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/master-data/vehicles/export', { params: { ownership: 'OWNED' }, responseType: 'arraybuffer' })
expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/last-leg/master-data/overseas-quotes/export', { params: { status: 'ACTIVE' }, responseType: 'arraybuffer' })
expect(http.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/master-data/exchange-rates/export', { params: {}, responseType: 'arraybuffer' })
expect(http.get).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/master-data/vehicles/export', { params: { ownership: 'OWNED' }, responseType: 'arraybuffer' })
})
})