This commit is contained in:
2026-10-19 17:56:27 +08:00
parent 513bbe2bb0
commit de31c14695
8 changed files with 69 additions and 322 deletions
@@ -1,5 +1,5 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import test from 'node:test'
import ts from 'typescript'
@@ -8,7 +8,6 @@ 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 deliveryStyles = readFileSync(new URL('./views/deliveryOrders.css', 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) {
@@ -33,22 +32,18 @@ for (const signedPackages of [null, 0, 3]) test(`preserves actual signed quantit
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.doesNotMatch(apiSource, /method: '(?:POST|PUT|PATCH|DELETE)'|createDeliveryOrder|DeliveryTransferPayload|DeliveryTransferResult/)
assert.match(apiSource, /apiRequest<DeliveryOrderDetailResponse>/)
assert.match(apiSource, /\.then\(\(\{ order, skus, fees \}\) => \(\{[\s\S]*\.\.\.order,[\s\S]*fees,[\s\S]*items: skus/)
assert.match(apiSource, /apiRequest<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', () => {
test('keeps customer delivery list and read-only detail active without a creation route', () => {
const listRoute = routesSource.match(/path: 'orders\/delivery',[\s\S]*?\n\s*},/)?.[0] ?? ''
assert.match(listRoute, /permission: 'merchant:warehouse:delivery:list'/)
assert.match(listRoute, /navigation: \{ key: 'orders-delivery', icon: Truck \}/)
assert.match(routesSource, /path: 'orders\/delivery\/create'[\s\S]*DeliveryOrderCreateView\.vue[\s\S]*permission: 'merchant:warehouse:delivery:create'/)
assert.doesNotMatch(routesSource, /orders\/delivery\/create|orders-delivery-create|DeliveryOrderCreateView/)
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/)
})
@@ -70,10 +65,9 @@ test('renders the operation column first and covers loading, empty, error and pa
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('does not expose customer delivery creation even to an account retaining an old permission snapshot', () => {
assert.doesNotMatch(listSource, /delivery:create|canCreate|openCreate|orders-delivery-create|创建派送单/)
assert.equal(existsSync(new URL('./views/DeliveryOrderCreateView.vue', import.meta.url)), false)
})
test('loads a typed detail route by delivery number and never exposes an internal id fallback', () => {
@@ -126,33 +120,12 @@ test('renders a compact customer-readable delivery detail surface', () => {
assert.match(deliveryStyles, /delivery-detail-surface \.delivery-detail-grid div\{display:grid;min-height:42px;/)
})
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\(\)/)
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 () => {
test('keeps merchant delivery reads and removes creation from the advertised permission map', async () => {
assert.match(permissionSource, /'merchant:warehouse:delivery:list': \['merchant:warehouse:delivery:list'\]/)
assert.match(permissionSource, /'merchant:warehouse:delivery:create': \['merchant:warehouse:delivery:create'\]/)
assert.doesNotMatch(permissionSource, /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(Object.hasOwn(contracts.FRONTEND_TO_MERCHANT_PERMISSION_CODES, 'merchant:warehouse:delivery:create'), false)
assert.equal(contracts.hasMappedMerchantPermission('merchant:warehouse:delivery:create', new Set(['merchant:warehouse:delivery:list'])), false)
})