2026-07-30 17:59:42 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
|
|
|
|
const storeSource = readFileSync(new URL('./firstLegOrders.ts', import.meta.url), 'utf8')
|
|
|
|
|
const apiSource = readFileSync(new URL('../api/firstLeg.ts', import.meta.url), 'utf8')
|
2026-08-06 13:56:13 +08:00
|
|
|
const unifiedApiSource = readFileSync(new URL('../api/unifiedOrders.ts', import.meta.url), 'utf8')
|
2026-07-30 17:59:42 +08:00
|
|
|
|
|
|
|
|
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, /`\/merchant\/v1\/orders\/first-leg\/\$\{id\}`/)
|
|
|
|
|
assert.match(apiSource, /`\/merchant\/v1\/orders\/first-leg\/\$\{id\}\/submit`/)
|
2026-07-30 21:23:06 +08:00
|
|
|
assert.match(apiSource, /`\/merchant\/v1\/warehouses\/\$\{id\}`/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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, /draft\.transportMode === 'ROAD'\) return 'TRUCK'/)
|
|
|
|
|
assert.match(storeSource, /status === 'SUBMITTED'\) return 'PENDING_REVIEW'/)
|
|
|
|
|
assert.match(storeSource, /status === 'REJECTED'\) return 'PENDING_REVIEW'/)
|
2026-08-06 13:56:13 +08:00
|
|
|
assert.match(storeSource, /status === 'APPROVED' \|\| status === 'ACCEPTED' \|\| status === 'PROCESSING'\) return 'WAIT_MEASURE'/)
|
|
|
|
|
assert.match(storeSource, /status === 'COMPLETED'\) return 'DELIVERED'/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-06 13:56:13 +08:00
|
|
|
test('prefers unified reads and only falls back to the real legacy API when C02 is unavailable', () => {
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.match(storeSource, /orders\.value = \[\]/)
|
|
|
|
|
assert.match(storeSource, /warehouses\.value = \[\]/)
|
2026-08-06 14:55:13 +08:00
|
|
|
assert.match(storeSource, /Promise\.allSettled\(\[getOrdersForClient\(query\), loadWarehouseType\('DOMESTIC', true\)\]\)/)
|
2026-08-06 13:56:13 +08:00
|
|
|
assert.match(storeSource, /getMerchantUnifiedOrders\(unifiedQuery\)/)
|
|
|
|
|
assert.match(storeSource, /isUnifiedOrderQueryUnavailable\(error\)/)
|
|
|
|
|
assert.match(storeSource, /getMerchantFirstLegOrders\(legacyQuery\(query\)\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
assert.doesNotMatch(storeSource, /DEMO_PLATFORM_RECEIVING_WAREHOUSES|initialOrders/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-06 13:56:13 +08:00
|
|
|
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, /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\[\]/)
|
|
|
|
|
assert.match(unifiedApiSource, /error\.status === 404 \|\| error\.code === 'A0404'/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-06 15:32:07 +08:00
|
|
|
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 \}/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-06 13:56:13 +08:00
|
|
|
test('keeps drafts on the legacy read path without assigning a unified main status', () => {
|
|
|
|
|
assert.match(storeSource, /getMerchantFirstLegOrders\(legacyQuery\(query, 'DRAFT'\)\)/)
|
|
|
|
|
assert.match(storeSource, /getById\(id\)\?\.status === 'DRAFT' \? loadLegacyDetail\(id\) : loadUnifiedDetail\(id\)/)
|
|
|
|
|
assert.match(storeSource, /return loadLegacyDetail\(String\(createdId\)\)/)
|
|
|
|
|
assert.match(storeSource, /draftLoadError/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('routes supported filters to C02 without sending DRAFT as a main status', () => {
|
|
|
|
|
assert.match(storeSource, /interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery/)
|
|
|
|
|
assert.match(storeSource, /if \(query\.draftOnly\)/)
|
|
|
|
|
assert.match(storeSource, /const unifiedQuery: MerchantUnifiedOrderQuery/)
|
|
|
|
|
assert.match(storeSource, /getMerchantUnifiedOrders\(unifiedQuery\)/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('maps C02 rows by orderId and leaves absent transport details unknown', () => {
|
|
|
|
|
assert.match(storeSource, /id: String\(summary\.orderId\)/)
|
|
|
|
|
assert.match(storeSource, /transportMode: null/)
|
|
|
|
|
assert.match(storeSource, /summary\?\.loading\?\.loadingVersion/)
|
|
|
|
|
assert.match(storeSource, /summary\?\.loading\?\.pickingVersion/)
|
|
|
|
|
assert.match(storeSource, /summary\?\.inboundId == null \? '' : String\(summary\.inboundId\)/)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-30 17:59:42 +08:00
|
|
|
test('sends backend-required channel, customs, destination and cargo fields', () => {
|
|
|
|
|
for (const field of ['pickupMethod', 'channelCode', 'channelName', 'taxMode', 'customsInspection', 'destinationType', 'packageType']) {
|
|
|
|
|
assert.match(storeSource, new RegExp(field))
|
|
|
|
|
}
|
|
|
|
|
assert.match(storeSource, /containerMode: draft\.transportMode === 'FCL' \? 'FULL_CONTAINER'/)
|
2026-08-04 12:01:05 +08:00
|
|
|
assert.doesNotMatch(storeSource, /customerId: draft\.shipperPartyId/)
|
|
|
|
|
assert.match(storeSource, /customerName: customerName\.trim\(\)/)
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(storeSource, /destinationWarehouseId: draft\.destinationType === 'PRIVATE' \? undefined : draft\.destinationWarehouseId/)
|
2026-08-04 12:01:05 +08:00
|
|
|
assert.match(storeSource, /draft\.pickupMethod === 'PICKUP'.*draft\.shipperPartySnapshot/s)
|
|
|
|
|
assert.match(storeSource, /draft\.destinationType === 'PRIVATE'.*draft\.consigneePartySnapshot/s)
|
2026-08-04 11:14:29 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('separates domestic receiving warehouses from public destinations', () => {
|
2026-08-06 14:55:13 +08:00
|
|
|
assert.match(apiSource, /warehouseType\?: MerchantWarehouseType/)
|
|
|
|
|
assert.match(apiSource, /params: \{ page: 1, size: 200, status: 'ACTIVE', \.\.\.params \}/)
|
2026-08-06 15:32:07 +08:00
|
|
|
assert.match(storeSource, /loadAllWarehouseRecords\(warehouseType, getMerchantWarehouses\)/)
|
2026-08-06 14:55:13 +08:00
|
|
|
assert.match(storeSource, /loadWarehouseType\('DOMESTIC', true\)/)
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(storeSource, /warehouse\.warehouseType !== 'DOMESTIC'/)
|
|
|
|
|
assert.match(storeSource, /item\.warehouseType === draft\.destinationType/)
|
|
|
|
|
assert.match(storeSource, /draft\.amazonWarehouseCode = draft\.destinationType === 'FBA' \? destinationWarehouse\.code : ''/)
|
2026-08-04 10:31:53 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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'\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('does not expose the out-of-scope cancellation write', () => {
|
|
|
|
|
assert.doesNotMatch(storeSource, /cancelMerchant|function cancel|\/cancel/)
|
|
|
|
|
assert.doesNotMatch(apiSource, /\/cancel/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('sends an idempotency key for create and submit writes', () => {
|
|
|
|
|
assert.match(apiSource, /'Idempotency-Key': createIdempotencyKey\('first-leg-create'\)/)
|
|
|
|
|
assert.match(apiSource, /'Idempotency-Key': createIdempotencyKey\(`first-leg-submit-\$\{id\}`\)/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('does not expose the contradictory backend rejected state in the client model', () => {
|
|
|
|
|
assert.match(storeSource, /status === 'REJECTED'\) return 'PENDING_REVIEW'/)
|
2026-08-06 13:56:13 +08:00
|
|
|
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\)/)
|
2026-07-30 17:59:42 +08:00
|
|
|
})
|
2026-07-30 21:23:06 +08:00
|
|
|
|
|
|
|
|
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\(''\)/)
|
2026-08-04 11:14:29 +08:00
|
|
|
assert.match(storeSource, /orderResult\.reason instanceof Error/)
|
|
|
|
|
assert.match(storeSource, /warehouseLoadError/)
|
2026-08-06 13:56:13 +08:00
|
|
|
assert.match(storeSource, /return \{ orders, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, draftLoadError/)
|
2026-07-30 21:23:06 +08:00
|
|
|
})
|