Files
baiyue-firstleg/scripts/synchronous-dependency-contract.test.mjs
T

295 lines
14 KiB
JavaScript

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'
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'
]
const canonicalSource = new Map(serviceModules.map(name => [name, `${name}${name.endsWith('-service') ? '' : '-service'}`]))
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->warehouse-service',
'lastleg-service->file-service',
'lastleg-service->print-service',
'lastleg-service->warehouse-overseas-service',
'lastleg-service->warehouse-service',
'order-service->file-service',
'order-service->finance-service',
'order-service->firstleg-service',
'order-service->iam-service',
'order-service->warehouse-service',
'warehouse-overseas-service->print-service',
'warehouse-overseas-service->warehouse-service'
]
const expectedBackgroundEdges = [
'event-bridge-service->finance-service',
'event-bridge-service->firstleg-service',
'event-bridge-service->lastleg-service',
'file-service->finance-service',
'file-service->firstleg-service',
'file-service->iam-service',
'file-service->order-service',
'file-service->warehouse-service',
'finance-service->event-bridge-service',
'finance-service->firstleg-service',
'firstleg-service->event-bridge-service',
'firstleg-service->finance-service',
'firstleg-service->integration-service',
'firstleg-service->order-service',
'iam-service->event-bridge-service',
'iam-service->firstleg-service',
'lastleg-service->event-bridge-service',
'order-service->firstleg-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 clients = []
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)}`)
clients.push({
path: path.relative(root, file).replaceAll('\\', '/'),
source: canonicalSource.get(module), target
})
}
}
return clients.sort((left, right) => left.path.localeCompare(right.path))
}
test('all 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, 63)
assert.deepEqual(await discoverFeignEdges(), reviewed)
})
test('all 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'))) {
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('\\', '/'))
}
}
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
.map(client => client.path).sort()
assert.equal(reviewed.length, 24)
assert.deepEqual(discovered.sort(), reviewed)
})
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('IAM customer dimension Feign is reachable only from the background relay', async () => {
const references = []
for (const file of await javaFiles(path.join(root, 'iam', 'src', 'main', 'java'))) {
const source = await readFile(file, 'utf8')
if (source.includes('CustomerDimensionRuleClient')) {
references.push(path.relative(root, file).replaceAll('\\', '/'))
}
}
assert.deepEqual(references.sort(), [
'iam/src/main/java/com/baiyue/iam/client/CustomerDimensionRuleClient.java',
'iam/src/main/java/com/baiyue/iam/service/CustomerDimensionInitializationRelay.java'
])
})
test('all six 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/customervisit/gateway/HttpCustomerVisitFileGateway.java', 'getCustomerVisitFile'],
['lastleg-service/src/main/java/com/baiyue/lastleg/procurement/gateway/HttpProcurementFileGateway.java', 'getProcurementFile'],
['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', 'customer-visit-file', 'procurement-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->lastleg-service'
])
assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [
['auth-service', 'iam-service'],
['finance-service', 'firstleg-service', 'order-service']
])
assert.deepEqual(stronglyConnectedComponents(graphEdges('BACKGROUND')), [[
'event-bridge-service', 'finance-service', 'firstleg-service', 'lastleg-service', 'order-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 projection stays in Lastleg infrastructure and background edges', () => {
assert.ok(!graphEdges('REQUEST').some(edge => edge === 'gateway-overseas->lastleg-service'))
assert.ok(graphEdges('INFRASTRUCTURE').includes('gateway-overseas->lastleg-service'))
assert.ok(graphEdges('BACKGROUND').includes('event-bridge-service->lastleg-service'))
})
test('Order, Firstleg and Finance form the reviewed REQUEST SCC', () => {
const requestClients = SYNCHRONOUS_CLIENTS.filter(client => client.contexts.includes('REQUEST'))
const orderToFirstleg = requestClients.filter(client =>
client.source === 'order-service' && client.target === 'firstleg-service')
const firstlegToOrder = requestClients.filter(client =>
client.source === 'firstleg-service' && client.target === 'order-service')
assert.deepEqual(orderToFirstleg.map(client => client.path).sort(), [
'order/src/main/java/com/baiyue/order/client/FirstlegColoadGroupClient.java',
'order/src/main/java/com/baiyue/order/client/FirstlegInboundClient.java',
'order/src/main/java/com/baiyue/order/client/FirstlegPricingClient.java',
'order/src/main/java/com/baiyue/order/client/FirstlegUnifiedOrderClient.java',
'order/src/main/java/com/baiyue/order/client/LegacyImportPricingClient.java',
'order/src/main/java/com/baiyue/order/client/MerchantPricingCatalogClient.java'
])
assert.deepEqual(firstlegToOrder.map(client => client.path).sort(), [
'firstleg/src/main/java/com/baiyue/firstleg/client/OrderCargoClient.java',
'firstleg/src/main/java/com/baiyue/firstleg/client/OrderFulfillmentSplitClient.java',
'firstleg/src/main/java/com/baiyue/firstleg/client/PendingReviewOrderClient.java',
'firstleg/src/main/java/com/baiyue/firstleg/client/PrintableOrderInboundClient.java'
])
assert.ok(stronglyConnectedComponents(graphEdges('REQUEST')).some(component =>
component.includes('firstleg-service') && component.includes('order-service')
&& component.includes('finance-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: 2, paths: [
'auth-service->iam-service->warehouse-service'
] },
'iam-service': { hops: 1, paths: [
'iam-service->auth-service',
'iam-service->warehouse-service'
] },
'order-service': { hops: 5, paths: [
'order-service->finance-service->firstleg-service->file-service->iam-service->auth-service',
'order-service->finance-service->firstleg-service->file-service->iam-service->warehouse-service'
] },
'firstleg-service': { hops: 5, paths: [
'firstleg-service->order-service->finance-service->file-service->iam-service->auth-service',
'firstleg-service->order-service->finance-service->file-service->iam-service->warehouse-service'
] },
'finance-service': { hops: 5, paths: [
'finance-service->firstleg-service->order-service->file-service->iam-service->auth-service',
'finance-service->firstleg-service->order-service->file-service->iam-service->warehouse-service'
] },
'file-service': { hops: 2, paths: [
'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'
] },
'warehouse-service': { hops: 0, paths: ['warehouse-service'] },
'warehouse-overseas-service': { hops: 1, paths: [
'warehouse-overseas-service->print-service',
'warehouse-overseas-service->warehouse-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')
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)
})