282 lines
18 KiB
JavaScript
282 lines
18 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import test from 'node:test'
|
|
import { runInNewContext } from 'node:vm'
|
|
import ts from 'typescript'
|
|
|
|
const storeSource = readFileSync(new URL('./firstLegOrders.ts', import.meta.url), 'utf8')
|
|
const apiSource = readFileSync(new URL('../api/firstLeg.ts', import.meta.url), 'utf8')
|
|
const unifiedApiSource = readFileSync(new URL('../api/unifiedOrders.ts', import.meta.url), 'utf8')
|
|
|
|
test('saves optional express tracking, trims it, and omits it for other pickup methods', () => {
|
|
const functions = storeSource.slice(storeSource.indexOf('function apiTransport('), storeSource.indexOf('function prepareDraftForSave('))
|
|
const javascript = ts.transpileModule(functions, { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
|
|
const save = runInNewContext(`${javascript}; savePayload`)
|
|
const draft = { pickupMethod: 'SELF_EXPRESS', trackingNumber: ' SF123456789 ', destinationCountryCode: 'US', destinationType: 'OVERSEAS', items: [] }
|
|
const warehouse = { id: '1', name: '收货仓' }
|
|
assert.equal(save(draft, warehouse, '客户').expressTrackingNo, 'SF123456789')
|
|
for (const trackingNumber of ['', ' ']) {
|
|
assert.equal(save({ ...draft, trackingNumber }, warehouse, '客户').expressTrackingNo, undefined)
|
|
}
|
|
for (const pickupMethod of ['SELF_TRUCK', 'PICKUP']) {
|
|
assert.equal(save({ ...draft, pickupMethod }, warehouse, '客户').expressTrackingNo, undefined)
|
|
}
|
|
})
|
|
|
|
test('detail fetches authoritative progress on direct entry and refresh, and clears stale progress on failure', async () => {
|
|
const functionSource = storeSource.slice(storeSource.indexOf(' async function loadDetail('), storeSource.indexOf(' async function loadWarehouseDetail('))
|
|
const progressSource = readFileSync(new URL('../utils/orderProgress.ts', import.meta.url), 'utf8')
|
|
const validationSource = progressSource.slice(progressSource.indexOf('export function requireOrderProgress(')).replace('export ', '')
|
|
const javascript = ts.transpileModule(`${validationSource}\n${functionSource}`, { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
|
|
const id = '2110663764278312961'
|
|
const orderProgress = { value: {} }
|
|
let calls = 0
|
|
let response = { progress: { orderId: id, stage: 'scheduled', partial: true } }
|
|
const loadDetail = runInNewContext(`${javascript}; loadDetail`, {
|
|
orderProgress, warehouses: { value: [] },
|
|
getMerchantUnifiedOrder: async requested => { assert.equal(requested, id); calls++; return response },
|
|
mapUnifiedOrderDetail: detail => ({ id, detail }), upsertOrder: order => order,
|
|
})
|
|
await loadDetail(id)
|
|
assert.equal(orderProgress.value[id].stage, 'scheduled')
|
|
assert.equal(orderProgress.value[id].partial, true)
|
|
response = { progress: { orderId: id, stage: 'delivering', partial: false } }
|
|
await loadDetail(id)
|
|
assert.equal(calls, 2)
|
|
assert.equal(orderProgress.value[id].stage, 'delivering')
|
|
response = { progress: { orderId: 'another-order', stage: 'delivered', partial: false } }
|
|
await assert.rejects(loadDetail(id), /订单履约进度不完整/)
|
|
assert.equal(orderProgress.value[id], undefined)
|
|
})
|
|
|
|
test('renders estimated sailing from the current API without substituting actual departure', () => {
|
|
const mapper = storeSource.slice(storeSource.indexOf('function businessSummary('), storeSource.indexOf('\nconst unifiedActionLabels'))
|
|
const javascript = ts.transpileModule(mapper, { compilerOptions: { target: ts.ScriptTarget.ES2022 } }).outputText
|
|
const map = runInNewContext(`${javascript}; businessSummary`)
|
|
const transport = {
|
|
vesselName: 'REASSIGNED', voyageNo: 'V02',
|
|
estimatedSailingAt: '2026-10-07T08:00:00', actualSailingAt: '2026-10-08T09:00:00',
|
|
effectiveSailingAt: '2026-10-08T09:00:00', effectiveSailingAtEstimated: false,
|
|
estimatedPortArrivalAt: '2026-10-27T08:00:00', actualPortArrivalAt: null,
|
|
}
|
|
const mapped = map({ transport })
|
|
assert.equal(mapped.estimatedSailingAt, transport.estimatedSailingAt)
|
|
assert.equal(mapped.estimatedPortArrivalAt, transport.estimatedPortArrivalAt)
|
|
assert.equal(mapped.vesselName, 'REASSIGNED')
|
|
assert.equal(mapped.voyageNo, 'V02')
|
|
assert.equal(map().estimatedSailingAt, '')
|
|
assert.equal(map({ transport: null }).estimatedPortArrivalAt, '')
|
|
})
|
|
|
|
test('maps the current workbench row without loading detail rows', () => {
|
|
assert.match(storeSource, /result\.page\.records\.map\(item => mapUnifiedOrderRow\(item, warehouses\.value\)\)/)
|
|
assert.match(storeSource, /version: summary\.orderVersion/)
|
|
assert.doesNotMatch(storeSource, /function mapFirstLegOrderRow/)
|
|
})
|
|
|
|
test('declares the documented merchant warehouse and first-leg endpoints', () => {
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/warehouses'/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg'/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg\/inbound-code-policy'/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg\/batch'/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg\/addresses'/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/orders\/first-leg\/addresses\/\$\{id\}`/)
|
|
assert.doesNotMatch(apiSource, /merchant\/v1\/order-addresses/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg\/batch-imports\/template'/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/orders\/first-leg\/batch-imports'/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/files\/order-batch-imports\/\$\{encodeURIComponent\(batchNo\)\}\/error-reports\/\$\{encodeURIComponent\(String\(fileId\)\)\}`/)
|
|
assert.match(apiSource, /params: \{ mode \}/)
|
|
assert.match(apiSource, /data\.append\('file', file\)/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/orders\/first-leg\/\$\{id\}`/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/orders\/first-leg\/\$\{id\}\/submit`/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/warehouses\/\$\{id\}`/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/pricing\/channels'/)
|
|
assert.match(apiSource, /params: \{ originWarehouseId \}/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/pricing\/estimate'/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/first-leg\/inbounds\/\$\{encodeURIComponent\(String\(inboundId\)\)\}\/carton-label-pdf`/)
|
|
assert.match(apiSource, /`\/merchant\/v1\/first-leg\/orders\/\$\{encodeURIComponent\(String\(orderId\)\)\}\/carton-label-pdf`/)
|
|
assert.match(apiSource, /url: '\/merchant\/v1\/first-leg\/orders\/carton-label-pdf'/)
|
|
assert.match(apiSource, /data: orderIds\.map\(String\)/)
|
|
assert.doesNotMatch(apiSource, /`\/merchant\/v1\/inbounds\/\$\{encodeURIComponent\(String\(inboundId\)\)\}\/carton-label-pdf`/)
|
|
assert.match(apiSource, /signature === '%PDF-'/)
|
|
assert.match(apiSource, /throw new ApiError\('箱唛服务未返回有效的 PDF 文件', 'INVALID_PDF'\)/)
|
|
})
|
|
|
|
test('maps transport and status fields instead of treating container mode as transport mode', () => {
|
|
assert.match(storeSource, /order\.transportMode === 'SEA'.*order\.containerMode === 'FULL_CONTAINER' \? 'FCL' : 'LCL'/s)
|
|
assert.match(storeSource, /order\.id \?\? order\.orderId \?\? ''/)
|
|
assert.match(storeSource, /order\.transportMode \? clientTransport\(order\) : null/)
|
|
assert.match(storeSource, /draft\.transportMode === 'ROAD'\) return 'TRUCK'/)
|
|
assert.match(storeSource, /status === 'SUBMITTED'\) return 'PENDING_REVIEW'/)
|
|
assert.match(storeSource, /status === 'REJECTED'\) return 'PENDING_REVIEW'/)
|
|
assert.match(storeSource, /status === 'APPROVED' \|\| status === 'ACCEPTED' \|\| status === 'PROCESSING'\) return 'WAIT_MEASURE'/)
|
|
assert.match(storeSource, /status === 'COMPLETED'\) return 'COMPLETED'/)
|
|
assert.doesNotMatch(storeSource, /status === 'COMPLETED'\) return 'DELIVERED'/)
|
|
})
|
|
|
|
test('uses only the formal workbench read and surfaces failures', () => {
|
|
assert.match(storeSource, /getMerchantOrderWorkbench\(query\)/)
|
|
assert.match(storeSource, /orders\.value = \[\]/)
|
|
assert.doesNotMatch(storeSource, /isUnifiedOrderQueryUnavailable|getOrdersForClient|getMerchantFirstLegOrders|initialOrders/)
|
|
})
|
|
|
|
test('declares the final C02 row and detail contract in the API adapter', () => {
|
|
assert.match(unifiedApiSource, /const unifiedOrderBaseUrl = '\/merchant\/v1\/orders'/)
|
|
assert.doesNotMatch(unifiedApiSource, /orders\/unified/)
|
|
assert.match(unifiedApiSource, /orderId: number \| string/)
|
|
assert.match(unifiedApiSource, /mainStatus: ApiUnifiedOrderMainStatus/)
|
|
assert.match(unifiedApiSource, /nextAction: string \| null/)
|
|
assert.match(unifiedApiSource, /fulfillmentType\?: 'STANDARD' \| 'ONE_CLICK_DROPSHIP' \| null/)
|
|
assert.match(unifiedApiSource, /lxCode\?: string \| null/)
|
|
assert.match(unifiedApiSource, /customerRemark: string \| null/)
|
|
for (const field of ['pickupMethod', 'requestedPickupAt', 'containerMode', 'destinationType', 'destinationCode', 'skuNos', 'totalQuantity', 'totalNetWeightKg', 'chargeableVolumeCbm', 'inboundCompletedAt', 'shipmentId', 'referenceId']) {
|
|
assert.match(unifiedApiSource, new RegExp(`${field}:`))
|
|
}
|
|
assert.match(unifiedApiSource, /taxMode: ApiTaxMode \| null/)
|
|
assert.match(unifiedApiSource, /customsInspection: boolean \| null/)
|
|
assert.match(unifiedApiSource, /loadingVersion: number/)
|
|
assert.match(unifiedApiSource, /pickingVersion: number/)
|
|
assert.match(unifiedApiSource, /summary: MerchantUnifiedOrderRow/)
|
|
assert.match(unifiedApiSource, /order: MerchantFirstLegOrderDetail/)
|
|
assert.match(unifiedApiSource, /timeline: MerchantUnifiedTimelineEvent\[\]/)
|
|
assert.match(unifiedApiSource, /customNodes\?: MerchantUnifiedCustomNode\[\]/)
|
|
})
|
|
|
|
test('keeps unified order pagination valid when the store omits page and size', () => {
|
|
assert.match(unifiedApiSource, /const \{ page, size, \.\.\.filters \} = params/)
|
|
assert.match(unifiedApiSource, /params: \{ page: page \?\? 1, size: size \?\? 200, \.\.\.filters \}/)
|
|
})
|
|
|
|
test('draft editing reads the order aggregate while navigation uses unified detail', () => {
|
|
assert.match(storeSource, /return loadOrderAggregate\(id\)/)
|
|
assert.match(storeSource, /const detail = await getMerchantUnifiedOrder\(id\)/)
|
|
assert.doesNotMatch(storeSource, /loadLegacyDetail|draftOnly|includeDrafts/)
|
|
})
|
|
|
|
test('maps C02 authoritative status, transport, cargo and pickup snapshots', () => {
|
|
assert.match(storeSource, /id: String\(summary\.orderId\)/)
|
|
assert.match(storeSource, /transportMode: unifiedTransport\(summary\.containerMode\)/)
|
|
assert.match(storeSource, /status: summary\.mainStatus/)
|
|
assert.match(storeSource, /taxMode: summary\.taxMode \?\? 'BUY_DECLARATION'/)
|
|
assert.match(storeSource, /customsInspection: summary\.customsInspection \?\? false/)
|
|
assert.match(storeSource, /fulfillmentType: summary\.fulfillmentType \?\? 'STANDARD'/)
|
|
assert.match(storeSource, /lxCode: summary\.lxCode \?\? ''/)
|
|
assert.match(storeSource, /mapped\.fulfillmentType = detail\.summary\.fulfillmentType \?\? mapped\.fulfillmentType/)
|
|
assert.match(storeSource, /mapped\.lxCode = detail\.summary\.lxCode \?\? mapped\.lxCode/)
|
|
assert.match(storeSource, /requestedPickupAt: summary\.requestedPickupAt \?\? ''/)
|
|
assert.match(storeSource, /chargeableVolumeCbm: summary\.chargeableVolumeCbm \?\? null/)
|
|
assert.match(storeSource, /inboundedAt: summary\.inboundCompletedAt \?\? ''/)
|
|
assert.match(storeSource, /pickupMethod: summary\.pickupMethod \?\? 'SELF_EXPRESS'/)
|
|
assert.match(storeSource, /destinationType: summary\.destinationType \?\? 'PRIVATE'/)
|
|
assert.match(storeSource, /mapped\.requestedPickupAt = summary\.requestedPickupAt/)
|
|
assert.match(storeSource, /summary\?\.loading\?\.loadingVersion/)
|
|
assert.match(storeSource, /summary\?\.loading\?\.pickingVersion/)
|
|
assert.match(storeSource, /summary\?\.inboundId == null \? '' : String\(summary\.inboundId\)/)
|
|
assert.match(storeSource, /customerRemark: summary\.customerRemark \?\? ''/)
|
|
assert.match(storeSource, /PALLETIZE_DROPSHIP: '一件代发组托'/)
|
|
})
|
|
|
|
test('sends a manually entered old-system inbound code with the draft payload', () => {
|
|
assert.match(storeSource, /inboundCode: draft\.inboundCode\?\.trim\(\) \|\| undefined/)
|
|
})
|
|
|
|
test('sends backend-required channel, pricing, customs, destination and cargo fields', () => {
|
|
for (const field of ['pickupMethod', 'channelCode', 'channelName', 'pricingTaxMode', 'taxMode', 'customsInspection', 'destinationType', 'packageType']) {
|
|
assert.match(storeSource, new RegExp(field))
|
|
}
|
|
assert.match(storeSource, /containerMode: draft\.transportMode === 'FCL' \? 'FULL_CONTAINER'/)
|
|
assert.doesNotMatch(storeSource, /customerId: draft\.shipperPartyId/)
|
|
assert.doesNotMatch(apiSource, /interface FirstLegOrderSaveRequest \{[^}]*customerId\??:/s)
|
|
assert.match(storeSource, /customerName: customerName\.trim\(\)/)
|
|
assert.match(storeSource, /destinationWarehouseId: draft\.destinationType === 'PRIVATE' \? undefined : draft\.destinationWarehouseId/)
|
|
assert.match(storeSource, /senderAddressId: draft\.pickupMethod === 'PICKUP' \? draft\.senderAddressId : undefined/)
|
|
assert.match(storeSource, /privateDestinationAddressId: draft\.destinationType === 'PRIVATE' \? draft\.privateDestinationAddressId : undefined/)
|
|
assert.doesNotMatch(storeSource, /sender: partyPayload|receiver: partyPayload/)
|
|
for (const field of [
|
|
'customsHsCode', 'lengthCm', 'widthCm', 'heightCm', 'unitsPerPackage', 'totalQuantity',
|
|
'netWeightKg', 'unitDeclaredValue', 'material', 'usage', 'model', 'brand',
|
|
'productLengthCm', 'productWidthCm', 'productHeightCm', 'shipmentId', 'referenceId',
|
|
'productImageFileId', 'woodenProduct',
|
|
]) {
|
|
assert.match(storeSource, new RegExp(`${field}: item\\.`))
|
|
}
|
|
assert.match(apiSource, /POST[^\n]*product-images|method: 'POST', url: '\/merchant\/v1\/files\/product-images'/)
|
|
assert.match(apiSource, /GET[^\n]*product-images|method: 'GET', url: `\/merchant\/v1\/files\/product-images\/\$\{fileId\}`/)
|
|
})
|
|
|
|
test('separates domestic receiving warehouses from public destinations', () => {
|
|
assert.match(apiSource, /warehouseType\?: MerchantWarehouseType/)
|
|
assert.match(apiSource, /params: \{ page: 1, size: 200, status: 'ACTIVE', \.\.\.params \}/)
|
|
assert.match(storeSource, /loadAllWarehouseRecords\(warehouseType, getMerchantWarehouses\)/)
|
|
assert.match(storeSource, /loadWarehouseType\('DOMESTIC'\)/)
|
|
assert.match(storeSource, /warehouse\.warehouseType !== 'DOMESTIC'/)
|
|
assert.match(storeSource, /item\.warehouseType === draft\.destinationType/)
|
|
assert.match(storeSource, /draft\.amazonWarehouseCode = draft\.destinationType === 'FBA' \? destinationWarehouse\.code : ''/)
|
|
})
|
|
|
|
test('only blocks submission when a known company audit status is not active', () => {
|
|
assert.match(storeSource, /companyAuditStatus\?: MerchantTenantStatus/)
|
|
assert.match(storeSource, /if \(companyAuditStatus && companyAuditStatus !== 'ACTIVE'\)/)
|
|
})
|
|
|
|
test('uses the versioned merchant cancellation contract', () => {
|
|
assert.match(storeSource, /cancelMerchantFirstLegOrder\(id, payload, createIdempotencyKey\(/)
|
|
assert.match(apiSource, /cancelMerchantFirstLegOrder\(id: number \| string, payload: \{ expectedVersion: number; reason: string \}, idempotencyKey: string\)/)
|
|
})
|
|
|
|
test('sends an idempotency key for create and submit writes', () => {
|
|
assert.match(apiSource, /'Idempotency-Key': createIdempotencyKey\('first-leg-create'\)/)
|
|
assert.match(apiSource, /submitMerchantFirstLegOrder\(id: number \| string, payload: \{ expectedVersion: number \}, idempotencyKey: string\)/)
|
|
assert.match(apiSource, /'Idempotency-Key': idempotencyKey/)
|
|
assert.match(apiSource, /createMerchantFirstLegOrderBatch\(items: MerchantBatchDraftItem\[\], idempotencyKey: string\)/)
|
|
assert.match(apiSource, /headers: \{ 'Idempotency-Key': idempotencyKey \}/)
|
|
assert.match(storeSource, /createMerchantFirstLegOrderBatch\(items, idempotencyKey\)/)
|
|
assert.doesNotMatch(storeSource, /pendingSubmissions|pendingEdits|pendingCancellation/)
|
|
assert.match(storeSource, /submitMerchantFirstLegOrder\(id, \{ expectedVersion: order\.version \}, createIdempotencyKey\(/)
|
|
})
|
|
|
|
test('does not expose the contradictory backend rejected state in the client model', () => {
|
|
assert.match(storeSource, /status === 'REJECTED'\) return 'PENDING_REVIEW'/)
|
|
assert.match(storeSource, /item\.toStatus === 'REJECTED' \? '订单信息已补充'/)
|
|
})
|
|
|
|
test('maps the final backend timeline and keeps missing custom nodes empty', () => {
|
|
assert.match(storeSource, /\(detail\.timeline \?\? \[\]\)\.flatMap/)
|
|
assert.match(storeSource, /item\.mainStatus/)
|
|
assert.match(storeSource, /\(detail\.customNodes \?\? \[\]\)/)
|
|
assert.match(storeSource, /\.filter\(\(item\) => item\.customerVisible\)/)
|
|
assert.match(storeSource, /kind: 'CUSTOM_NODE'/)
|
|
assert.match(storeSource, /status: null/)
|
|
assert.match(storeSource, /\.sort\(\(left, right\)/)
|
|
})
|
|
|
|
test('keeps unified detail mapping recoverable for historical partial responses', () => {
|
|
assert.match(storeSource, /\(detail\.parties \?\? \[\]\)\.find/)
|
|
assert.match(storeSource, /\(detail\.statusHistory \?\? \[\]\)\.map/)
|
|
assert.match(storeSource, /item\.attachmentIds\?\.length \?\? 0/)
|
|
assert.match(storeSource, /if \(!detail\?\.summary \|\| !detail\.order\?\.order\) throw new Error\('订单详情数据不完整,请刷新列表后重试'\)/)
|
|
})
|
|
|
|
test('maps customer-visible carrier and order lifecycle snapshots into detail', () => {
|
|
assert.match(apiSource, /export interface MerchantFirstLegOrder[\s\S]*carrierId\?: number \| string \| null/)
|
|
assert.match(storeSource, /carrierName: order\.carrierName \?\? ''/)
|
|
assert.match(storeSource, /receivingTime: order\.receivingTime \?\? ''/)
|
|
assert.match(storeSource, /submittedAt: order\.submittedAt \?\? ''/)
|
|
assert.match(storeSource, /completedAt: order\.completedAt \?\? ''/)
|
|
})
|
|
|
|
test('maps backend warehouse snapshots and fulfillment courier fields', () => {
|
|
assert.match(storeSource, /order\.receivingWarehouseAddress/)
|
|
assert.match(storeSource, /order\.expressCompany \?\? ''/)
|
|
assert.match(storeSource, /order\.expressTrackingNo \?\? ''/)
|
|
assert.match(storeSource, /getMerchantWarehouseDetail\(id\)/)
|
|
assert.match(storeSource, /receivingCapability\.cutoffTime/)
|
|
})
|
|
|
|
test('keeps failed order loads distinct from empty results', () => {
|
|
assert.match(storeSource, /const loadError = ref\(''\)/)
|
|
assert.match(storeSource, /orderResult\.reason instanceof Error/)
|
|
assert.match(storeSource, /warehouseLoadError/)
|
|
assert.match(storeSource, /return \{ orders, orderDetails, total, queueCounts, orderProgress, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError/)
|
|
})
|