Files
admin-web/src/modules/container-loading/api/terminalDrayages.test.ts
T

86 lines
6.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import {
sendTerminalDrayageEmail,
getTerminalDrayageEmails,
retryTerminalDrayageEmail,
getTerminalDrayageHistory,
listTerminalDrayages,
recordTerminalDrayageEmptyReturn,
scheduleTerminalDrayagePickup,
selectTerminalDrayageFleet,
getDirectDeliveries, recordDirectDelivery, recordTerminalPickup,
} from './terminalDrayages'
it('records only the original actual PICKED_UP node with an immutable observed time and idempotency header', async () => {
const command = { standardNode: 'PICKED_UP' as const, observedAt: '2026-10-15T09:00:00', estimatedAt: null, actualAt: '2026-10-14T23:00:00', locationCode: 'TEST-PORT', locationName: '测试码头', reason: '司机确认已提柜' }
vi.mocked(http.post).mockResolvedValue({ data: {} })
await recordTerminalPickup('9007199254740993', command, 'same-pickup-key')
expect(http.post).toHaveBeenCalledWith('/platform/v1/container-shipping-nodes/9007199254740993', command, { headers: { 'Idempotency-Key': 'same-pickup-key' } })
})
it('uses the actual direct-delivery source and stable idempotency key without coercing long IDs', async () => {
vi.mocked(http.get).mockResolvedValue({ data: [] }); await getDirectDeliveries('9007199254740993')
expect(http.get).toHaveBeenCalledWith('/platform/v1/terminal-drayages/9007199254740993/direct-deliveries')
const request = { expectedVersion: 0, actualAt: '2026-10-16T01:00:00', timezone: 'America/Los_Angeles', receivedBy: '测试签收人', evidenceNo: 'POD-001', remark: null }
vi.mocked(http.post).mockResolvedValue({ data: { requirementKey: 'INBOUND:9007199254740999', version: 1 } })
await recordDirectDelivery('9007199254740993', 'INBOUND:9007199254740999', request, 'retry-same-key')
expect(http.post).toHaveBeenCalledWith('/platform/v1/terminal-drayages/9007199254740993/direct-deliveries/9007199254740999', request, { headers: { 'Idempotency-Key': 'retry-same-key' } })
await expect(recordDirectDelivery('1', 'ORDER:1', request, 'bad')).rejects.toThrow('标识无效')
})
vi.mock('@/shared/api/http', () => ({
http: { get: vi.fn(), post: vi.fn() },
unwrapApiResponse: <T>(response: { data: T }) => response.data,
}))
const view = {
containerBookingId: '9007199254740993', containerNo: 'MSCU1234567', oceanBookingId: '9007199254740994',
status: 'UNLOADED', version: '3', fleetSupplierId: '9007199254740995', fleetSupplierName: '港运车队',
pickupAppointmentAt: '2026-09-07T08:00:00', actualPickupAt: '2026-09-07T09:00:00', estimatedEmptyReturnAt: '2026-09-10T10:00:00',
actualEmptyReturnAt: null, unloadingCompletedAt: '2026-09-07T10:00:00',
emptyReturnWarning: { status: 'OVERDUE', deadlineAt: '2026-09-10T10:00:00', overdueHours: '26', overdueDays: '1' },
allowedActions: ['SELECT_FLEET', 'RECORD_EMPTY_RETURN'], updatedAt: '2026-09-11T12:00:00',
}
describe('terminal drayages API', () => {
beforeEach(() => vi.clearAllMocks())
it('queues pickup email with version and idempotency and scopes retries to the container', async () => {
vi.mocked(http.post).mockResolvedValueOnce({ data: view })
await sendTerminalDrayageEmail('9007199254740993', 3)
expect(http.post).toHaveBeenCalledWith('/platform/v1/terminal-drayages/9007199254740993/pickup-email',
{ expectedVersion: 3 }, { headers: { 'Idempotency-Key': expect.any(String) } })
vi.mocked(http.get).mockResolvedValueOnce({ data: { deliveryEnabled: false, records: [{ id: '9007199254740999', status: 'FAILED' }] } })
expect((await getTerminalDrayageEmails('9007199254740993')).records[0].id).toBe('9007199254740999')
vi.mocked(http.post).mockResolvedValueOnce({ data: null })
await retryTerminalDrayageEmail('9007199254740993', '9007199254740999')
expect(http.post).toHaveBeenLastCalledWith('/platform/v1/terminal-drayages/9007199254740993/emails/9007199254740999/retry')
})
it('sends repeated container ids and preserves long identifiers as strings', async () => {
vi.mocked(http.get).mockResolvedValueOnce({ data: [view] })
const result = await listTerminalDrayages(['9007199254740993', '9007199254740994'])
const config = vi.mocked(http.get).mock.calls[0][1] as { params: URLSearchParams }
expect(config.params.getAll('containerBookingIds')).toEqual(['9007199254740993', '9007199254740994'])
expect(result[0]).toMatchObject({ containerBookingId: '9007199254740993', oceanBookingId: '9007199254740994', fleetSupplierId: '9007199254740995', version: 3 })
expect(result[0].emptyReturnWarning).toMatchObject({ overdueHours: 26, overdueDays: 1 })
})
it('uses frozen command paths, versions and idempotency keys', async () => {
vi.mocked(http.post).mockResolvedValue({ data: view })
await selectTerminalDrayageFleet('101', { expectedVersion: 3, fleetSupplierId: '501', fleetSupplierName: '港运车队' })
await scheduleTerminalDrayagePickup('101', { expectedVersion: 4, pickupAppointmentAt: '2026-09-07T08:00:00' })
await recordTerminalDrayageEmptyReturn('101', { expectedVersion: 5, actualAt: '2026-09-10T09:00:00', remark: '实际还柜' })
expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/terminal-drayages/101/fleet', expect.objectContaining({ expectedVersion: 3, fleetSupplierId: '501' }), expect.objectContaining({ headers: { 'Idempotency-Key': expect.stringContaining('terminal-drayage-fleet-') } }))
expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/terminal-drayages/101/pickup-appointment', expect.objectContaining({ expectedVersion: 4 }), expect.any(Object))
expect(http.post).toHaveBeenNthCalledWith(3, '/platform/v1/terminal-drayages/101/empty-return', expect.objectContaining({ expectedVersion: 5, remark: '实际还柜' }), expect.objectContaining({ headers: { 'Idempotency-Key': expect.stringContaining('terminal-drayage-empty-return-') } }))
})
it('loads operation history from the container business id', async () => {
vi.mocked(http.get).mockResolvedValueOnce({ data: [{ action: 'SELECT_FLEET', fromStatus: 'ARRIVED', toStatus: 'ARRIVED', fromVersion: 0, toVersion: 1, reason: null, operatorId: '9007199254740993', operatorName: '平台管理员', createdAt: '2026-09-07T08:00:00' }] })
const result = await getTerminalDrayageHistory('101')
expect(http.get).toHaveBeenCalledWith('/platform/v1/terminal-drayages/101/history')
expect(result[0].operatorId).toBe('9007199254740993')
})
})