Files
client-web/src/modules/order-management/deliveryOrders.test.mjs
T

121 lines
8.2 KiB
JavaScript
Raw Normal View History

2026-09-01 14:24:00 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import ts from 'typescript'
const apiSource = readFileSync(new URL('./api/deliveryOrders.ts', import.meta.url), 'utf8')
const routesSource = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8')
const listSource = readFileSync(new URL('./views/DeliveryOrdersView.vue', import.meta.url), 'utf8')
const detailSource = readFileSync(new URL('./views/DeliveryOrderDetailView.vue', import.meta.url), 'utf8')
2026-09-18 16:48:35 +08:00
const deliveryStyles = readFileSync(new URL('./views/deliveryOrders.css', import.meta.url), 'utf8')
2026-09-01 14:24:00 +08:00
const createSource = readFileSync(new URL('./views/DeliveryOrderCreateView.vue', import.meta.url), 'utf8')
const permissionSource = readFileSync(new URL('../tenant-management/contracts.ts', import.meta.url), 'utf8')
async function importTypeScriptModule(source) {
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 },
}).outputText
return import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`)
}
test('uses only authenticated merchant delivery endpoints without customer scope or mock fallback', () => {
assert.match(apiSource, /method: 'GET'[\s\S]*url: '\/merchant\/v1\/last-leg\/delivery-orders'/)
assert.match(apiSource, /delivery-orders\/\$\{encodeURIComponent\(deliveryNo\)\}/)
assert.match(apiSource, /method: 'POST'[\s\S]*url: '\/merchant\/v1\/last-leg\/delivery-orders'/)
assert.match(apiSource, /const \{ dispatchNo, \.\.\.request \} = payload/)
assert.match(apiSource, /params: \{ dispatchNo \}/)
2026-09-29 17:56:03 +08:00
assert.match(apiSource, /apiRequest<DeliveryOrderDetailResponse>/)
2026-09-02 02:39:56 +08:00
assert.match(apiSource, /\.then\(\(\{ order, skus, fees \}\) => \(\{[\s\S]*\.\.\.order,[\s\S]*fees,[\s\S]*items: skus/)
2026-09-29 17:56:03 +08:00
assert.match(apiSource, /apiRequest<DeliveryTransferResult>/)
2026-09-01 14:24:00 +08:00
assert.doesNotMatch(apiSource, /customerId|tenantId|Record<string, unknown>|mock|fallback/i)
})
test('keeps list, read-only detail and create permissions independent in active routes', () => {
2026-09-19 17:02:11 +08:00
const listRoute = routesSource.match(/path: 'orders\/delivery',[\s\S]*?\n\s*},/)?.[0] ?? ''
assert.match(listRoute, /permission: 'merchant:warehouse:delivery:list'/)
assert.doesNotMatch(listRoute, /navigation:/)
2026-09-01 14:24:00 +08:00
assert.match(routesSource, /path: 'orders\/delivery\/create'[\s\S]*DeliveryOrderCreateView\.vue[\s\S]*permission: 'merchant:warehouse:delivery:create'/)
assert.match(routesSource, /path: 'orders\/delivery\/:deliveryNo'[\s\S]*DeliveryOrderDetailView\.vue[\s\S]*props: true[\s\S]*permission: 'merchant:warehouse:delivery:list'/)
assert.match(routesSource, /orders-delivery-create[\s\S]*keepAlive: false/)
assert.match(routesSource, /orders-delivery-detail[\s\S]*keepAlive: false/)
})
test('renders the operation column first and covers loading, empty, error and pagination states', () => {
2026-09-18 16:48:35 +08:00
const operationIndex = listSource.indexOf("label: '操作'")
const deliveryNumberIndex = listSource.indexOf("label: '派送单号'")
2026-09-01 14:24:00 +08:00
assert.ok(operationIndex >= 0 && operationIndex < deliveryNumberIndex)
2026-09-18 16:48:35 +08:00
assert.match(listSource, /:loading="loading"/)
2026-09-01 14:24:00 +08:00
assert.match(listSource, /empty-text="暂无派送单"/)
assert.match(listSource, /delivery-state--error/)
assert.match(listSource, /@click="loadOrders">重试/)
assert.match(listSource, /listDeliveryOrders\(buildQuery\(\)\)/)
2026-09-18 16:48:35 +08:00
assert.match(listSource, /<DataTable/)
assert.match(listSource, /v-model:page="page"/)
2026-09-03 20:20:29 +08:00
assert.match(listSource, /v-model:page-size="pageSize"/)
2026-09-18 16:48:35 +08:00
assert.match(listSource, /watch\(\[page, pageSize\]/)
2026-09-03 20:20:29 +08:00
assert.match(apiSource, /\.then\(normalizeDeliveryPage\)/)
assert.match(apiSource, /total: Number\(data\.total\)/)
2026-09-01 14:24:00 +08:00
assert.doesNotMatch(listSource, /row\.id[^a-zA-Z]|deliveryNo \|\| row\.id|customerId|tenantId/)
})
test('shows the create button only with the independent create permission', () => {
assert.match(listSource, /auth\.hasPermission\('merchant:warehouse:delivery:create'\)/)
assert.match(listSource, /<el-button v-if="canCreate" type="primary" @click="openCreate"/)
assert.doesNotMatch(listSource, /hasPermission\('merchant:warehouse:delivery:list'\)[\s\S]{0,120}创建派送单/)
})
test('loads a typed detail route by delivery number and never exposes an internal id fallback', () => {
assert.match(detailSource, /defineProps<\{ deliveryNo: string \}>/)
assert.match(detailSource, /getDeliveryOrder\(props\.deliveryNo\)/)
assert.match(detailSource, /派送单详情加载失败/)
2026-09-18 16:48:35 +08:00
for (const heading of ['订单概览', '运输与预约', '货物明细', '费用明细']) assert.match(detailSource, new RegExp(heading))
2026-09-01 14:24:00 +08:00
assert.doesNotMatch(detailSource, /detail\?\.id|deliveryNo \|\| detail\?\.id|customerId|tenantId/)
})
2026-09-18 16:48:35 +08:00
test('renders a compact customer-readable delivery detail surface', () => {
assert.match(detailSource, /<article class="delivery-detail-surface">/)
assert.match(detailSource, /<el-button link type="primary" @click="backToList"><ArrowLeft[^>]*\/>返回列表<\/el-button>/)
assert.match(detailSource, /class="client-status" :data-tone="statusTone\(detail\.status\)"/)
assert.match(detailSource, /TRUCK_PALLET: '卡派打托'/)
assert.match(detailSource, /WAREHOUSE_PICKUP: '仓库自提'/)
assert.match(detailSource, /PALLET: '托盘'/)
assert.match(detailSource, /TRUCK: '卡车'/)
assert.match(detailSource, /SELF: '自有账号'/)
assert.match(detailSource, /formatter: tableValueFormatter/)
assert.match(detailSource, /<DataTable :data="items" :columns="itemColumns"/)
assert.match(detailSource, /<DataTable :data="detail\.fees" :columns="feeColumns"/)
assert.doesNotMatch(detailSource, /<h2[^>]*>派送单详情<\/h2>|display\(detail\.status\)/)
assert.match(deliveryStyles, /delivery-detail-surface \.delivery-detail-grid div\{display:grid;min-height:42px;/)
})
2026-09-01 14:24:00 +08:00
test('submits the complete create contract with stable retry idempotency and recoverable failures', () => {
for (const model of ['dispatchNo', 'plannedDeliveryAt', 'deliveryMethod', 'pickupMethod', 'loadingType', 'waybillNo', 'carrierName', 'vehicleType', 'vehicleSize', 'driverName', 'driverPhone', 'vehiclePlate', 'appointmentAccount', 'sellerName', 'remark']) {
assert.match(createSource, new RegExp(`v-model="form\\.${model}"`))
}
assert.match(createSource, /if \(!pendingIdempotencyKey\) pendingIdempotencyKey = createIdempotencyKey\(\)/)
2026-09-01 14:24:00 +08:00
assert.match(createSource, /createDeliveryOrder\(requestPayload\(\)\)/)
assert.match(createSource, /fees: form\.fees\.map/)
2026-09-02 02:39:56 +08:00
assert.match(createSource, /if \(form\.fees\.length === 0\)/)
assert.match(createSource, /请至少添加一条费用明细/)
for (const value of ['FBA_PICKUP', 'CUSTOMER_PICKUP', 'TRUCK_PALLET', 'TRUCK_FLOOR', 'EXPRESS', 'OUTSOURCED', 'DEDICATED_TRUCK', 'LTL']) {
assert.match(createSource, new RegExp(`value="${value}"`))
}
for (const value of ['ONE_PICKUP_ONE_DROP', 'ONE_PICKUP_MULTI_DROP', 'MULTI_PICKUP_ONE_DROP', 'MULTI_PICKUP_MULTI_DROP', 'WAREHOUSE_PICKUP', 'PALLET', 'FLOOR', 'MIXED']) {
assert.match(createSource, new RegExp(`value="${value}"`))
}
assert.doesNotMatch(createSource, /value="(?:TRUCK|COURIER|SELF_PICKUP|DOOR_PICKUP|LOOSE_CARTON)"/)
2026-09-01 14:24:00 +08:00
assert.match(createSource, /submitError\.value = error instanceof Error/)
assert.match(createSource, /useUnsavedWorkRegistration/)
assert.doesNotMatch(createSource, /customerId|tenantId|mock|fallback/i)
})
test('maps both merchant delivery permissions independently', async () => {
assert.match(permissionSource, /'merchant:warehouse:delivery:list': \['merchant:warehouse:delivery:list'\]/)
assert.match(permissionSource, /'merchant:warehouse:delivery:create': \['merchant:warehouse:delivery:create'\]/)
const contracts = await importTypeScriptModule(permissionSource)
assert.equal(contracts.hasMappedMerchantPermission('merchant:warehouse:delivery:list', new Set(['merchant:warehouse:delivery:list'])), true)
assert.equal(contracts.hasMappedMerchantPermission('merchant:warehouse:delivery:list', new Set(['merchant:warehouse:delivery:create'])), false)
assert.equal(contracts.hasMappedMerchantPermission('merchant:warehouse:delivery:create', new Set(['merchant:warehouse:delivery:create'])), true)
assert.equal(contracts.hasMappedMerchantPermission('merchant:warehouse:delivery:create', new Set(['merchant:warehouse:delivery:list'])), false)
})