优化
This commit is contained in:
@@ -108,6 +108,11 @@ try {
|
||||
await orderMutationEvidenceModule()
|
||||
await orderExcelImportModule()
|
||||
await orderSubmissionChannelGateModule()
|
||||
await merchantFinanceBillsModule()
|
||||
await merchantOverseasWarehouseModule()
|
||||
await merchantCrossBorderModule()
|
||||
await merchantWarehouseOrdersModule()
|
||||
await merchantPricingCustomsModule()
|
||||
await notificationInboxModule()
|
||||
await accountSecurityModule()
|
||||
report.success = report.steps.every((step) => step.passed)
|
||||
@@ -1490,6 +1495,520 @@ async function isolationModule() {
|
||||
}, ({ json }) => ({ qualificationId: report.resources.businessLicenseQualificationId, rejectionCode: json?.code }))
|
||||
}
|
||||
|
||||
async function merchantFinanceBillsModule() {
|
||||
const module = 'merchant-bills'
|
||||
const base = '/api/client/finance/v1'
|
||||
const customerId = String(report.resources.tenantId)
|
||||
const requireArray = (json, label) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data), `${label} must be an array`)
|
||||
return json.data
|
||||
}
|
||||
const bills = await apiStep(module, 'customer bills ignore caller-supplied customer scope', {
|
||||
path: `${base}/bills?customerId=0&tenantId=0`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
const rows = requireArray(json, 'bills')
|
||||
for (const row of rows) {
|
||||
assertEqual(String(row.customerId), customerId, 'bill owner must be the authenticated customer')
|
||||
assertPresent(row.billNo, 'billNo')
|
||||
assertPresent(row.customerName, 'customerName')
|
||||
assertTrue(['ISSUED', 'PARTIALLY_PAID', 'PAID'].includes(row.status), `unexpected visible bill status ${row.status}`)
|
||||
assertTrue(Number.isSafeInteger(Number(row.version)), 'bill version required')
|
||||
assertTrue(['NOT_REQUIRED', 'PENDING_CONFIRMATION', 'CONFIRMED'].includes(row.customerConfirmationStatus),
|
||||
'customer confirmation status required')
|
||||
}
|
||||
}, ({ json }) => ({ customerId, count: json.data?.length ?? null, scope: 'authenticated-enterprise-only' }))
|
||||
const statements = await apiStep(module, 'customer statements expose the same visible bills', {
|
||||
path: `${base}/statements`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
const rows = requireArray(json, 'statements')
|
||||
assertEqual(rows.length, bills.json.data.length, 'bills and statements must expose the same visible set')
|
||||
for (const row of rows) assertEqual(String(row.customerId), customerId, 'statement owner must be the authenticated customer')
|
||||
}, ({ json }) => ({ count: json.data?.length ?? null }))
|
||||
await apiStep(module, 'customer receipts are confirmed-only and customer-scoped', {
|
||||
path: `${base}/receipts`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
const rows = requireArray(json, 'receipts')
|
||||
for (const row of rows) {
|
||||
assertPresent(row.receiptNo, 'receiptNo')
|
||||
assertTrue(Number.isFinite(Number(row.receivedAmount)), 'receipt amount required')
|
||||
assertEqual(row.status, 'CONFIRMED', 'only confirmed receipts are customer-visible')
|
||||
}
|
||||
}, ({ json }) => ({ count: json.data?.length ?? null }))
|
||||
await apiStep(module, 'customer receivable settlements are readable', {
|
||||
path: `${base}/receivable-settlements`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
const rows = requireArray(json, 'receivable settlements')
|
||||
for (const row of rows) {
|
||||
assertPresent(row.settlementNo, 'settlementNo')
|
||||
assertEqual(String(row.customerId), customerId, 'settlement owner must be the authenticated customer')
|
||||
assertTrue(Number.isFinite(Number(row.receivableAmount)), 'receivable amount required')
|
||||
}
|
||||
}, ({ json }) => ({ count: json.data?.length ?? null }))
|
||||
await apiStep(module, 'unknown bill id is rejected without cross-customer disclosure', {
|
||||
path: `${base}/bills/999999999999999`, token: merchantToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', `unknown bill must not succeed, code=${json?.code}`)
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
await apiStep(module, 'unknown statement confirmation is rejected with idempotency key', {
|
||||
method: 'POST', path: `${base}/statements/999999999999999/confirm`, token: merchantToken,
|
||||
headers: { 'Idempotency-Key': newIdempotencyKey('bill-confirm-negative') },
|
||||
body: { expectedVersion: 1 },
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', `unknown statement must not confirm, code=${json?.code}`)
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
if (existingClientToken) {
|
||||
await apiStep(module, 'cross-customer bill detail stays outside the other enterprise', {
|
||||
path: `${base}/bills/999999999999999`, token: existingClientToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', 'another enterprise bill must not be readable')
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
} else {
|
||||
recordSkipped(module, 'cross-customer bill detail stays outside the other enterprise',
|
||||
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
|
||||
}
|
||||
report.merchantFinanceBills = {
|
||||
customerId,
|
||||
bills: bills.json.data.length,
|
||||
statements: statements.json.data.length,
|
||||
scope: 'read-only; statement confirmation with real issued bills is covered when Admin finance chain runs first',
|
||||
}
|
||||
}
|
||||
|
||||
async function merchantOverseasWarehouseModule() {
|
||||
const module = 'merchant-overseas-warehouse'
|
||||
const base = '/api/merchant/v1/last-leg'
|
||||
const page = 'page=1&size=20'
|
||||
const listStep = async (label, path, validateRow) => {
|
||||
const response = await apiStep(module, label, { path, token: merchantToken }, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records), `${label} records must be an array`)
|
||||
assertTrue(Number.isFinite(Number(json.data?.total)), `${label} total required`)
|
||||
for (const row of json.data.records) validateRow?.(row)
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
|
||||
return response.json.data
|
||||
}
|
||||
|
||||
const warehouseResponse = await apiStep(module, 'customer reads active overseas warehouses', {
|
||||
path: '/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=OVERSEAS', token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records) && json.data.records.length > 0, 'at least one active overseas warehouse required')
|
||||
assertTrue(json.data.records.every((row) => row.status === 'ACTIVE' && row.warehouseType === 'OVERSEAS'),
|
||||
'customer warehouse list must contain only active overseas warehouses')
|
||||
}, ({ json }) => ({ count: json.data?.records?.length ?? null }))
|
||||
const warehouse = warehouseResponse.json.data.records[0]
|
||||
|
||||
await listStep('return forecasts are customer-scoped', `${base}/return-orders?${page}`, (row) => {
|
||||
assertPresent(row.returnNo, 'returnNo')
|
||||
assertPresent(row.status, 'return status')
|
||||
})
|
||||
await listStep('relabel applications are customer-scoped', `${base}/relabel-applications?${page}`, (row) => {
|
||||
assertPresent(row.relabelNo, 'relabelNo')
|
||||
assertTrue(Number.isFinite(Number(row.totalPackageCount)), 'relabel package count required')
|
||||
})
|
||||
await listStep('warehouse transfers are customer-scoped', `${base}/warehouse-transfers?${page}`, (row) => {
|
||||
assertPresent(row.transferNo, 'transferNo')
|
||||
assertPresent(row.status, 'transfer status')
|
||||
})
|
||||
await listStep('cargo discards are customer-scoped', `${base}/cargo-discards?${page}`, (row) => {
|
||||
assertPresent(row.discardNo, 'discardNo')
|
||||
assertTrue(Number.isFinite(Number(row.totalPackages)), 'discard package count required')
|
||||
})
|
||||
await listStep('return inventory is customer-scoped', `${base}/return-inventory?${page}`, (row) => {
|
||||
assertPresent(row.cartonMark, 'cartonMark')
|
||||
assertTrue(Number.isFinite(Number(row.quantity)), 'return inventory quantity required')
|
||||
})
|
||||
await listStep('return inventory summary is customer-scoped', `${base}/return-inventory/summary?${page}`, (row) => {
|
||||
assertPresent(row.skuCode, 'summary skuCode')
|
||||
assertTrue(Number.isFinite(Number(row.availableQuantity)), 'available quantity required')
|
||||
})
|
||||
await listStep('return cartons are customer-scoped', `${base}/return-inventory/cartons?${page}`, (row) => {
|
||||
assertPresent(row.cartonMark, 'cartonMark')
|
||||
assertPresent(row.status, 'carton status')
|
||||
})
|
||||
|
||||
const suffix = marker.replace(/[^0-9A-Za-z]/g, '').slice(-20)
|
||||
const sku = `ACCEPT-${suffix}`
|
||||
const createPayload = {
|
||||
overseasWarehouseId: String(warehouse.id),
|
||||
warehouseContactName: '验收联系人',
|
||||
warehouseContactPhone: '13800000000',
|
||||
customerReferenceNo: `RET-${suffix}`,
|
||||
needRelabel: false,
|
||||
remark: `${marker} client overseas self-service acceptance`,
|
||||
idempotencyKey: newIdempotencyKey('return-order'),
|
||||
items: [{ skuCode: sku, productNameZh: '验收测试货物', expectedQuantity: 1 }],
|
||||
}
|
||||
const created = await apiStep(module, 'create return forecast with marker-owned payload', {
|
||||
method: 'POST', path: `${base}/return-orders`, token: merchantToken, body: createPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.order?.returnNo, 'returnNo')
|
||||
assertEqual(String(json.data.order.overseasWarehouseId), String(warehouse.id), 'return order warehouse')
|
||||
assertTrue(Array.isArray(json.data.items) && json.data.items.length === 1
|
||||
&& json.data.items[0].skuCode === sku && Number(json.data.items[0].expectedQuantity) === 1,
|
||||
'return order items echo the marker payload')
|
||||
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null, status: json.data?.order?.status ?? null }))
|
||||
const returnNo = created.json.data.order.returnNo
|
||||
report.resources.returnOrderNos = [returnNo]
|
||||
|
||||
await apiStep(module, 'same idempotency key replays the same return order', {
|
||||
method: 'POST', path: `${base}/return-orders`, token: merchantToken, body: createPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.order?.returnNo, returnNo, 'replayed returnNo must match the original')
|
||||
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null }))
|
||||
|
||||
await apiStep(module, 'return order detail keeps the marker payload', {
|
||||
path: `${base}/return-orders/${encodeURIComponent(returnNo)}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.order?.returnNo, returnNo, 'detail returnNo')
|
||||
assertTrue(Array.isArray(json.data.items) && json.data.items.some((item) => item.skuCode === sku),
|
||||
'detail keeps the submitted sku')
|
||||
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null, items: json.data?.items?.length ?? null }))
|
||||
|
||||
await apiStep(module, 'return order appears in the customer list', {
|
||||
path: `${base}/return-orders?${page}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(json.data.records.some((row) => String(row.returnNo) === String(returnNo)),
|
||||
'new return order must appear in the first customer page')
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, returnNo }))
|
||||
|
||||
await apiStep(module, 'unknown return order is rejected', {
|
||||
path: `${base}/return-orders/${encodeURIComponent(`RET-MISSING-${suffix}`)}`, token: merchantToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', `unknown return order must not succeed, code=${json?.code}`)
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
|
||||
if (existingClientToken) {
|
||||
await apiStep(module, 'cross-customer return order is rejected', {
|
||||
path: `${base}/return-orders/${encodeURIComponent(returnNo)}`, token: existingClientToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', 'another enterprise return order must not be readable')
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
} else {
|
||||
recordSkipped(module, 'cross-customer return order is rejected',
|
||||
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
|
||||
}
|
||||
|
||||
report.overseasWarehouseReadOnlyGaps = [
|
||||
'Relabel, transfer and cargo-discard creation require inventory produced by the platform overseas PDA chain; this run only covers their customer-scoped lists and the return forecast write chain.',
|
||||
'Return relabel creation, completion confirmation and document downloads require received return cartons produced by the PDA return receipt chain.',
|
||||
]
|
||||
}
|
||||
|
||||
async function merchantCrossBorderModule() {
|
||||
const module = 'merchant-cross-border'
|
||||
const truckPath = '/api/merchant/v1/last-leg/us-truck-orders'
|
||||
const looseboardPath = '/api/merchant/v1/last-leg/local-looseboard-orders'
|
||||
const procurementPath = '/api/merchant/v1/last-leg/overseas-procurements'
|
||||
const page = 'page=1&size=20'
|
||||
const suffix = marker.replace(/[^0-9A-Za-z]/g, '').slice(-18)
|
||||
const localTime = (days) => {
|
||||
const date = new Date(Date.now() + days * 86_400_000)
|
||||
return date.toISOString().slice(0, 19)
|
||||
}
|
||||
const listStep = async (label, path, validateRow) => apiStep(module, label, { path, token: merchantToken }, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records), `${label} records must be an array`)
|
||||
assertTrue(Number.isFinite(Number(json.data?.total)), `${label} total required`)
|
||||
for (const row of json.data.records) validateRow?.(row)
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
|
||||
|
||||
await listStep('customer procurement orders are customer-scoped', `${procurementPath}?${page}`, (row) => {
|
||||
assertPresent(row.orderNo, 'procurement orderNo')
|
||||
assertPresent(row.status, 'procurement status')
|
||||
})
|
||||
await listStep('customer US truck orders are customer-scoped', `${truckPath}?${page}`, (row) => {
|
||||
assertPresent(row.orderNo, 'truck orderNo')
|
||||
assertPresent(row.status, 'truck status')
|
||||
})
|
||||
await listStep('customer local looseboard orders are customer-scoped', `${looseboardPath}?${page}`, (row) => {
|
||||
assertPresent(row.orderNo, 'looseboard orderNo')
|
||||
assertPresent(row.status, 'looseboard status')
|
||||
})
|
||||
|
||||
const truckPayload = {
|
||||
vehicleType: 'PALLET_15',
|
||||
origin: `验收起点-${suffix}`,
|
||||
destination: `验收终点-${suffix}`,
|
||||
packageCount: 2,
|
||||
totalWeightKg: 12.5,
|
||||
totalVolumeCbm: 0.8,
|
||||
budgetAmount: 100,
|
||||
currency: 'USD',
|
||||
originContactName: '验收发货联系人',
|
||||
originContactPhone: '13800000001',
|
||||
destinationContactName: '验收收货联系人',
|
||||
destinationContactPhone: '13800000002',
|
||||
pickupAt: localTime(2),
|
||||
deliveryAt: localTime(3),
|
||||
remark: `${marker} client US truck acceptance`,
|
||||
idempotencyKey: newIdempotencyKey('us-truck'),
|
||||
}
|
||||
const truck = await apiStep(module, 'customer creates a US truck order', {
|
||||
method: 'POST', path: truckPath, token: merchantToken, body: truckPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.summary?.orderNo, 'truck orderNo')
|
||||
assertTrue(['PENDING_REVIEW', 'SUBMITTED', 'DRAFT'].includes(json.data.summary.status),
|
||||
`unexpected truck status ${json.data.summary.status}`)
|
||||
assertTrue(Number(json.data.summary.version) >= 0, 'truck version required')
|
||||
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null, status: json.data?.summary?.status ?? null }))
|
||||
const truckNo = truck.json.data.summary.orderNo
|
||||
report.resources.crossBorderOrderNos = [truckNo]
|
||||
|
||||
await apiStep(module, 'same idempotency key replays the same US truck order', {
|
||||
method: 'POST', path: truckPath, token: merchantToken, body: truckPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.summary?.orderNo, truckNo, 'replayed truck orderNo')
|
||||
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
|
||||
|
||||
await apiStep(module, 'US truck order detail keeps the marker payload', {
|
||||
path: `${truckPath}/${encodeURIComponent(truckNo)}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.summary?.orderNo, truckNo, 'truck detail orderNo')
|
||||
assertEqual(json.data?.summary?.vehicleType, 'PALLET_15', 'truck vehicle type')
|
||||
assertEqual(Number(json.data?.summary?.packageCount), 2, 'truck package count')
|
||||
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
|
||||
|
||||
const looseboardPayload = {
|
||||
origin: `散板起点-${suffix}`,
|
||||
destination: `散板终点-${suffix}`,
|
||||
productName: '验收散板货物',
|
||||
estimatedPalletCount: 1,
|
||||
packageCount: 3,
|
||||
totalWeightKg: 20,
|
||||
totalVolumeCbm: 1.2,
|
||||
budgetAmount: 80,
|
||||
currency: 'USD',
|
||||
originContactName: '验收发货联系人',
|
||||
originContactPhone: '13800000001',
|
||||
destinationContactName: '验收收货联系人',
|
||||
destinationContactPhone: '13800000002',
|
||||
pickupAt: localTime(2),
|
||||
deliveryAt: localTime(4),
|
||||
remark: `${marker} client looseboard acceptance`,
|
||||
idempotencyKey: newIdempotencyKey('looseboard'),
|
||||
}
|
||||
const looseboard = await apiStep(module, 'customer creates a local looseboard order', {
|
||||
method: 'POST', path: looseboardPath, token: merchantToken, body: looseboardPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.summary?.orderNo, 'looseboard orderNo')
|
||||
assertTrue(Number(json.data.summary.estimatedPalletCount) === 1, 'looseboard pallet count')
|
||||
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null, status: json.data?.summary?.status ?? null }))
|
||||
const looseboardNo = looseboard.json.data.summary.orderNo
|
||||
report.resources.crossBorderOrderNos.push(looseboardNo)
|
||||
|
||||
await apiStep(module, 'same idempotency key replays the same looseboard order', {
|
||||
method: 'POST', path: looseboardPath, token: merchantToken, body: looseboardPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.summary?.orderNo, looseboardNo, 'replayed looseboard orderNo')
|
||||
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
|
||||
|
||||
const procurementPayload = {
|
||||
customerName: companyName,
|
||||
purchaseCategory: '验收测试',
|
||||
productName: `验收采购样品-${suffix}`,
|
||||
productSpecification: '1 件/箱',
|
||||
purchaseQuantity: 1,
|
||||
budgetUnitPrice: 10,
|
||||
currency: 'CNY',
|
||||
receivingAddress: '广东省深圳市南山区验收路 1 号',
|
||||
contactName: '验收采购联系人',
|
||||
contactPhone: '13800000003',
|
||||
remark: `${marker} client procurement acceptance`,
|
||||
idempotencyKey: newIdempotencyKey('procurement'),
|
||||
}
|
||||
const procurement = await apiStep(module, 'customer creates an overseas procurement order', {
|
||||
method: 'POST', path: procurementPath, token: merchantToken, body: procurementPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.order?.orderNo, 'procurement orderNo')
|
||||
assertTrue(Number(json.data.order.purchaseQuantity) === 1, 'procurement quantity')
|
||||
}, ({ json }) => ({ orderNo: json.data?.order?.orderNo ?? null, status: json.data?.order?.status ?? null }))
|
||||
const procurementNo = procurement.json.data.order.orderNo
|
||||
report.resources.crossBorderOrderNos.push(procurementNo)
|
||||
|
||||
await apiStep(module, 'same idempotency key replays the same procurement order', {
|
||||
method: 'POST', path: procurementPath, token: merchantToken, body: procurementPayload,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.order?.orderNo, procurementNo, 'replayed procurement orderNo')
|
||||
}, ({ json }) => ({ orderNo: json.data?.order?.orderNo ?? null }))
|
||||
|
||||
await apiStep(module, 'created cross-border orders appear in customer lists', {
|
||||
path: `${truckPath}?${page}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(json.data.records.some((row) => String(row.orderNo) === String(truckNo)),
|
||||
'new truck order must appear in the customer list')
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, truckNo }))
|
||||
for (const [label, path] of [['truck', truckPath], ['looseboard', looseboardPath], ['procurement', procurementPath]]) {
|
||||
await apiStep(module, `unknown ${label} order is rejected`, {
|
||||
path: `${path}/${encodeURIComponent(`CB-MISSING-${suffix}`)}`, token: merchantToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', `unknown ${label} order must not succeed, code=${json?.code}`)
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
}
|
||||
|
||||
if (existingClientToken) {
|
||||
await apiStep(module, 'cross-customer truck order is rejected', {
|
||||
path: `${truckPath}/${encodeURIComponent(truckNo)}`, token: existingClientToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', 'another enterprise truck order must not be readable')
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
} else {
|
||||
recordSkipped(module, 'cross-customer truck order is rejected',
|
||||
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
|
||||
}
|
||||
|
||||
report.crossBorderLimitations = [
|
||||
'Customer creation stops at the submitted/pending platform review state; platform review, pricing, purchase execution, receipts and delivery are covered by Admin/fleet specialty entries.',
|
||||
]
|
||||
}
|
||||
|
||||
async function merchantWarehouseOrdersModule() {
|
||||
const module = 'merchant-warehouse-orders'
|
||||
const base = '/api/merchant/v1/last-leg'
|
||||
const page = 'page=1&size=20'
|
||||
const inbound = await apiStep(module, 'customer inbound plans are customer-scoped', {
|
||||
path: `${base}/inbound-plans?${page}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records) && Number.isFinite(Number(json.data?.total)),
|
||||
'inbound plan page required')
|
||||
for (const row of json.data.records) {
|
||||
assertPresent(row.planNo, 'planNo')
|
||||
assertPresent(row.inboundCode, 'inboundCode')
|
||||
assertTrue(Number.isFinite(Number(row.packageCount)), 'inbound packageCount')
|
||||
assertPresent(row.status, 'inbound status')
|
||||
}
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
|
||||
|
||||
const delivery = await apiStep(module, 'customer delivery orders are customer-scoped', {
|
||||
path: `${base}/delivery-orders?${page}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records) && Number.isFinite(Number(json.data?.total)),
|
||||
'delivery page required')
|
||||
for (const row of json.data.records) {
|
||||
assertPresent(row.deliveryNo, 'deliveryNo')
|
||||
assertPresent(row.dispatchNo, 'dispatchNo')
|
||||
assertPresent(row.status, 'delivery status')
|
||||
assertTrue(Number.isFinite(Number(row.packageCount)), 'delivery packageCount')
|
||||
}
|
||||
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
|
||||
|
||||
const planNo = inbound.json.data.records[0]?.planNo
|
||||
if (planNo) {
|
||||
await apiStep(module, 'inbound plan detail keeps plan identity and lines', {
|
||||
path: `${base}/inbound-plans/${encodeURIComponent(planNo)}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.planNo, planNo, 'planNo')
|
||||
assertPresent(json.data?.status, 'plan status')
|
||||
assertTrue(Array.isArray(json.data?.lines) && Array.isArray(json.data?.pallets), 'plan lines and pallets required')
|
||||
}, ({ json }) => ({ planNo: json.data?.planNo ?? null, lines: json.data?.lines?.length ?? null }))
|
||||
} else {
|
||||
recordSkipped(module, 'inbound plan detail keeps plan identity and lines',
|
||||
'this run customer has no inbound plan yet')
|
||||
}
|
||||
|
||||
const deliveryNo = delivery.json.data.records[0]?.deliveryNo
|
||||
if (deliveryNo) {
|
||||
await apiStep(module, 'delivery order detail keeps customer-visible fees and skus', {
|
||||
path: `${base}/delivery-orders/${encodeURIComponent(deliveryNo)}?page=1&size=200`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.order?.deliveryNo, deliveryNo, 'deliveryNo')
|
||||
assertTrue(Array.isArray(json.data?.skus) && Array.isArray(json.data?.fees), 'delivery skus and fees required')
|
||||
assertPresent(json.data?.order?.status, 'delivery status')
|
||||
}, ({ json }) => ({ deliveryNo: json.data?.order?.deliveryNo ?? null, skus: json.data?.skus?.length ?? null }))
|
||||
} else {
|
||||
recordSkipped(module, 'delivery order detail keeps customer-visible fees and skus',
|
||||
'this run customer has no delivery order yet')
|
||||
}
|
||||
|
||||
for (const [label, path] of [['inbound plan', `${base}/inbound-plans`], ['delivery order', `${base}/delivery-orders`]]) {
|
||||
await apiStep(module, `unknown ${label} is rejected`, {
|
||||
path: `${path}/${encodeURIComponent(`WO-MISSING-${marker}`)}`, token: merchantToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
assertTrue(String(json?.code) !== '200', `unknown ${label} must not succeed, code=${json?.code}`)
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
|
||||
}
|
||||
}
|
||||
|
||||
async function merchantPricingCustomsModule() {
|
||||
const module = 'merchant-pricing-customs'
|
||||
const warehouseResponse = await apiStep(module, 'customer reads active warehouses for pricing', {
|
||||
path: '/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE', token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data?.records) && json.data.records.length > 0, 'active warehouse required')
|
||||
assertTrue(json.data.records.every((row) => row.status === 'ACTIVE'), 'only active warehouses allowed')
|
||||
}, ({ json }) => ({ count: json.data?.records?.length ?? null }))
|
||||
const domestic = warehouseResponse.json.data.records.find((row) => row.warehouseType !== 'OVERSEAS')
|
||||
if (domestic) {
|
||||
await apiStep(module, 'customer reads active pricing channels', {
|
||||
path: `/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${encodeURIComponent(String(domestic.id))}`,
|
||||
token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Array.isArray(json.data), 'pricing channels must be an array')
|
||||
for (const row of json.data) {
|
||||
assertPresent(row.channelCode, 'channelCode')
|
||||
assertPresent(row.priceModel, 'priceModel')
|
||||
assertTrue(Array.isArray(row.pricingTaxModes), 'pricingTaxModes required')
|
||||
assertTrue(Number.isFinite(Number(row.priceBookVersionId)), 'priceBookVersionId required')
|
||||
}
|
||||
}, ({ json }) => ({ count: json.data?.length ?? null, warehouseId: String(domestic.id) }))
|
||||
} else {
|
||||
recordSkipped(module, 'customer reads active pricing channels',
|
||||
'this customer has no domestic origin warehouse')
|
||||
}
|
||||
|
||||
await apiStep(module, 'customer customs pending summary is readable', {
|
||||
path: '/api/merchant/v1/customs/pending-summary', token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Number.isFinite(Number(json.data?.pendingCount)), 'pendingCount required')
|
||||
assertTrue(Number(json.data.pendingCount) >= 0, 'pendingCount must be non-negative')
|
||||
}, ({ json }) => ({ pendingCount: json.data?.pendingCount ?? null }))
|
||||
|
||||
const orderId = report.resources.orderIds[0]
|
||||
if (orderId) {
|
||||
await apiStep(module, 'order customs documents stay customer-scoped', {
|
||||
path: `/api/merchant/v1/customs/orders/${encodeURIComponent(String(orderId))}`, token: merchantToken,
|
||||
}, ({ json, status }) => {
|
||||
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
|
||||
if (String(json?.code) === '200') {
|
||||
assertTrue(json.data && typeof json.data === 'object', 'customs payload required')
|
||||
} else {
|
||||
assertTrue(['A0403', 'A0404'].includes(String(json?.code)),
|
||||
`unexpected customs code ${json?.code}`)
|
||||
}
|
||||
}, ({ json, status }) => ({ http: status, code: json?.code ?? null, orderId: String(orderId) }))
|
||||
} else {
|
||||
recordSkipped(module, 'order customs documents stay customer-scoped', 'no current-run order id')
|
||||
}
|
||||
}
|
||||
|
||||
async function notificationInboxModule() {
|
||||
const module = 'notification-inbox'
|
||||
const templateBase = '/api/platform/v1/notification-templates'
|
||||
|
||||
Reference in New Issue
Block a user