fix(phase4): harden development acceptance adapters
This commit is contained in:
@@ -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: 'blob' })
|
||||
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/master-data/exchange-rates/export', { params: {}, responseType: 'blob' })
|
||||
expect(overseasHttp.get).toHaveBeenNthCalledWith(3, '/platform/v1/last-leg/master-data/vehicles/export', { params: { ownership: 'OWNED' }, responseType: 'blob' })
|
||||
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' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,27 +34,52 @@ const normalizeQuoteDetail = (value: QuoteDetailWire): OverseasWarehouseQuoteDet
|
||||
const normalizeExchangeRate = (value: ExchangeRateWire): MonthlyExchangeRate => ({ ...value, usdToCnyAverage: numeric(value.usdToCnyAverage), recordVersion: numeric(value.recordVersion) })
|
||||
const normalizeVehicle = (value: VehicleWire): VehicleDetail => ({ ...value, fuelConsumptionPer100Km: numeric(value.fuelConsumptionPer100Km), mileageKm: numeric(value.mileageKm), recordVersion: numeric(value.recordVersion) })
|
||||
|
||||
async function csvError(blob: Blob, status: number) {
|
||||
if (!blob.type.toLowerCase().includes('json')) return null
|
||||
type DownloadPayload = Blob | ArrayBuffer | ArrayBufferView | string
|
||||
|
||||
async function downloadText(payload: DownloadPayload) {
|
||||
if (typeof payload === 'string') return payload
|
||||
if (payload instanceof Blob) return payload.text()
|
||||
const bytes = payload instanceof ArrayBuffer
|
||||
? new Uint8Array(payload)
|
||||
: new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength)
|
||||
return new TextDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
async function csvError(payload: DownloadPayload, status: number) {
|
||||
const contentType = payload instanceof Blob ? payload.type.toLowerCase() : ''
|
||||
const text = await downloadText(payload)
|
||||
if (!contentType.includes('json') && !/^\s*[\[{]/.test(text)) return null
|
||||
try {
|
||||
const payload = JSON.parse(await blob.text()) as Partial<ApiEnvelope<unknown>>
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new Error('invalid envelope')
|
||||
return new ApiResponseError(payload.message || 'CSV 导出失败', payload.code || 'DOWNLOAD_FAILED', status,
|
||||
payload.data, payload.requestId, payload.traceId)
|
||||
const envelope = JSON.parse(text) as Partial<ApiEnvelope<unknown>>
|
||||
if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) throw new Error('invalid envelope')
|
||||
return new ApiResponseError(envelope.message || 'CSV 导出失败', envelope.code || 'DOWNLOAD_FAILED', status,
|
||||
envelope.data, envelope.requestId, envelope.traceId)
|
||||
} catch (error) {
|
||||
if (error instanceof ApiResponseError) return error
|
||||
return new ApiResponseError('CSV 导出接口返回了无法识别的 JSON 响应', 'INVALID_DOWNLOAD_RESPONSE', status)
|
||||
}
|
||||
}
|
||||
function csvBlob(payload: DownloadPayload) {
|
||||
if (payload instanceof Blob) return payload
|
||||
if (typeof payload === 'string') return new Blob([payload], { type: 'text/csv;charset=utf-8' })
|
||||
const bytes = payload instanceof ArrayBuffer
|
||||
? new Uint8Array(payload)
|
||||
: new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength)
|
||||
const copy = new Uint8Array(bytes.byteLength)
|
||||
copy.set(bytes)
|
||||
return new Blob([copy.buffer], { type: 'text/csv;charset=utf-8' })
|
||||
}
|
||||
async function download(path: string, params: object) {
|
||||
try {
|
||||
const blob = await overseasHttp.get<Blob, Blob>(path, { params, responseType: 'blob' })
|
||||
const error = await csvError(blob, 200)
|
||||
const payload = await overseasHttp.get<DownloadPayload, DownloadPayload>(path, { params, responseType: 'arraybuffer' })
|
||||
const error = await csvError(payload, 200)
|
||||
if (error) throw error
|
||||
return blob
|
||||
return csvBlob(payload)
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.data instanceof Blob) {
|
||||
const responseError = await csvError(error.response.data, error.response.status)
|
||||
const axiosError = axios.isAxiosError(error) ? error : null
|
||||
const responsePayload = axiosError?.response?.data as DownloadPayload | undefined
|
||||
if (responsePayload !== undefined) {
|
||||
const responseError = await csvError(responsePayload, axiosError?.response?.status || 0)
|
||||
if (responseError) throw responseError
|
||||
}
|
||||
throw error
|
||||
|
||||
Reference in New Issue
Block a user