103 lines
7.0 KiB
JavaScript
103 lines
7.0 KiB
JavaScript
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')
|
|
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 \}/)
|
|
assert.match(apiSource, /overseasApiRequest<DeliveryOrderDetailResponse>/)
|
|
assert.match(apiSource, /\.then\(\(\{ order, skus, fees \}\) => \(\{[\s\S]*\.\.\.order,[\s\S]*fees,[\s\S]*items: skus/)
|
|
assert.match(apiSource, /overseasApiRequest<DeliveryTransferResult>/)
|
|
assert.doesNotMatch(apiSource, /customerId|tenantId|Record<string, unknown>|mock|fallback/i)
|
|
})
|
|
|
|
test('keeps list, read-only detail and create permissions independent in active routes', () => {
|
|
assert.match(routesSource, /path: 'orders\/delivery'[\s\S]*permission: 'merchant:warehouse:delivery:list'[\s\S]*navigation:/)
|
|
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', () => {
|
|
const operationIndex = listSource.indexOf('label="操作"')
|
|
const deliveryNumberIndex = listSource.indexOf('label="派送单号"')
|
|
assert.ok(operationIndex >= 0 && operationIndex < deliveryNumberIndex)
|
|
assert.match(listSource, /v-loading="loading"/)
|
|
assert.match(listSource, /empty-text="暂无派送单"/)
|
|
assert.match(listSource, /delivery-state--error/)
|
|
assert.match(listSource, /@click="loadOrders">重试/)
|
|
assert.match(listSource, /listDeliveryOrders\(buildQuery\(\)\)/)
|
|
assert.match(listSource, /<el-pagination/)
|
|
assert.match(listSource, /v-model:current-page="page"/)
|
|
assert.match(listSource, /v-model:page-size="pageSize"/)
|
|
assert.match(listSource, /@current-change="changePage"/)
|
|
assert.match(listSource, /@size-change="changePageSize"/)
|
|
assert.match(apiSource, /\.then\(normalizeDeliveryPage\)/)
|
|
assert.match(apiSource, /total: Number\(data\.total\)/)
|
|
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, /派送单详情加载失败/)
|
|
for (const heading of ['订单信息', '运输与预约', '货物明细', '费用明细']) assert.match(detailSource, new RegExp(heading))
|
|
assert.doesNotMatch(detailSource, /detail\?\.id|deliveryNo \|\| detail\?\.id|customerId|tenantId/)
|
|
})
|
|
|
|
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 = crypto\.randomUUID\(\)/)
|
|
assert.match(createSource, /createDeliveryOrder\(requestPayload\(\)\)/)
|
|
assert.match(createSource, /fees: form\.fees\.map/)
|
|
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)"/)
|
|
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)
|
|
})
|