import { beforeEach, describe, expect, it, vi } from 'vitest' import { ApiResponseError, http } from '@/shared/api/http' import { changeDriverStatus, createDriver, downloadDriverLicense, getDriver, getDriverQualification, listDriverHistory, listDrivers, reviewDriver, submitDriver, updateDriver, uploadDriverLicense } from './drivers' import type { DriverDraft } from '../driverTypes' vi.mock('@/shared/api/http', async importOriginal => ({ ...(await importOriginal()), http: { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn() } })) const id = '9223372036854775701' const fileId = '9223372036854775801' const base = '/platform/v1/last-leg/fleet/drivers' const details: DriverDraft = { name: '测试司机', phone: 'test-phone', ownership: 'OWNED', region: 'OVERSEAS', licenseNumber: 'LIC-1', licenseType: 'Class A', licenseIssuedOn: null, licenseExpiresOn: '2026-10-22', licenseZone: 'America/Los_Angeles', remark: null, profile: null } const record = { id, driverNo: 'DR-TEST', details, active: true, reviewStatus: 'APPROVED', licenseFileId: fileId, recordVersion: '2', updatedBy: 'operator', updatedAt: '2026-10-02T12:00:00', qualification: { canDispatch: true, expiringSoon: true, reason: 'ELIGIBLE', licenseExpiresOn: '2026-10-22' } } const ok = (data: unknown) => ({ code: '200', message: '成功', data }) beforeEach(() => vi.clearAllMocks()) describe('driver APIs', () => { it('preserves long IDs and date-only values, uses one page request, and normalizes versions', async () => { vi.mocked(http.get).mockResolvedValueOnce(ok({ records: [record], page: '1', size: '20', total: '1', pages: '1', summary: { total: '1', owned: '1', dispatched: '0', expiringSoon: '1', expired: '0' } })).mockResolvedValueOnce(ok(record)) const result = await listDrivers({ page: 1, size: 20, active: false }) expect(result).toMatchObject({ page: 1, total: 1, records: [{ id, licenseFileId: fileId, recordVersion: 2, details }] }) expect(http.get).toHaveBeenCalledTimes(1) await getDriver(id) expect(http.get).toHaveBeenNthCalledWith(1, base, { params: { page: 1, size: 20, active: false } }) expect(http.get).toHaveBeenNthCalledWith(2, `${base}/${id}`, { params: { expectedEnd: undefined } }) }) it('sends current versions to edits, reviews, status changes, and resubmission', async () => { vi.mocked(http.post).mockResolvedValue(ok(record)) vi.mocked(http.put).mockResolvedValue(ok(record)) vi.mocked(http.patch).mockResolvedValue(ok(record)) await createDriver(details) await updateDriver(id, 2, details) await reviewDriver(id, 3, 'REJECTED', '附件不清楚') await changeDriverStatus(id, 4, false, '暂停驾驶') await submitDriver(id, 5) expect(http.post).toHaveBeenCalledWith(base, details) expect(http.put).toHaveBeenCalledWith(`${base}/${id}`, { recordVersion: 2, details }) expect(http.post).toHaveBeenCalledWith(`${base}/${id}/review`, { recordVersion: 3, decision: 'REJECTED', reason: '附件不清楚' }) expect(http.patch).toHaveBeenCalledWith(`${base}/${id}/status`, { recordVersion: 4, active: false, reason: '暂停驾驶' }) expect(http.post).toHaveBeenCalledWith(`${base}/${id}/submit`, { recordVersion: 5 }) }) it('uploads a real multipart file with the version and leaves multipart boundary to the browser', async () => { vi.mocked(http.post).mockResolvedValue(ok(record)) const file = new File(['%PDF-1.7'], 'license.pdf', { type: 'application/pdf' }) await uploadDriverLicense(id, 2, file) const [url, body] = vi.mocked(http.post).mock.calls[0]! expect(url).toBe(`${base}/${id}/license`) expect(body).toBeInstanceOf(FormData) expect((body as FormData).get('recordVersion')).toBe('2') expect((body as FormData).get('file')).toBe(file) }) it('takes task qualification from server and keeps history snapshots distinct', async () => { vi.mocked(http.get).mockResolvedValueOnce(ok(record.qualification)).mockResolvedValueOnce(ok({ page: '1', size: '20', total: '1', pages: '1', records: [{ recordVersion: '1', action: 'UPLOAD_LICENSE', snapshot: { ...record, recordVersion: '1', reviewStatus: 'PENDING' } }] })) const expectedEnd = '2026-10-20T18:00:00Z' expect(await getDriverQualification(id, expectedEnd)).toEqual(record.qualification) expect(http.get).toHaveBeenNthCalledWith(1, `${base}/${id}/qualification`, { params: { expectedEnd } }) expect((await listDriverHistory(id)).records[0]).toMatchObject({ recordVersion: 1, snapshot: { recordVersion: 1, reviewStatus: 'PENDING' } }) }) it('downloads only real supported blobs and rejects HTTP200 JSON business failures', async () => { const pdf = new Blob(['%PDF-1.7'], { type: 'application/pdf' }) vi.mocked(http.get).mockResolvedValueOnce(pdf).mockResolvedValueOnce({ code: 'A0404', message: '文件未关联', requestId: 'test-request' }).mockResolvedValueOnce(new Blob([''], { type: 'text/html' })) expect(await downloadDriverLicense(id, fileId)).toBe(pdf) expect(http.get).toHaveBeenNthCalledWith(1, `${base}/${id}/license/${fileId}`, { responseType: 'blob' }) await expect(downloadDriverLicense(id, fileId)).rejects.toMatchObject({ message: '文件未关联', requestId: 'test-request' }) await expect(downloadDriverLicense(id, fileId)).rejects.toThrow('格式不受支持') }) it('does not turn a failed write into a success or round an unsafe record version', async () => { vi.mocked(http.post).mockResolvedValueOnce({ code: 'A0409', message: '请刷新后重试' }).mockResolvedValueOnce(ok({ ...record, recordVersion: '9223372036854775807' })) await expect(reviewDriver(id, 1, 'APPROVED', null)).rejects.toBeInstanceOf(ApiResponseError) await expect(createDriver(details)).rejects.toThrow('无效的分页或版本信息') }) })