优化
This commit is contained in:
@@ -115,6 +115,19 @@ foreach ($pdaApplication in $pdaApplications) {
|
||||
}
|
||||
}
|
||||
|
||||
# 海外 PDA 当前只开放真实拆柜收货;其余后端虽已存在,前端接入前必须保持禁用。
|
||||
$overseasWorkbenchPath = Join-Path $workspaceRoot 'apps/pda-overseas-uniapp/src/data/overseas-workbench.js'
|
||||
$overseasWorkbench = Read-RequiredText $overseasWorkbenchPath
|
||||
Assert-Matches $overseasWorkbench "(?s)name:\s*'拆柜收货'.{0,300}?available:\s*true" 'Overseas PDA 拆柜收货开关'
|
||||
foreach ($disabledOperation in @(
|
||||
'库存盘点', '商品移库', '托盘管理',
|
||||
'换标收货', '换标执行', '换标复核',
|
||||
'库存查询', '库位查询', '托盘查询'
|
||||
)) {
|
||||
$disabledPattern = "(?s)name:\s*'$([regex]::Escape($disabledOperation))'.{0,300}?available:\s*false"
|
||||
Assert-Matches $overseasWorkbench $disabledPattern "Overseas PDA 禁用入口 $disabledOperation"
|
||||
}
|
||||
|
||||
# 文档声明为正式开放的 Client 海外仓页面必须仍是 active 路由。
|
||||
$clientOverseasRoutesPath = Join-Path $workspaceRoot 'apps/client-web/src/modules/overseas-warehouse/routes.ts'
|
||||
$clientOverseasRoutes = Read-RequiredText $clientOverseasRoutesPath
|
||||
@@ -134,6 +147,7 @@ $overseasGatewayConfig = Read-RequiredText $overseasGatewayConfigPath
|
||||
foreach ($gatewayPath in @(
|
||||
'/api/merchant/v1/last-leg/**',
|
||||
'/api/platform/v1/last-leg/**',
|
||||
'/api/pda/v1/last-leg/**',
|
||||
'/api/merchant/v1/warehouses/**',
|
||||
'/api/platform/v1/warehouse-admin/**'
|
||||
)) {
|
||||
@@ -174,6 +188,9 @@ $criticalPaths = @(
|
||||
'apps/pda-overseas-uniapp/src/pages/operation/operation.vue',
|
||||
'apps/pda-overseas-uniapp/src/config/runtime.js',
|
||||
'apps/pda-overseas-uniapp/src/api/auth.js',
|
||||
'apps/pda-overseas-uniapp/src/api/inbound.js',
|
||||
'apps/pda-overseas-uniapp/src/api/receiving-adapter.js',
|
||||
'apps/pda-overseas-uniapp/src/api/overseas.js',
|
||||
'apps/pda-overseas-uniapp/src/utils/scan-broadcast.js',
|
||||
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/service/FeePaymentSnapshotValidator.java',
|
||||
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/client/FeePaymentVoucherFileClient.java',
|
||||
@@ -187,6 +204,22 @@ $criticalPaths = @(
|
||||
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformSmartLoadingController.java',
|
||||
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java',
|
||||
'baiyue-firstleg/auth/src/main/java/com/baiyue/auth/service/OverseasTokenService.java',
|
||||
'baiyue-firstleg/auth/src/main/java/com/baiyue/auth/service/OverseasSessionValidationService.java',
|
||||
'baiyue-firstleg/gateway-overseas/src/main/java/com/baiyue/gateway/filter/PdaSessionValidationFilter.java',
|
||||
'baiyue-firstleg/gateway-overseas/src/main/java/com/baiyue/gateway/filter/HttpPdaSessionValidationClient.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/ReceivingContainerController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/controller/PdaStocktakeController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/controller/PdaInventoryMoveController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/controller/PdaPalletOperationController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/controller/PdaRelabelController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/pda/service/PdaRelabelService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/warehouse/controller/PdaWarehouseController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/pda/warehouse/service/PdaWarehouseService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/resources/openapi/overseas-pda-operations-v1.yaml',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/controller/MerchantLastlegOrderController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderType.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderStatus.java',
|
||||
@@ -197,6 +230,10 @@ $criticalPaths = @(
|
||||
'baiyue-firstleg/gateway-overseas/src/test/java/com/baiyue/gateway/config/OverseasLastlegRouteContractTest.java',
|
||||
'baiyue-firstleg/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/FirstlegEventClient.java',
|
||||
'baiyue-firstleg/common-event-contract/src/main/java/com/baiyue/common/event/FirstLegLoadingPlanCompletedV2.java',
|
||||
'baiyue-firstleg/common-event-contract/src/main/java/com/baiyue/common/event/FirstLegContainerShippingNodeRecordedV1.java',
|
||||
'baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/CarrierScheduleNodeOutboxService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/shipping/service/ContainerShippingNodeConsumer.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/shipping/entity/ContainerShippingGate.java',
|
||||
'baiyue-firstleg/firstleg/src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegInboundOrder.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegInboundOrderMapper.java',
|
||||
@@ -256,5 +293,5 @@ if ($failures.Count -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、前端模块、Client active 路由、Gateway 覆盖、PDA 注册页面、Maven 模块、关键入口和排除目录。" -ForegroundColor Green
|
||||
Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、前端模块、Client active 路由、Gateway 覆盖、PDA 注册页面与能力开关、Maven 模块、关键入口和排除目录。" -ForegroundColor Green
|
||||
exit 0
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { access, readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
const read = async (path) => (await readFile(new URL(`../${path}`, import.meta.url), 'utf8'))
|
||||
.replaceAll('\r\n', '\n')
|
||||
|
||||
const sorted = (values) => [...values].sort()
|
||||
|
||||
const parseRootModules = (xml) => {
|
||||
const body = xml.match(/<modules>([\s\S]*?)<\/modules>/)?.[1] ?? ''
|
||||
return [...body.matchAll(/<module>([^<]+)<\/module>/g)].map((match) => match[1].trim())
|
||||
}
|
||||
|
||||
const parsePom = (xml) => {
|
||||
const project = xml.replace(/<parent>[\s\S]*?<\/parent>/, '')
|
||||
return {
|
||||
artifactId: project.match(/<artifactId>([^<]+)<\/artifactId>/)?.[1]?.trim(),
|
||||
springBoot: /<artifactId>spring-boot-maven-plugin<\/artifactId>/.test(project)
|
||||
}
|
||||
}
|
||||
|
||||
const parseServiceMap = (yaml) => {
|
||||
const services = new Map()
|
||||
let current
|
||||
for (const line of yaml.split('\n')) {
|
||||
const service = line.match(/^ ([a-z][a-z0-9-]+):$/)
|
||||
if (service) {
|
||||
current = {}
|
||||
services.set(service[1], current)
|
||||
continue
|
||||
}
|
||||
const property = line.match(/^ ([a-zA-Z]+): (.+)$/)
|
||||
if (!current || !property) continue
|
||||
const [, key, raw] = property
|
||||
current[key] = key === 'port'
|
||||
? Number(raw)
|
||||
: key === 'regions'
|
||||
? raw.slice(1, -1).split(',').map((value) => value.trim())
|
||||
: raw
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
const parseGroovyList = (source, variable) => {
|
||||
const body = source.match(new RegExp(`def ${variable} = \\[([\\s\\S]*?)\\n\\]`))?.[1] ?? ''
|
||||
return [...body.matchAll(/'([^']+)'/g)].map((match) => match[1])
|
||||
}
|
||||
|
||||
const parseGroovyMap = (source, variable) => {
|
||||
const body = source.match(new RegExp(`def ${variable} = \\[([\\s\\S]*?)\\n\\]`))?.[1] ?? ''
|
||||
return new Map([...body.matchAll(/'([^']+)':\s*'([^']+)'/g)]
|
||||
.map((match) => [match[1], match[2]]))
|
||||
}
|
||||
|
||||
const parseCompose = (yaml) => {
|
||||
const body = yaml.match(/^services:\n([\s\S]*?)(?=^networks:)/m)?.[1] ?? ''
|
||||
const matches = [...body.matchAll(/^ ([a-z][a-z0-9-]+):\s*$/gm)]
|
||||
return new Map(matches.map((match, index) => {
|
||||
const block = body.slice(match.index, matches[index + 1]?.index ?? body.length)
|
||||
return [match[1], Number(block.match(/^ SERVER_PORT: (\d+)$/m)?.[1])]
|
||||
}))
|
||||
}
|
||||
|
||||
test('root POM freezes 22 modules and service-map freezes 15 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)
|
||||
|
||||
const bootModules = []
|
||||
for (const module of modules) {
|
||||
const pom = parsePom(await read(`${module}/pom.xml`))
|
||||
if (pom.springBoot) bootModules.push(module)
|
||||
if (!services.has(module)) continue
|
||||
const service = services.get(module)
|
||||
assert.equal(service.artifactId, pom.artifactId, `${module} artifactId drifted`)
|
||||
assert.equal(service.jar, `${module}/target/${pom.artifactId}-1.0.0-SNAPSHOT.jar`)
|
||||
}
|
||||
assert.deepEqual(sorted(bootModules), sorted(services.keys()))
|
||||
assert.equal(new Set([...services.values()].map(({ port }) => port)).size, services.size)
|
||||
})
|
||||
|
||||
test('Jenkins and GitLab package exactly the service-map modules', async () => {
|
||||
const services = parseServiceMap(await read('deploy/service-map.yml'))
|
||||
const expected = sorted(services.keys())
|
||||
const coordinator = await read('Jenkinsfile')
|
||||
const servicePipeline = await read('Jenkinsfile.service')
|
||||
const serviceJars = parseGroovyMap(servicePipeline, 'serviceJars')
|
||||
const gitlab = await read('.gitlab-ci.yml')
|
||||
const gitlabArtifacts = [...gitlab.matchAll(/^ - ([a-z][a-z0-9-]+)\/target\/\*\.jar$/gm)]
|
||||
.map((match) => match[1])
|
||||
|
||||
assert.deepEqual(sorted(parseGroovyList(coordinator, 'serviceModules')), expected)
|
||||
assert.deepEqual(sorted(parseGroovyList(servicePipeline, 'serviceModules')), expected)
|
||||
assert.deepEqual(sorted(serviceJars.keys()), expected)
|
||||
assert.deepEqual(sorted(gitlabArtifacts), expected)
|
||||
for (const [module, service] of services) assert.equal(serviceJars.get(module), service.jar)
|
||||
})
|
||||
|
||||
test('GitLab runs the topology contract in a dedicated Node job', async () => {
|
||||
const gitlab = await read('.gitlab-ci.yml')
|
||||
const topologyJob = gitlab.match(/^Java拓扑合同:\n([\s\S]*?)(?=^[^\s#].*:\n)/m)?.[1] ?? ''
|
||||
const packageJob = gitlab.match(/^打包服务:\n([\s\S]*)/m)?.[1] ?? ''
|
||||
|
||||
assert.match(topologyJob, /^ stage: verify$/m)
|
||||
assert.match(topologyJob, /^ image: node:[^\s]+$/m)
|
||||
assert.match(topologyJob, /^ - node --test scripts\/java-topology-contract\.test\.mjs$/m)
|
||||
assert.match(packageJob, /^ - Java拓扑合同$/m)
|
||||
})
|
||||
|
||||
test('Jenkins cannot invoke the Node contract without declaring a Node runtime', async () => {
|
||||
for (const path of ['Jenkinsfile', 'Jenkinsfile.service']) {
|
||||
const pipeline = await read(path)
|
||||
if (!pipeline.includes('java-topology-contract.test.mjs')) continue
|
||||
assert.match(
|
||||
pipeline,
|
||||
/(?:nodejs\s*\(|NodeJSInstallation|docker(?:\.image\([^)]*node:|\s+run[^\n]*\bnode:))/,
|
||||
`${path} must declare how Node is provided before invoking the topology contract`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
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([
|
||||
['CN', parseCompose(await read('deploy/docker-compose.yml'))],
|
||||
['OVERSEAS', parseCompose(await read('deploy/docker-compose.overseas.yml'))]
|
||||
])
|
||||
|
||||
for (const [module, service] of services) {
|
||||
const regions = service.regions ?? ['CN']
|
||||
for (const [region, compose] of composeByRegion) {
|
||||
assert.equal(compose.has(module), regions.includes(region), `${module} Compose region drifted for ${region}`)
|
||||
if (!regions.includes(region)) continue
|
||||
assert.equal(compose.get(module), service.port, `${module} internal port drifted for ${region}`)
|
||||
await access(new URL(`../config/nacos/${region.toLowerCase()}/${service.artifactId}.yml`, import.meta.url))
|
||||
}
|
||||
}
|
||||
assert.deepEqual(
|
||||
sorted(new Set([...composeByRegion.values()].flatMap((compose) => [...compose.keys()]))),
|
||||
sorted(services.keys())
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,188 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readdir, readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
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'
|
||||
]
|
||||
|
||||
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 reviewedRawInternalEdges = [
|
||||
'event-bridge-service->firstleg-service',
|
||||
'event-bridge-service->lastleg-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'lastleg-service->file-service',
|
||||
'lastleg-service->warehouse-overseas-service'
|
||||
]
|
||||
|
||||
const rawHttpPattern = /\b(?:RestClient|WebClient|HttpClient)\b/
|
||||
|
||||
async function javaFiles(directory) {
|
||||
const result = []
|
||||
async function visit(current) {
|
||||
for (const entry of await readdir(current, { withFileTypes: true })) {
|
||||
const target = path.join(current, entry.name)
|
||||
if (entry.isDirectory()) await visit(target)
|
||||
else if (entry.isFile() && entry.name.endsWith('.java')) result.push(target)
|
||||
}
|
||||
}
|
||||
await visit(directory)
|
||||
return result
|
||||
}
|
||||
|
||||
async function discoverFeignEdges() {
|
||||
const edges = new Map()
|
||||
for (const module of serviceModules) {
|
||||
const sourceRoot = path.join(root, module, 'src', 'main', 'java')
|
||||
for (const file of await javaFiles(sourceRoot)) {
|
||||
const source = await readFile(file, 'utf8')
|
||||
if (!source.includes('@FeignClient')) continue
|
||||
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)
|
||||
}
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
||||
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('raw HTTP transports cannot be added without an explicit graph review', 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'))) {
|
||||
const source = await readFile(file, 'utf8')
|
||||
if (!rawHttpPattern.test(source)) continue
|
||||
if (!/(?:private|final)\s+(?:RestClient|WebClient|HttpClient)\b|HttpClient\s+client\s*=/.test(source)) continue
|
||||
discovered.push(path.relative(root, file).replaceAll('\\', '/'))
|
||||
}
|
||||
}
|
||||
assert.deepEqual(discovered.sort(), [...expectedRawHttpImplementations].sort())
|
||||
})
|
||||
|
||||
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, [[
|
||||
'auth-service', 'event-bridge-service', 'file-service', 'finance-service',
|
||||
'firstleg-service', 'iam-service', 'lastleg-service', 'order-service'
|
||||
]])
|
||||
})
|
||||
|
||||
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')
|
||||
assert.match(source, /connect-timeout:\s*3000/)
|
||||
assert.match(source, /read-timeout:\s*5000/)
|
||||
}
|
||||
const fileConfig = await readFile(path.join(root, 'file', 'src', 'main', 'resources', 'application.yml'), 'utf8')
|
||||
assert.equal((fileConfig.match(/read-timeout:\s*15000/g) ?? []).length, 5)
|
||||
})
|
||||
Reference in New Issue
Block a user