feat(fleet): enforce reviewed trips and original fee coverage for dispatch
This commit is contained in:
@@ -9,7 +9,7 @@ const domesticSchemas = ['baiyue_auth', 'baiyue_iam', 'baiyue_order', 'baiyue_wa
|
||||
const overseasSchemas = ['baiyue_lastleg', 'baiyue_lastlag_warehouse']
|
||||
|
||||
test('两个入口分别只操作八个主业务库与两个仓储业务库,包括限定名引用', () => {
|
||||
for (const [sql, allowed, other, count] of [[cn, domesticSchemas, overseasSchemas, 223], [overseas, overseasSchemas, domesticSchemas, 136]]) {
|
||||
for (const [sql, allowed, other, count] of [[cn, domesticSchemas, overseasSchemas, 223], [overseas, overseasSchemas, domesticSchemas, 145]]) {
|
||||
assert.deepEqual([...sql.matchAll(/^CREATE DATABASE IF NOT EXISTS (\w+)/gm)].map(m => m[1]), allowed)
|
||||
assert.deepEqual([...new Set([...sql.matchAll(/^USE (\w+);/gm)].map(m => m[1]))], allowed)
|
||||
const body = sql.replace(/^--.*$/gm, '')
|
||||
@@ -28,7 +28,7 @@ test('纯初始化禁止迁移、数据回填、清理、重复建表和提前
|
||||
assert.doesNotMatch(body.replace(/^CREATE TRIGGER [\s\S]*?\$\$/gm, ''), /^\s*(?:UPDATE|DELETE FROM|REPLACE INTO)\b/gmi)
|
||||
assert.doesNotMatch(body, /CREATE TABLE IF NOT EXISTS/i)
|
||||
assert.equal((body.match(/AS initialization_status/g) ?? []).length, 1)
|
||||
assert.match(body.trim(), /SELECT 'PASS' AS initialization_status, '(CN|OVERSEAS)' AS region, (223|136) AS initialized_tables;$/)
|
||||
assert.match(body.trim(), /SELECT 'PASS' AS initialization_status, '(CN|OVERSEAS)' AS region, (223|145) AS initialized_tables;$/)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -56,7 +56,7 @@ test('平台管理员由外部bcrypt引导,强制首登改密且不创建客
|
||||
})
|
||||
|
||||
test('必要系统种子完整保留,但不写入示例资源与业务事实', () => {
|
||||
for (const [name, count] of Object.entries({ iam_role: 8, iam_permission: 370, iam_role_permission: 551,
|
||||
for (const [name, count] of Object.entries({ iam_role: 8, iam_permission: 392, iam_role_permission: 573,
|
||||
iam_data_scope: 7, wh_warehouse: 318, wh_warehouse_address: 318, wh_service_capability: 4,
|
||||
fl_fee_item: 112, fl_price_channel_catalog: 15, fl_price_origin_alias: 7, fl_price_warehouse_mapping: 4,
|
||||
file_export_task_guard: 1, fin_subject: 1 })) assert.equal(seedRows(cn, name).length, count, name)
|
||||
@@ -81,6 +81,14 @@ test('国内IAM权限与角色授权没有丢失或悬挂,客户模板仍完
|
||||
'merchant:warehouse:delivery:list', 'merchant:warehouse:delivery:create']) assert.ok(merchant.has(code), code)
|
||||
const admin = roleAuthorities(cn, 'PLATFORM_ADMIN')
|
||||
assert.ok(admin.has('lastleg:cargo-discard:approve'), 'existing discard review endpoint must be grantable to platform administrator')
|
||||
for (const action of ['list', 'create', 'edit', 'status', 'review', 'upload']) {
|
||||
assert.ok(admin.has(`platform:overseas-master-data:driver:${action}`))
|
||||
}
|
||||
for (const action of ['list', 'create', 'edit', 'assign', 'cancel']) {
|
||||
assert.ok(admin.has(`platform:fleet:trip:${action}`))
|
||||
}
|
||||
assert.ok(!permissions.some(row => row.permission_code === 'platform:order:complete'),
|
||||
'订单完成必须由可信履约回写产生,不能恢复人工完成权限')
|
||||
for (const code of ['platform:order:declaration:edit', 'platform:firstleg:inbound:hold',
|
||||
'platform:overseas-warehouse:last-mile-order:list', 'platform:overseas-business:purchase:list',
|
||||
'platform:overseas-master-data:quote:list', 'platform:finance:fee-source:review',
|
||||
|
||||
@@ -15,6 +15,18 @@ const http = (path, source, target, contexts, access, idempotency, note) => ({
|
||||
})
|
||||
|
||||
export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/client/FinanceFeeCoverageClient.java',
|
||||
'lastleg-service', 'finance-service', ['REQUEST'], 'READ', 'SAFE_READ',
|
||||
'原运费覆盖只读:按实际客户与头程入仓ID批量读取Finance已接受来源;目标端只查本库,无下游调用;不提供费用写入或资金命令'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/maintenance/HttpVehicleCareFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
|
||||
'运维图片上传无远端幂等键,调用方保留请求结果;元数据与下载按平台/记录复合归属核对,禁止客户端自动重试'),
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/customer/FleetCustomerClient.java',
|
||||
'lastleg-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ',
|
||||
'外接登记读取权威有效客户与下单资格,内部服务令牌保护;不依据采购历史或创建客户主档'),
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/driver/FleetWarehouseClient.java',
|
||||
'lastleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ',
|
||||
'司机仓绑定候选只读权威仓库目录,使用内部服务令牌,不传用户JWT或修改仓库主档'),
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/client/FinanceBusinessEventClient.java',
|
||||
'lastleg-service', 'finance-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '费用及资金指令按eventId/hash直投对应Finance内部入口,严格DeliveryAck'),
|
||||
http('common-security/src/main/java/com/baiyue/common/security/HttpSessionStateVerifier.java',
|
||||
@@ -169,6 +181,9 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
'common-audit', 'audit-service', ['INFRASTRUCTURE'], 'WRITE', 'NONE', '同步审计投递失败当前仅记录告警'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/customervisit/gateway/HttpCustomerVisitFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '回访附件上传无幂等键,归属校验可重复'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/fleet/driver/HttpDriverFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
|
||||
'内部服务令牌上传驾驶证,无上传幂等键;读取按平台公共作用域、司机业务键及文件ID核对类型/大小/归属,不自动重放上传'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/delivery/gateway/HttpDeliveryDocumentFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '派送资料上传无幂等键,元数据按客户/派送单/类别校验,当前与历史下载只读'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/gateway/HttpExceptionEvidenceFileGateway.java',
|
||||
|
||||
@@ -32,6 +32,8 @@ const expectedOnlineEdges = [
|
||||
'iam-service->auth-service',
|
||||
'iam-service->warehouse-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->finance-service',
|
||||
'lastleg-service->iam-service',
|
||||
'lastleg-service->print-service',
|
||||
'lastleg-service->warehouse-overseas-service',
|
||||
'lastleg-service->warehouse-service',
|
||||
@@ -100,7 +102,7 @@ test('all Feign clients have reviewed context, access and idempotency metadata',
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'FEIGN')
|
||||
.map(({ path, source, target }) => ({ path, source, target }))
|
||||
.sort((left, right) => left.path.localeCompare(right.path))
|
||||
assert.equal(reviewed.length, 63)
|
||||
assert.equal(reviewed.length, 66)
|
||||
assert.deepEqual(await discoverFeignEdges(), reviewed)
|
||||
})
|
||||
|
||||
@@ -116,7 +118,7 @@ test('all raw HTTP transports have reviewed context, access and idempotency meta
|
||||
}
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
|
||||
.map(client => client.path).sort()
|
||||
assert.equal(reviewed.length, 22)
|
||||
assert.equal(reviewed.length, 24)
|
||||
assert.deepEqual(discovered.sort(), reviewed)
|
||||
})
|
||||
|
||||
@@ -206,7 +208,11 @@ test('unified authority checks remain infrastructure edges and direct events rem
|
||||
assert.ok(graphEdges('INFRASTRUCTURE').includes('gateway->auth-service'))
|
||||
assert.ok(graphEdges('INFRASTRUCTURE').includes('common-security->auth-service'))
|
||||
assert.ok(graphEdges('BACKGROUND').includes('lastleg-service->finance-service'))
|
||||
assert.ok(!graphEdges('REQUEST').includes('lastleg-service->finance-service'))
|
||||
const financeReads = SYNCHRONOUS_CLIENTS.filter(client => client.source === 'lastleg-service'
|
||||
&& client.target === 'finance-service' && client.contexts.includes('REQUEST'))
|
||||
assert.deepEqual(financeReads.map(client => [client.path, client.access, client.idempotency]), [[
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/client/FinanceFeeCoverageClient.java', 'READ', 'SAFE_READ'
|
||||
]])
|
||||
assert.ok(!SYNCHRONOUS_CLIENTS.some(client => ['event-bridge-service','gateway-overseas'].includes(client.source) || ['event-bridge-service','gateway-overseas'].includes(client.target)))
|
||||
})
|
||||
|
||||
@@ -264,9 +270,9 @@ test('known user entries expose every longest simple REQUEST path over the two-h
|
||||
'file-service->iam-service->auth-service',
|
||||
'file-service->iam-service->warehouse-service'
|
||||
] },
|
||||
'lastleg-service': { hops: 3, paths: [
|
||||
'lastleg-service->file-service->iam-service->auth-service',
|
||||
'lastleg-service->file-service->iam-service->warehouse-service'
|
||||
'lastleg-service': { hops: 6, paths: [
|
||||
'lastleg-service->finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
|
||||
'lastleg-service->finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service'
|
||||
] },
|
||||
'warehouse-service': { hops: 0, paths: ['warehouse-service'] },
|
||||
'warehouse-overseas-service': { hops: 1, paths: [
|
||||
@@ -280,6 +286,16 @@ test('known user entries expose every longest simple REQUEST path over the two-h
|
||||
}
|
||||
})
|
||||
|
||||
test('delivery fee coverage is a single readonly Finance hop and cannot write financial state', async () => {
|
||||
const client = await readFile(path.join(root, 'lastleg-service/src/main/java/com/baiyue/lastleg/client/FinanceFeeCoverageClient.java'), 'utf8')
|
||||
const service = await readFile(path.join(root, 'finance/src/main/java/com/baiyue/finance/service/DeliveryFeeCoverageQueryService.java'), 'utf8')
|
||||
assert.equal([...client.matchAll(/@(?:Get|Post|Put|Delete|Patch)Mapping/g)].length, 1)
|
||||
assert.ok(client.includes('/internal/finance/v1/delivery-fee-coverage/sources'))
|
||||
assert.ok(service.includes('@Transactional(readOnly = true)'))
|
||||
assert.ok(!/\.client\.|Feign|RestClient|WebClient|HttpClient|jdbc\.update|\.execute\(/.test(service))
|
||||
assert.ok(service.includes("f.customer_id=:customerId AND f.inbound_id IN (:inboundIds)"))
|
||||
})
|
||||
|
||||
test('shared Feign timeout defaults and long-running export overrides do not drift silently', async () => {
|
||||
for (const profile of ['test']) {
|
||||
const source = await readFile(path.join(root, 'config', 'nacos', profile, 'baiyue-common.yml'), 'utf8')
|
||||
|
||||
Reference in New Issue
Block a user