优化
This commit is contained in:
@@ -62,13 +62,13 @@ const parseCompose = (yaml) => {
|
||||
}))
|
||||
}
|
||||
|
||||
test('root POM freezes 22 modules and service-map freezes 15 executable services', async () => {
|
||||
test('root POM freezes 23 modules and service-map freezes 16 executable services', async () => {
|
||||
const modules = parseRootModules(await read('pom.xml'))
|
||||
const services = parseServiceMap(await read('deploy/service-map.yml'))
|
||||
|
||||
assert.equal(modules.length, 22)
|
||||
assert.equal(new Set(modules).size, 22)
|
||||
assert.equal(services.size, 15)
|
||||
assert.equal(modules.length, 23)
|
||||
assert.equal(new Set(modules).size, 23)
|
||||
assert.equal(services.size, 16)
|
||||
|
||||
const bootModules = []
|
||||
for (const module of modules) {
|
||||
@@ -155,6 +155,32 @@ test('Maven verify publishes JaCoCo reports without enforcing a threshold', asyn
|
||||
assert.match(gitlab, /^ - "\*\*\/target\/jacoco\.exec"$/m)
|
||||
})
|
||||
|
||||
test('SpotBugs remains an opt-in non-blocking report without quality thresholds', async () => {
|
||||
const pom = await read('pom.xml')
|
||||
const gitlab = await read('.gitlab-ci.yml')
|
||||
const profile = pom.match(/<profile>\s*<id>static-analysis-report<\/id>[\s\S]*?<\/profile>/)?.[0] ?? ''
|
||||
const spotbugs = profile.match(/<plugin>\s*<groupId>com\.github\.spotbugs<\/groupId>[\s\S]*?<\/plugin>/)?.[0] ?? ''
|
||||
const reportJob = gitlab.match(/^静态分析报告:\n([\s\S]*?)(?=^[^\s#].*:\n)/m)?.[1] ?? ''
|
||||
const verifyJob = gitlab.match(/^校验与测试:\n([\s\S]*?)(?=^[^\s#].*:\n)/m)?.[1] ?? ''
|
||||
const packageJob = gitlab.match(/^打包服务:\n([\s\S]*)/m)?.[1] ?? ''
|
||||
|
||||
assert.match(pom, /<spotbugs-maven-plugin\.version>4\.9\.8\.5<\/spotbugs-maven-plugin\.version>/)
|
||||
assert.match(spotbugs, /<artifactId>spotbugs-maven-plugin<\/artifactId>/)
|
||||
assert.match(spotbugs, /<phase>verify<\/phase>/)
|
||||
assert.match(spotbugs, /<goal>spotbugs<\/goal>/)
|
||||
assert.match(spotbugs, /<xmlOutput>true<\/xmlOutput>/)
|
||||
assert.match(spotbugs, /<htmlOutput>true<\/htmlOutput>/)
|
||||
assert.match(spotbugs, /<failOnError>false<\/failOnError>/)
|
||||
assert.doesNotMatch(spotbugs, /<goal>check<\/goal>|<rules>|<threshold>/)
|
||||
assert.doesNotMatch(pom.replace(profile, ''), /<artifactId>spotbugs-maven-plugin<\/artifactId>/)
|
||||
assert.match(reportJob, /^ allow_failure: true$/m)
|
||||
assert.match(reportJob, /-Pstatic-analysis-report/)
|
||||
assert.match(reportJob, /^ - "\*\*\/target\/spotbugs\*\.xml"$/m)
|
||||
assert.match(reportJob, /^ - "\*\*\/target\/site\/spotbugs\.html"$/m)
|
||||
assert.doesNotMatch(verifyJob, /static-analysis-report|spotbugs/i)
|
||||
assert.doesNotMatch(packageJob, /^ - 静态分析报告$/m)
|
||||
})
|
||||
|
||||
test('regional Compose and Nacos manifests cover the service-map topology', async () => {
|
||||
const services = parseServiceMap(await read('deploy/service-map.yml'))
|
||||
const composeByRegion = new Map([
|
||||
|
||||
@@ -8,16 +8,18 @@ const read = async (path) => (await readFile(new URL(`../${path}`, import.meta.u
|
||||
test('Jenkins pipelines recognize all overseas deployable modules', async () => {
|
||||
const controller = await read('Jenkinsfile')
|
||||
const service = await read('Jenkinsfile.service')
|
||||
for (const module of ['gateway-overseas', 'lastleg-service', 'warehouse-overseas-service']) {
|
||||
for (const module of ['gateway-overseas', 'lastleg-service', 'warehouse-overseas-service', 'overseas-security-service']) {
|
||||
assert.match(controller, new RegExp(`'${module}'`))
|
||||
assert.match(service, new RegExp(`'${module}'`))
|
||||
}
|
||||
assert.match(controller, /'gateway-overseas': 'SERVICE_GATEWAY_OVERSEAS'/)
|
||||
assert.match(controller, /'lastleg-service': 'SERVICE_LASTLEG'/)
|
||||
assert.match(controller, /'warehouse-overseas-service': 'SERVICE_WAREHOUSE_OVERSEAS'/)
|
||||
assert.match(controller, /'overseas-security-service': 'SERVICE_OVERSEAS_SECURITY'/)
|
||||
assert.match(service, /gateway-overseas\/target\/gateway-overseas-1\.0\.0-SNAPSHOT\.jar/)
|
||||
assert.match(service, /lastleg-service\/target\/lastleg-service-1\.0\.0-SNAPSHOT\.jar/)
|
||||
assert.match(service, /warehouse-overseas-service\/target\/warehouse-overseas-service-1\.0\.0-SNAPSHOT\.jar/)
|
||||
assert.match(service, /overseas-security-service\/target\/overseas-security-service-1\.0\.0-SNAPSHOT\.jar/)
|
||||
assert.match(controller, /EVENT_BRIDGE_REGION/)
|
||||
assert.match(service, /EVENT_BRIDGE_REGION_OVERRIDE=\$\{bridgeRegion\}/)
|
||||
assert.match(controller, /choices: \['BOTH', 'CN', 'OVERSEAS'\]/)
|
||||
@@ -30,10 +32,12 @@ test('service map freezes unique internal ports and moves event bridge away from
|
||||
assert.match(map, /^ gateway-overseas:[\s\S]*?port: 9300$/m)
|
||||
assert.match(map, /^ lastleg-service:[\s\S]*?port: 9303$/m)
|
||||
assert.match(map, /^ warehouse-overseas-service:[\s\S]*?port: 9305$/m)
|
||||
assert.match(map, /^ overseas-security-service:[\s\S]*?port: 9307$/m)
|
||||
assert.match(map, /^ event-bridge-service:[\s\S]*?port: 9312$/m)
|
||||
assert.match(map, /^ gateway-overseas:[\s\S]*?regions: \[OVERSEAS\]$/m)
|
||||
assert.match(map, /^ lastleg-service:[\s\S]*?regions: \[OVERSEAS\]$/m)
|
||||
assert.match(map, /^ warehouse-overseas-service:[\s\S]*?regions: \[OVERSEAS\]$/m)
|
||||
assert.match(map, /^ overseas-security-service:[\s\S]*?regions: \[OVERSEAS\]$/m)
|
||||
assert.match(map, /^ event-bridge-service:[\s\S]*?regions: \[CN, OVERSEAS\]$/m)
|
||||
})
|
||||
|
||||
@@ -46,6 +50,7 @@ test('overseas compose is isolated from CN defaults and disables implicit schema
|
||||
assert.match(compose, /^ gateway-overseas:\s*$/m)
|
||||
assert.match(compose, /^ lastleg-service:\s*$/m)
|
||||
assert.match(compose, /^ warehouse-overseas-service:\s*$/m)
|
||||
assert.match(compose, /^ overseas-security-service:\s*$/m)
|
||||
assert.match(compose, /^ event-bridge-service:\s*$/m)
|
||||
assert.match(compose, /EVENT_BRIDGE_OVERSEAS_PORT:-9313}:9312/)
|
||||
assert.match(compose, /INTERNAL_SERVICE_TOKEN: \$\{INTERNAL_SERVICE_TOKEN\}/)
|
||||
@@ -53,6 +58,8 @@ test('overseas compose is isolated from CN defaults and disables implicit schema
|
||||
assert.match(compose, /EVENT_BRIDGE_DB_URL: \$\{EVENT_BRIDGE_OVERSEAS_DB_URL\}/)
|
||||
assert.match(compose, /EVENT_BRIDGE_DB_USERNAME: \$\{EVENT_BRIDGE_OVERSEAS_DB_USERNAME\}/)
|
||||
assert.match(compose, /EVENT_BRIDGE_DB_PASSWORD: \$\{EVENT_BRIDGE_OVERSEAS_DB_PASSWORD\}/)
|
||||
assert.match(compose, /OVERSEAS_SECURITY_DB_URL: \$\{OVERSEAS_SECURITY_DB_URL\}/)
|
||||
assert.match(compose, /OVERSEAS_SECURITY_EVENT_CONSUMER_URL:/)
|
||||
assert.match(compose, /CN_EVENT_BRIDGE_URL: \$\{CN_EVENT_BRIDGE_TARGET_URL:-http:\/\/event-bridge-cn:9312\}/)
|
||||
assert.match(compose, /FIRSTLEG_EVENT_BRIDGE_URL: \$\{CN_EVENT_BRIDGE_TARGET_URL:-http:\/\/event-bridge-cn:9312\}/)
|
||||
assert.doesNotMatch(compose, /CN_EVENT_BRIDGE_URL: \$\{CN_EVENT_BRIDGE_URL:/)
|
||||
|
||||
@@ -6,7 +6,7 @@ boolean applyChanges = false
|
||||
String templateName = 'baiyue-firstleg-dev-print'
|
||||
List<String> serviceModules = [
|
||||
'gateway', 'gateway-overseas', 'auth', 'iam', 'firstleg', 'finance', 'order', 'warehouse', 'file', 'print',
|
||||
'integration-service', 'lastleg-service', 'warehouse-overseas-service', 'event-bridge-service', 'audit-service'
|
||||
'integration-service', 'lastleg-service', 'warehouse-overseas-service', 'overseas-security-service', 'event-bridge-service', 'audit-service'
|
||||
]
|
||||
|
||||
Jenkins jenkins = Jenkins.get()
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
export const CONTEXTS = Object.freeze(['REQUEST', 'BACKGROUND', 'INFRASTRUCTURE', 'EXTERNAL'])
|
||||
export const ACCESS_MODES = Object.freeze(['READ', 'WRITE', 'READ_WRITE'])
|
||||
export const IDEMPOTENCY = Object.freeze([
|
||||
'SAFE_READ', 'IDEMPOTENCY_KEY', 'BUSINESS_KEY', 'EVENT_ID', 'STATE_CONVERGENT', 'NONE', 'MIXED'
|
||||
])
|
||||
|
||||
const feign = (path, source, target, contexts, access, idempotency, note) => ({
|
||||
id: `FEIGN:${path}`,
|
||||
transport: 'FEIGN', path, source, target, contexts, access, idempotency, note
|
||||
})
|
||||
|
||||
const http = (path, source, target, contexts, access, idempotency, note) => ({
|
||||
id: `HTTP:${path}`,
|
||||
transport: 'HTTP', path, source, target, contexts, access, idempotency, note
|
||||
})
|
||||
|
||||
export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
feign('auth/src/main/java/com/baiyue/auth/client/IamAuthorizationClient.java',
|
||||
'auth-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 读取授权快照,不写 IAM'),
|
||||
feign('auth/src/main/java/com/baiyue/auth/client/IamOnboardingClient.java',
|
||||
'auth-service', 'iam-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY', '注册/补偿以 authUserId 收敛,不自动重试'),
|
||||
|
||||
feign('file/src/main/java/com/baiyue/file/client/FinanceExportProviderClient.java',
|
||||
'file-service', 'finance-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/FirstlegExportProviderClient.java',
|
||||
'file-service', 'firstleg-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/IamExportProviderClient.java',
|
||||
'file-service', 'iam-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/IamQualificationClient.java',
|
||||
'file-service', 'iam-service', ['REQUEST'], 'WRITE', 'STATE_CONVERGENT', '资质登记/替换按客户与资质类型收敛,无通用幂等头'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/OrderExportProviderClient.java',
|
||||
'file-service', 'order-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/WarehouseExportProviderClient.java',
|
||||
'file-service', 'warehouse-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '导出 Worker 分批读取'),
|
||||
feign('file/src/main/java/com/baiyue/file/client/WeightOcrClient.java',
|
||||
'file-service', 'integration-service', ['REQUEST'], 'READ', 'NONE', '上传线程内调用 OCR;第三方调用不可假定免费幂等'),
|
||||
|
||||
feign('finance/src/main/java/com/baiyue/finance/client/FinanceFileClient.java',
|
||||
'finance-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '元数据 GET 可重复,凭证上传无幂等键'),
|
||||
feign('finance/src/main/java/com/baiyue/finance/client/FirstlegCostSnapshotClient.java',
|
||||
'finance-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取整柜成本分摊快照'),
|
||||
feign('finance/src/main/java/com/baiyue/finance/client/FirstlegFeeSourceClient.java',
|
||||
'finance-service', 'firstleg-service', ['REQUEST', 'BACKGROUND'], 'READ_WRITE', 'MIXED',
|
||||
'事件消费回读快照;平台作废命令携带 Idempotency-Key 与请求哈希'),
|
||||
feign('finance/src/main/java/com/baiyue/finance/client/IamCustomerClient.java',
|
||||
'finance-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户主档快照'),
|
||||
feign('finance/src/main/java/com/baiyue/finance/client/WarehouseSupplierClient.java',
|
||||
'finance-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取并校验供应商快照'),
|
||||
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/CarrierScheduleIntegrationClient.java',
|
||||
'firstleg-service', 'integration-service', ['BACKGROUND'], 'READ', 'SAFE_READ', '船节点定时任务查询,不属于用户请求'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/EventBridgeClient.java',
|
||||
'firstleg-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '装柜/船节点 Outbox 按 eventId 投递'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/FeePaymentVoucherFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '校验付款凭证元数据'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/FinanceFeeSourceEventClient.java',
|
||||
'firstleg-service', 'finance-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '费用 Outbox 按事件幂等键投递'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/IamCustomerSnapshotClient.java',
|
||||
'firstleg-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户名称与状态快照'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/LoadingDocumentFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据/下载可重复'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/LoadingEvidenceFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取装柜证据元数据/内容'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/MeasurementImageClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '图片上传/作废非安全重放,查询可重复'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/OrderCargoClient.java',
|
||||
'firstleg-service', 'order-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取订单货物/来源快照'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/OrderFulfillmentSplitClient.java',
|
||||
'firstleg-service', 'order-service', ['REQUEST'], 'WRITE', 'IDEMPOTENCY_KEY', 'PDA 结算拆单携带 idempotencyKey/operationId'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PriceBookFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '价目文件上传无幂等键,读取可重复'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PriceBookWarehouseClient.java',
|
||||
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库/渠道主数据'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java',
|
||||
'firstleg-service', 'print-service', ['REQUEST'], 'READ', 'SAFE_READ', '无状态 PDF 渲染,POST 为计算型读取'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/TruckAppointmentFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '附件上传无幂等键,元数据读取可重复'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseMasterDataClient.java',
|
||||
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库主数据'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehousePalletLocationClient.java',
|
||||
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ_WRITE', 'MIXED',
|
||||
'查询可重复;预占/选择释放/转移/结算使用业务幂等键,普通确认/释放按状态收敛'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/WarehouseSupplierClient.java',
|
||||
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取供应商快照'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/customs/client/CustomsFileClient.java',
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '报关文件上传无幂等键,读取可重复'),
|
||||
|
||||
feign('iam/src/main/java/com/baiyue/iam/client/AuthAccountClient.java',
|
||||
'iam-service', 'auth-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '账号查询可重复,写操作按账号/命令业务键收敛'),
|
||||
feign('iam/src/main/java/com/baiyue/iam/client/CustomerDimensionRuleClient.java',
|
||||
'iam-service', 'firstleg-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY', '初始化/补偿使用 initializationKey'),
|
||||
feign('iam/src/main/java/com/baiyue/iam/client/OverseasStorageFeeClient.java',
|
||||
'iam-service', 'warehouse-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY', '初始化/补偿使用 initializationKey'),
|
||||
feign('iam/src/main/java/com/baiyue/iam/client/SecurityEventBridgeClient.java',
|
||||
'iam-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'IAM 安全 Outbox 按 eventId 投递'),
|
||||
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/client/EventBridgeClient.java',
|
||||
'lastleg-service', 'event-bridge-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', '卸柜完成 Outbox 按 eventId 投递'),
|
||||
feign('lastleg-service/src/main/java/com/baiyue/lastleg/masterdata/quote/client/DomesticPartnerWarehouseClient.java',
|
||||
'lastleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '报价创建时读取国内合作仓快照'),
|
||||
|
||||
feign('order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java',
|
||||
'order-service', 'firstleg-service', ['REQUEST', 'BACKGROUND'], 'READ_WRITE', 'BUSINESS_KEY',
|
||||
'列表查询/缺失修复在线执行;审核建单由 Outbox 投递,按 orderId/inboundCode 收敛'),
|
||||
feign('order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java',
|
||||
'order-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 校验计价渠道,不写履约状态'),
|
||||
feign('order/src/main/java/com/baiyue/order/client/FirstlegUnifiedOrderClient.java',
|
||||
'order-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', 'POST 查询统一订单投影'),
|
||||
feign('order/src/main/java/com/baiyue/order/client/ProductImageClient.java',
|
||||
'order-service', 'file-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取商品图片元数据'),
|
||||
feign('order/src/main/java/com/baiyue/order/client/TenantEligibilityClient.java',
|
||||
'order-service', 'iam-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取客户下单资格'),
|
||||
feign('order/src/main/java/com/baiyue/order/client/WarehouseClient.java',
|
||||
'order-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库主数据'),
|
||||
|
||||
http('common-audit/src/main/java/com/baiyue/common/audit/HttpAuditSink.java',
|
||||
'common-audit', 'audit-service', ['INFRASTRUCTURE'], 'WRITE', 'NONE', '同步审计投递失败当前仅记录告警'),
|
||||
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFirstlegEventClient.java',
|
||||
'event-bridge-service', 'firstleg-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'Inbox 租约投递,目标按 eventId 幂等'),
|
||||
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java',
|
||||
'event-bridge-service', 'lastleg-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'Inbox 租约投递,目标按 eventId 幂等'),
|
||||
http('event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpSecurityProjectionEventClient.java',
|
||||
'event-bridge-service', 'overseas-security-service', ['BACKGROUND'], 'WRITE', 'EVENT_ID', 'IAM 安全事件投影按 eventId/版本单调幂等'),
|
||||
http('gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpOverseasSecurityValidationClient.java',
|
||||
'gateway-overseas', 'overseas-security-service', ['INFRASTRUCTURE'], 'READ', 'SAFE_READ', 'POST 实时校验,失败关闭'),
|
||||
http('gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpPdaSessionValidationClient.java',
|
||||
'gateway-overseas', 'auth-service', ['INFRASTRUCTURE'], 'READ', 'SAFE_READ', 'POST PDA 会话校验,失败关闭'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据校验可重复'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java',
|
||||
'lastleg-service', 'warehouse-overseas-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '查询可重复;写操作按 business/reservation key 或状态收敛'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java',
|
||||
'lastleg-service', 'warehouse-overseas-service', ['REQUEST'], 'READ_WRITE', 'IDEMPOTENCY_KEY', '托盘变更/补偿使用 operationKey 与请求哈希'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据校验可重复'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/EvergreenTrackingClient.java',
|
||||
'integration-service', 'external-evergreen', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/HedeTrackingClient.java',
|
||||
'integration-service', 'external-hede', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/MatsonTrackingClient.java',
|
||||
'integration-service', 'external-matson', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/SmlTrackingClient.java',
|
||||
'integration-service', 'external-sml', ['EXTERNAL'], 'READ', 'SAFE_READ', '船司轨迹查询;遵守外部限流'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/ZimTrackingClient.java',
|
||||
'integration-service', 'external-zim', ['EXTERNAL'], 'READ', 'SAFE_READ', '令牌获取与轨迹查询不改变平台业务事实'),
|
||||
http('integration-service/src/main/java/com/baiyue/integration/provider/ocr/BaiduMeterClient.java',
|
||||
'integration-service', 'external-baidu-ocr', ['EXTERNAL'], 'READ', 'NONE', 'OCR 可能计费,不由 HTTP 层自动重试')
|
||||
])
|
||||
|
||||
export const USER_ENTRIES = Object.freeze([
|
||||
{ name: '认证 API', service: 'auth-service' },
|
||||
{ name: 'IAM API', service: 'iam-service' },
|
||||
{ name: '订单 API', service: 'order-service' },
|
||||
{ name: '头程 API/PDA', service: 'firstleg-service' },
|
||||
{ name: '财务 API', service: 'finance-service' },
|
||||
{ name: '文件 API', service: 'file-service' },
|
||||
{ name: '海外尾程 API/PDA', service: 'lastleg-service' },
|
||||
{ name: '国内仓储 API', service: 'warehouse-service' },
|
||||
{ name: '海外仓储 API', service: 'warehouse-overseas-service' }
|
||||
])
|
||||
|
||||
export function graphEdges(context) {
|
||||
return [...new Set(SYNCHRONOUS_CLIENTS
|
||||
.filter(client => context === 'ALL' || client.contexts.includes(context))
|
||||
.map(client => `${client.source}->${client.target}`))].sort()
|
||||
}
|
||||
|
||||
export function stronglyConnectedComponents(edgeNames) {
|
||||
const adjacency = new Map()
|
||||
for (const edge of edgeNames) {
|
||||
const [from, to] = edge.split('->')
|
||||
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
||||
if (!adjacency.has(to)) adjacency.set(to, new Set())
|
||||
adjacency.get(from).add(to)
|
||||
}
|
||||
let index = 0
|
||||
const indexes = new Map()
|
||||
const lowLinks = new Map()
|
||||
const stack = []
|
||||
const onStack = new Set()
|
||||
const components = []
|
||||
function connect(node) {
|
||||
indexes.set(node, index)
|
||||
lowLinks.set(node, index++)
|
||||
stack.push(node)
|
||||
onStack.add(node)
|
||||
for (const target of adjacency.get(node)) {
|
||||
if (!indexes.has(target)) {
|
||||
connect(target)
|
||||
lowLinks.set(node, Math.min(lowLinks.get(node), lowLinks.get(target)))
|
||||
} else if (onStack.has(target)) lowLinks.set(node, Math.min(lowLinks.get(node), indexes.get(target)))
|
||||
}
|
||||
if (lowLinks.get(node) !== indexes.get(node)) return
|
||||
const component = []
|
||||
let current
|
||||
do {
|
||||
current = stack.pop()
|
||||
onStack.delete(current)
|
||||
component.push(current)
|
||||
} while (current !== node)
|
||||
components.push(component.sort())
|
||||
}
|
||||
for (const node of adjacency.keys()) if (!indexes.has(node)) connect(node)
|
||||
return components.filter(component => component.length > 1)
|
||||
.sort((left, right) => left.join().localeCompare(right.join()))
|
||||
}
|
||||
|
||||
export function longestSimplePaths(entryService, edgeNames = graphEdges('REQUEST')) {
|
||||
const adjacency = new Map()
|
||||
for (const edge of edgeNames) {
|
||||
const [from, to] = edge.split('->')
|
||||
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
||||
adjacency.get(from).add(to)
|
||||
}
|
||||
let longest = []
|
||||
function visit(node, path) {
|
||||
const targets = [...(adjacency.get(node) ?? [])].sort()
|
||||
let extended = false
|
||||
for (const target of targets) {
|
||||
if (path.includes(target)) continue
|
||||
extended = true
|
||||
visit(target, [...path, target])
|
||||
}
|
||||
if (extended) return
|
||||
if (longest.length === 0 || path.length > longest[0].length) longest = [path]
|
||||
else if (path.length === longest[0].length) longest.push(path)
|
||||
}
|
||||
visit(entryService, [entryService])
|
||||
return longest.sort((left, right) => left.join('->').localeCompare(right.join('->')))
|
||||
}
|
||||
@@ -3,72 +3,62 @@ import { readdir, readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import {
|
||||
ACCESS_MODES, CONTEXTS, IDEMPOTENCY, SYNCHRONOUS_CLIENTS, USER_ENTRIES,
|
||||
graphEdges, longestSimplePaths, stronglyConnectedComponents
|
||||
} from './synchronous-dependency-baseline.mjs'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const serviceModules = [
|
||||
'auth', 'iam', 'order', 'firstleg', 'finance', 'file', 'warehouse',
|
||||
'integration-service', 'print', 'event-bridge-service', 'lastleg-service',
|
||||
'warehouse-overseas-service'
|
||||
'warehouse-overseas-service', 'overseas-security-service'
|
||||
]
|
||||
|
||||
const canonicalSource = new Map(serviceModules.map(name => [name, `${name}${name.endsWith('-service') ? '' : '-service'}`]))
|
||||
|
||||
const expectedFeignEdges = new Map(Object.entries({
|
||||
'auth-service->iam-service': 2,
|
||||
'file-service->finance-service': 1,
|
||||
'file-service->firstleg-service': 1,
|
||||
'file-service->iam-service': 2,
|
||||
'file-service->integration-service': 1,
|
||||
'file-service->order-service': 1,
|
||||
'file-service->warehouse-service': 1,
|
||||
'finance-service->file-service': 1,
|
||||
'finance-service->firstleg-service': 2,
|
||||
'finance-service->iam-service': 1,
|
||||
'finance-service->warehouse-service': 1,
|
||||
'firstleg-service->event-bridge-service': 1,
|
||||
'firstleg-service->file-service': 7,
|
||||
'firstleg-service->finance-service': 1,
|
||||
'firstleg-service->iam-service': 1,
|
||||
'firstleg-service->integration-service': 1,
|
||||
'firstleg-service->order-service': 2,
|
||||
'firstleg-service->print-service': 1,
|
||||
'firstleg-service->warehouse-service': 4,
|
||||
'iam-service->auth-service': 1,
|
||||
'iam-service->event-bridge-service': 1,
|
||||
'iam-service->firstleg-service': 1,
|
||||
'iam-service->warehouse-service': 1,
|
||||
'lastleg-service->event-bridge-service': 1,
|
||||
'lastleg-service->warehouse-service': 1,
|
||||
'order-service->file-service': 1,
|
||||
'order-service->firstleg-service': 3,
|
||||
'order-service->iam-service': 1,
|
||||
'order-service->warehouse-service': 1
|
||||
}))
|
||||
|
||||
const expectedRawHttpImplementations = [
|
||||
'common-audit/src/main/java/com/baiyue/common/audit/HttpAuditSink.java',
|
||||
'event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpFirstlegEventClient.java',
|
||||
'event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java',
|
||||
'gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpPdaSessionValidationClient.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java',
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/EvergreenTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/HedeTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/MatsonTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/SmlTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/ZimTrackingClient.java',
|
||||
'integration-service/src/main/java/com/baiyue/integration/provider/ocr/BaiduMeterClient.java'
|
||||
const expectedOnlineEdges = [
|
||||
'auth-service->iam-service',
|
||||
'file-service->iam-service',
|
||||
'file-service->integration-service',
|
||||
'finance-service->file-service',
|
||||
'finance-service->firstleg-service',
|
||||
'finance-service->iam-service',
|
||||
'finance-service->warehouse-service',
|
||||
'firstleg-service->file-service',
|
||||
'firstleg-service->iam-service',
|
||||
'firstleg-service->order-service',
|
||||
'firstleg-service->print-service',
|
||||
'firstleg-service->warehouse-service',
|
||||
'iam-service->auth-service',
|
||||
'iam-service->firstleg-service',
|
||||
'iam-service->warehouse-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->warehouse-overseas-service',
|
||||
'lastleg-service->warehouse-service',
|
||||
'order-service->file-service',
|
||||
'order-service->firstleg-service',
|
||||
'order-service->iam-service',
|
||||
'order-service->warehouse-service'
|
||||
]
|
||||
|
||||
const reviewedRawInternalEdges = [
|
||||
const expectedBackgroundEdges = [
|
||||
'event-bridge-service->firstleg-service',
|
||||
'event-bridge-service->lastleg-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->warehouse-overseas-service'
|
||||
'event-bridge-service->overseas-security-service',
|
||||
'file-service->finance-service',
|
||||
'file-service->firstleg-service',
|
||||
'file-service->iam-service',
|
||||
'file-service->order-service',
|
||||
'file-service->warehouse-service',
|
||||
'finance-service->firstleg-service',
|
||||
'firstleg-service->event-bridge-service',
|
||||
'firstleg-service->finance-service',
|
||||
'firstleg-service->integration-service',
|
||||
'iam-service->event-bridge-service',
|
||||
'lastleg-service->event-bridge-service',
|
||||
'order-service->firstleg-service'
|
||||
]
|
||||
|
||||
const rawHttpPattern = /\b(?:RestClient|WebClient|HttpClient)\b/
|
||||
@@ -87,7 +77,7 @@ async function javaFiles(directory) {
|
||||
}
|
||||
|
||||
async function discoverFeignEdges() {
|
||||
const edges = new Map()
|
||||
const clients = []
|
||||
for (const module of serviceModules) {
|
||||
const sourceRoot = path.join(root, module, 'src', 'main', 'java')
|
||||
for (const file of await javaFiles(sourceRoot)) {
|
||||
@@ -96,66 +86,24 @@ async function discoverFeignEdges() {
|
||||
const annotation = source.match(/@FeignClient\(([\s\S]*?)\)\s*(?:public\s+)?interface/)
|
||||
const target = annotation?.[1].match(/name\s*=\s*"([^"]+)"/)?.[1]
|
||||
assert.ok(target, `cannot resolve Feign target from ${path.relative(root, file)}`)
|
||||
const edge = `${canonicalSource.get(module)}->${target}`
|
||||
edges.set(edge, (edges.get(edge) ?? 0) + 1)
|
||||
clients.push({
|
||||
path: path.relative(root, file).replaceAll('\\', '/'),
|
||||
source: canonicalSource.get(module), target
|
||||
})
|
||||
}
|
||||
}
|
||||
return edges
|
||||
return clients.sort((left, right) => left.path.localeCompare(right.path))
|
||||
}
|
||||
|
||||
function sortedEntries(map) {
|
||||
return [...map.entries()].sort(([left], [right]) => left.localeCompare(right))
|
||||
}
|
||||
|
||||
function stronglyConnectedComponents(edgeNames) {
|
||||
const adjacency = new Map()
|
||||
for (const edge of edgeNames) {
|
||||
const [from, to] = edge.split('->')
|
||||
if (!adjacency.has(from)) adjacency.set(from, new Set())
|
||||
if (!adjacency.has(to)) adjacency.set(to, new Set())
|
||||
adjacency.get(from).add(to)
|
||||
}
|
||||
let index = 0
|
||||
const indexes = new Map()
|
||||
const lowLinks = new Map()
|
||||
const stack = []
|
||||
const onStack = new Set()
|
||||
const components = []
|
||||
|
||||
function connect(node) {
|
||||
indexes.set(node, index)
|
||||
lowLinks.set(node, index++)
|
||||
stack.push(node)
|
||||
onStack.add(node)
|
||||
for (const target of adjacency.get(node)) {
|
||||
if (!indexes.has(target)) {
|
||||
connect(target)
|
||||
lowLinks.set(node, Math.min(lowLinks.get(node), lowLinks.get(target)))
|
||||
} else if (onStack.has(target)) {
|
||||
lowLinks.set(node, Math.min(lowLinks.get(node), indexes.get(target)))
|
||||
}
|
||||
}
|
||||
if (lowLinks.get(node) !== indexes.get(node)) return
|
||||
const component = []
|
||||
let current
|
||||
do {
|
||||
current = stack.pop()
|
||||
onStack.delete(current)
|
||||
component.push(current)
|
||||
} while (current !== node)
|
||||
components.push(component.sort())
|
||||
}
|
||||
|
||||
for (const node of adjacency.keys()) if (!indexes.has(node)) connect(node)
|
||||
return components.filter(component => component.length > 1)
|
||||
.sort((left, right) => left.join().localeCompare(right.join()))
|
||||
}
|
||||
|
||||
test('all Feign clients remain covered by the reviewed synchronous edge baseline', async () => {
|
||||
assert.deepEqual(sortedEntries(await discoverFeignEdges()), sortedEntries(expectedFeignEdges))
|
||||
test('all 44 Feign clients have reviewed context, access and idempotency metadata', async () => {
|
||||
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, 44)
|
||||
assert.deepEqual(await discoverFeignEdges(), reviewed)
|
||||
})
|
||||
|
||||
test('raw HTTP transports cannot be added without an explicit graph review', async () => {
|
||||
test('all 16 raw HTTP transports have reviewed context, access and idempotency metadata', async () => {
|
||||
const discovered = []
|
||||
for (const module of ['common-audit', 'event-bridge-service', 'gateway-overseas', 'lastleg-service', 'integration-service']) {
|
||||
for (const file of await javaFiles(path.join(root, module, 'src', 'main', 'java'))) {
|
||||
@@ -165,18 +113,127 @@ test('raw HTTP transports cannot be added without an explicit graph review', asy
|
||||
discovered.push(path.relative(root, file).replaceAll('\\', '/'))
|
||||
}
|
||||
}
|
||||
assert.deepEqual(discovered.sort(), [...expectedRawHttpImplementations].sort())
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
|
||||
.map(client => client.path).sort()
|
||||
assert.equal(reviewed.length, 16)
|
||||
assert.deepEqual(discovered.sort(), reviewed)
|
||||
})
|
||||
|
||||
test('the current synchronous service graph keeps its reviewed strongly connected component visible', async () => {
|
||||
const feignEdges = (await discoverFeignEdges()).keys()
|
||||
const components = stronglyConnectedComponents([...feignEdges, ...reviewedRawInternalEdges])
|
||||
assert.deepEqual(components, [[
|
||||
test('every reviewed synchronous client has a complete and exclusive executable classification', () => {
|
||||
assert.equal(new Set(SYNCHRONOUS_CLIENTS.map(client => client.id)).size, SYNCHRONOUS_CLIENTS.length)
|
||||
for (const client of SYNCHRONOUS_CLIENTS) {
|
||||
assert.ok(client.path && client.source && client.target && client.note, `${client.id} metadata is incomplete`)
|
||||
assert.ok(client.contexts.length > 0, `${client.id} has no execution context`)
|
||||
assert.equal(new Set(client.contexts).size, client.contexts.length, `${client.id} repeats a context`)
|
||||
for (const context of client.contexts) assert.ok(CONTEXTS.includes(context), `${client.id} has invalid context ${context}`)
|
||||
assert.ok(ACCESS_MODES.includes(client.access), `${client.id} has invalid access ${client.access}`)
|
||||
assert.ok(IDEMPOTENCY.includes(client.idempotency), `${client.id} has invalid idempotency ${client.idempotency}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('all four Lastleg RestClient gateways use explicit bounded transports', async () => {
|
||||
const clients = new Map([
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java', 'getUnloadPhotoFile'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/gateway/HttpRelabelPhotoFileGateway.java', 'getRelabelPhotoFile'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java', 'getWarehouseResource'],
|
||||
['lastleg-service/src/main/java/com/baiyue/lastleg/palletops/gateway/HttpPalletPhysicalGateway.java', 'getPalletPhysical']
|
||||
])
|
||||
for (const [file, propertyAccessor] of clients) {
|
||||
const source = await readFile(path.join(root, file), 'utf8')
|
||||
assert.match(source, /restClientFactory\.build\(restClientBuilder, baseUrl,/)
|
||||
assert.match(source, new RegExp(`restClientProperties\\.${propertyAccessor}\\(\\)`))
|
||||
assert.doesNotMatch(source, /RetryTemplate|Retryer|@Retryable|\.retry\(|fallbackFactory|fallback\s*=/i)
|
||||
}
|
||||
|
||||
const factory = await readFile(path.join(root,
|
||||
'lastleg-service/src/main/java/com/baiyue/lastleg/config/LastlegRestClientFactory.java'), 'utf8')
|
||||
assert.match(factory, /connectTimeout\(timeouts\.getConnectTimeout\(\)\)/)
|
||||
assert.match(factory, /setReadTimeout\(timeouts\.getReadTimeout\(\)\)/)
|
||||
assert.doesNotMatch(factory, /RetryTemplate|Retryer|@Retryable|\.retry\(|fallbackFactory|fallback\s*=/i)
|
||||
|
||||
for (const configFile of [
|
||||
'lastleg-service/src/main/resources/application.yml',
|
||||
'config/nacos/overseas/lastleg-service.yml'
|
||||
]) {
|
||||
const source = await readFile(path.join(root, configFile), 'utf8')
|
||||
for (const key of ['unload-photo-file', 'relabel-photo-file', 'warehouse-resource', 'pallet-physical']) {
|
||||
assert.match(source, new RegExp(
|
||||
`${key}:\\s*\\n\\s+connect-timeout:\\s*\\$\\{[^}]+:[1-9][0-9]*(?:ms|s)\\}`
|
||||
+ `\\s*\\n\\s+read-timeout:\\s*\\$\\{[^}]+:[1-9][0-9]*(?:ms|s)\\}`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('REQUEST, BACKGROUND and full graphs remain separate executable baselines', () => {
|
||||
assert.deepEqual(graphEdges('REQUEST'), expectedOnlineEdges)
|
||||
assert.deepEqual(graphEdges('BACKGROUND'), expectedBackgroundEdges)
|
||||
assert.deepEqual(graphEdges('INFRASTRUCTURE'), [
|
||||
'common-audit->audit-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'gateway-overseas->overseas-security-service'
|
||||
])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [[
|
||||
'auth-service', 'file-service', 'firstleg-service', 'iam-service', 'order-service'
|
||||
]])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('BACKGROUND')), [[
|
||||
'event-bridge-service', 'finance-service', 'firstleg-service', 'lastleg-service'
|
||||
]])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('ALL')), [[
|
||||
'auth-service', 'event-bridge-service', 'file-service', 'finance-service',
|
||||
'firstleg-service', 'iam-service', 'lastleg-service', 'order-service'
|
||||
]])
|
||||
})
|
||||
|
||||
test('overseas security edges never become online business dependencies', () => {
|
||||
assert.ok(!graphEdges('REQUEST').some(edge => edge.includes('overseas-security-service')))
|
||||
assert.ok(graphEdges('INFRASTRUCTURE').includes('gateway-overseas->overseas-security-service'))
|
||||
assert.ok(graphEdges('BACKGROUND').includes('event-bridge-service->overseas-security-service'))
|
||||
})
|
||||
|
||||
test('known user entries expose every longest simple REQUEST path over the two-hop budget', () => {
|
||||
const summary = Object.fromEntries(USER_ENTRIES.map(entry => {
|
||||
const paths = longestSimplePaths(entry.service)
|
||||
return [entry.service, { hops: paths[0].length - 1, paths: paths.map(path => path.join('->')) }]
|
||||
}))
|
||||
assert.deepEqual(summary, {
|
||||
'auth-service': { hops: 5, paths: [
|
||||
'auth-service->iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'iam-service': { hops: 4, paths: [
|
||||
'iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'order-service': { hops: 4, paths: [
|
||||
'order-service->file-service->iam-service->firstleg-service->print-service',
|
||||
'order-service->file-service->iam-service->firstleg-service->warehouse-service',
|
||||
'order-service->firstleg-service->file-service->iam-service->auth-service',
|
||||
'order-service->firstleg-service->file-service->iam-service->warehouse-service',
|
||||
'order-service->iam-service->firstleg-service->file-service->integration-service'
|
||||
] },
|
||||
'firstleg-service': { hops: 4, paths: [
|
||||
'firstleg-service->order-service->file-service->iam-service->auth-service',
|
||||
'firstleg-service->order-service->file-service->iam-service->warehouse-service'
|
||||
] },
|
||||
'finance-service': { hops: 5, paths: [
|
||||
'finance-service->file-service->iam-service->firstleg-service->order-service->warehouse-service',
|
||||
'finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
|
||||
'finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service',
|
||||
'finance-service->iam-service->firstleg-service->order-service->file-service->integration-service'
|
||||
] },
|
||||
'file-service': { hops: 4, paths: [
|
||||
'file-service->iam-service->firstleg-service->order-service->warehouse-service'
|
||||
] },
|
||||
'lastleg-service': { hops: 5, paths: [
|
||||
'lastleg-service->file-service->iam-service->firstleg-service->order-service->warehouse-service'
|
||||
] },
|
||||
'warehouse-service': { hops: 0, paths: ['warehouse-service'] },
|
||||
'warehouse-overseas-service': { hops: 0, paths: ['warehouse-overseas-service'] }
|
||||
})
|
||||
for (const entry of USER_ENTRIES) {
|
||||
const result = summary[entry.service]
|
||||
if (result.hops > 2) assert.ok(result.paths.every(path => path.split('->').length > 3))
|
||||
}
|
||||
})
|
||||
|
||||
test('shared Feign timeout defaults and long-running export overrides do not drift silently', async () => {
|
||||
for (const profile of ['local', 'cn', 'overseas']) {
|
||||
const source = await readFile(path.join(root, 'config', 'nacos', profile, 'baiyue-common.yml'), 'utf8')
|
||||
|
||||
Reference in New Issue
Block a user