57 lines
5.4 KiB
TypeScript
57 lines
5.4 KiB
TypeScript
import { beforeEach, expect, it, vi } from 'vitest'
|
|
import { http, ApiResponseError } from '@/shared/api/http'
|
|
import { getCompanyCost, commandCompanyCost, payCompanyCost, listCompanyCostHistory, listCompanyCostPayments, uploadCompanyCostVoucher, downloadCompanyCostVoucher } from './companyVehicleCosts'
|
|
vi.mock('@/shared/api/http', async original => ({ ...(await original<typeof import('@/shared/api/http')>()), http: { get: vi.fn(), post: vi.fn() } }))
|
|
const source = { sourceType: 'REPAIR', sourceNo: 'VR-ONE', sourceVersion: '1', vehicleId: '90071992547409931', vehiclePlate: 'VEH-A', tripNo: null,
|
|
supplierId: '90071992547409932', feeName: '维修', businessDate: '2026-01-01', originalCurrency: 'USD', originalAmount: '10.00', exchangeRate: '7.000000', exchangeRateMonth: '2026-01', amount: '70.00', remark: null }
|
|
const wire = { costNo: 'VC-ONE', subjectCode: 'PLATFORM_DEFAULT', supplierName: '维修供应商', currency: 'CNY', status: 'APPROVED', recordVersion: '90071992547409933', paidAmount: '0.00', outstandingAmount: '70.00', source }
|
|
const base = '/platform/finance/v1/vehicle-costs/VC-ONE'
|
|
const ok = (data: unknown) => ({ code: '200', data })
|
|
beforeEach(() => vi.resetAllMocks())
|
|
it('reads real company detail while preserving long versions and original-currency facts', async () => {
|
|
vi.mocked(http.get).mockResolvedValue(ok(wire))
|
|
const result = await getCompanyCost('VC-ONE')
|
|
expect(result).toMatchObject({ recordVersion: wire.recordVersion, paidAmount: 0, outstandingAmount: 70,
|
|
source: { supplierId: source.supplierId, originalAmount: 10, amount: 70, exchangeRate: 7 } })
|
|
expect(http.get).toHaveBeenCalledExactlyOnceWith(base)
|
|
})
|
|
it('sends exact action versions and stable Idempotency-Key headers for booking, void and payment', async () => {
|
|
vi.mocked(http.post).mockResolvedValue(ok(wire))
|
|
const command = { expectedVersion: wire.recordVersion, reason: '核对' }
|
|
await commandCompanyCost('VC-ONE', 'approve', 'approve-key', command)
|
|
await commandCompanyCost('VC-ONE', 'void', 'void-key', command)
|
|
const payment = { expectedVersion: wire.recordVersion, amount: 20, paidAt: '2026-01-02T00:00:00Z', method: 'BANK_TRANSFER' as const, referenceNo: null, voucherFileIds: ['90071992547409934'], remark: null }
|
|
await payCompanyCost('VC-ONE', 'payment-key', payment)
|
|
expect(http.post).toHaveBeenNthCalledWith(1, `${base}/approve`, command, { headers: { 'Idempotency-Key': 'approve-key' } })
|
|
expect(http.post).toHaveBeenNthCalledWith(2, `${base}/void`, command, { headers: { 'Idempotency-Key': 'void-key' } })
|
|
expect(http.post).toHaveBeenNthCalledWith(3, `${base}/payments`, payment, { headers: { 'Idempotency-Key': 'payment-key' } })
|
|
})
|
|
it('loads separately paged history and payment records without treating unknown payable as zero', async () => {
|
|
vi.mocked(http.get).mockResolvedValueOnce(ok({ records: [{ recordVersion: '0', action: 'RECEIVE', reason: null, operatorName: '系统', occurredAt: '2026-01-01T00:00:00Z', snapshot: { ...wire, status: 'PENDING', outstandingAmount: null } }], page: 2, size: 10, total: 11, pages: 2 }))
|
|
const history = await listCompanyCostHistory('VC-ONE', 2)
|
|
expect(history.records[0]?.snapshot.outstandingAmount).toBeNull()
|
|
vi.mocked(http.get).mockResolvedValueOnce(ok({ records: [{ paymentNo: 'VP-ONE', currency: 'CNY', amount: '20.00', voucherFileIds: ['90071992547409934'] }], page: 1, size: 10, total: 1, pages: 1 }))
|
|
const payments = await listCompanyCostPayments('VC-ONE')
|
|
expect(payments.records[0]).toMatchObject({ amount: 20, voucherFileIds: ['90071992547409934'] })
|
|
expect(http.get).toHaveBeenNthCalledWith(1, `${base}/history`, { params: { page: 2, size: 10 } })
|
|
expect(http.get).toHaveBeenNthCalledWith(2, `${base}/payments`, { params: { page: 1, size: 10 } })
|
|
})
|
|
it('uploads only to the selected cost and rejects customer or operational photographs as payment evidence', async () => {
|
|
const file = new File(['png'], 'payment.png', { type: 'image/png' })
|
|
vi.mocked(http.post).mockResolvedValueOnce(ok({ fileId: '90071992547409934', fileName: 'payment.png', contentType: 'image/png', sizeBytes: '3', ownerScope: 'PLATFORM', tenantId: '0', businessType: 'FINANCE_VEHICLE_VOUCHER' }))
|
|
expect(await uploadCompanyCostVoucher('VC-ONE', file)).toMatchObject({ fileId: '90071992547409934', sizeBytes: 3 })
|
|
expect(vi.mocked(http.post).mock.calls[0]?.[0]).toBe(`${base}/vouchers`)
|
|
expect((vi.mocked(http.post).mock.calls[0]?.[1] as FormData).get('file')).toBe(file)
|
|
vi.mocked(http.post).mockResolvedValueOnce(ok({ fileId: '1', ownerScope: 'CUSTOMER', tenantId: '12', businessType: 'FINANCE_VOUCHER' }))
|
|
await expect(uploadCompanyCostVoucher('VC-ONE', file)).rejects.toThrow('凭证返回内容无效')
|
|
})
|
|
it('keeps JSON download errors as diagnostic errors rather than successful images', async () => {
|
|
vi.mocked(http.get).mockResolvedValue(new Blob([JSON.stringify({ code: 'FIN_404', message: '不存在', requestId: 'request-one', traceId: 'trace-one' })], { type: 'application/json' }))
|
|
await expect(downloadCompanyCostVoucher('VC-ONE', '11')).rejects.toBeInstanceOf(ApiResponseError)
|
|
})
|
|
it('rejects mismatched costs and imprecise money instead of displaying successful facts', async () => {
|
|
vi.mocked(http.get).mockResolvedValueOnce(ok({ ...wire, costNo: 'VC-OTHER' })).mockResolvedValueOnce(ok({ ...wire, paidAmount: '90071992547409.92' }))
|
|
await expect(getCompanyCost('VC-ONE')).rejects.toThrow('归属')
|
|
await expect(getCompanyCost('VC-ONE')).rejects.toThrow('精确展示')
|
|
})
|