优化
This commit is contained in:
@@ -350,7 +350,9 @@ export class AcceptanceRun {
|
||||
this.coverageGaps = []
|
||||
this.context = {}
|
||||
this.token = ''
|
||||
this.refreshToken = ''
|
||||
this.overseasToken = ''
|
||||
this.overseasExpiresAt = 0
|
||||
this.fatal = null
|
||||
}
|
||||
|
||||
@@ -553,6 +555,30 @@ export class AcceptanceRun {
|
||||
this.modules.push({ name, startedAt, finishedAt: new Date().toISOString(), success: fatal === null && steps.every((s) => s.passed), passed: steps.filter((s) => s.passed).length, failed: steps.filter((s) => !s.passed).length, fatal })
|
||||
}
|
||||
|
||||
async refreshSession(reason) {
|
||||
const response = await this.request('POST', '/api/auth/refresh',
|
||||
{ refreshToken: this.refreshToken }, '')
|
||||
const next = this.apiSuccess('authentication', '国内 refresh 签发新双区域会话', response,
|
||||
`reason=${reason}`, { scenario: 'dual-token-refresh', deploymentBlocker: true })
|
||||
const valid = Boolean(next?.accessToken) && Boolean(next?.refreshToken)
|
||||
&& Boolean(next?.overseasAccessToken) && Number(next?.overseasExpiresIn) > 0
|
||||
this.assert('authentication', '刷新响应完整后原子替换国内与海外Token', response, valid,
|
||||
`accessTokenPresent=${Boolean(next?.accessToken)}, refreshTokenPresent=${Boolean(next?.refreshToken)}, overseasTokenPresent=${Boolean(next?.overseasAccessToken)}, overseasExpiresIn=${next?.overseasExpiresIn ?? 'missing'}`,
|
||||
{ scenario: 'dual-token-refresh', deploymentBlocker: true })
|
||||
if (!valid) return false
|
||||
const replacement = {
|
||||
token: next.accessToken,
|
||||
refreshToken: next.refreshToken,
|
||||
overseasToken: next.overseasAccessToken,
|
||||
overseasExpiresAt: Date.now() + Number(next.overseasExpiresIn) * 1000,
|
||||
}
|
||||
this.token = replacement.token
|
||||
this.refreshToken = replacement.refreshToken
|
||||
this.overseasToken = replacement.overseasToken
|
||||
this.overseasExpiresAt = replacement.overseasExpiresAt
|
||||
return true
|
||||
}
|
||||
|
||||
async findAvailablePhone(module = 'customers') {
|
||||
const seed = BigInt(Date.now() % 10_000_000_000)
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
@@ -1159,7 +1185,10 @@ export class AcceptanceRun {
|
||||
if (!login?.accessToken) this.fatal = '平台管理员登录失败,未执行业务模块'
|
||||
else {
|
||||
this.token = login.accessToken
|
||||
this.refreshToken = login.refreshToken || ''
|
||||
this.overseasToken = login.overseasAccessToken || ''
|
||||
this.overseasExpiresAt = Number(login.overseasExpiresIn) > 0
|
||||
? Date.now() + Number(login.overseasExpiresIn) * 1000 : 0
|
||||
const requiresOverseas = this.options.modules.some((module) => module.startsWith('phase4-overseas-'))
|
||||
if (requiresOverseas) {
|
||||
this.assert('authentication', '登录响应包含有效海外访问令牌', loginResponse,
|
||||
@@ -1170,6 +1199,10 @@ export class AcceptanceRun {
|
||||
const handlers = { customers: () => this.runCustomers() }
|
||||
for (let moduleIndex = 0; moduleIndex < this.options.modules.length; moduleIndex += 1) {
|
||||
const module = this.options.modules[moduleIndex]
|
||||
if (module.startsWith('phase4-overseas-')
|
||||
&& this.overseasExpiresAt > 0 && Date.now() + 120_000 >= this.overseasExpiresAt) {
|
||||
await this.refreshSession(`海外模块 ${module} 执行前Token临近到期`)
|
||||
}
|
||||
const fullFinanceChain = ['finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full'].every((name) => this.options.modules.includes(name))
|
||||
if (module === 'finance' && fullFinanceChain) {
|
||||
this.modules.push({ name: module, startedAt: new Date().toISOString(), finishedAt: new Date().toISOString(), success: true, passed: 0, failed: 0, fatal: null, skipped: 'finance-full-chain owns the finance source locks' })
|
||||
@@ -1233,6 +1266,19 @@ export class AcceptanceRun {
|
||||
} finally {
|
||||
if (restoreFixture) restoreFixture()
|
||||
}
|
||||
const laterOverseasModule = this.options.modules.slice(moduleIndex + 1)
|
||||
.some((name) => name.startsWith('phase4-overseas-'))
|
||||
if (module === 'system' && laterOverseasModule) {
|
||||
const refreshed = await this.refreshSession('system模块权限与角色变更完成')
|
||||
if (refreshed) {
|
||||
const probe = await this.requestOverseas('GET',
|
||||
'/api/platform/v1/last-leg/orders?page=1&size=1')
|
||||
this.assert('authentication', 'system后新admin海外Token只读探针可用', probe,
|
||||
probe.http === 200 && String(probe.body?.code) === '200',
|
||||
`HTTP=${probe.http}, code=${probe.body?.code ?? 'missing'}, requestId=${probe.requestId || 'missing'}, traceId=${probe.traceId || 'missing'}`,
|
||||
{ scenario: 'dual-token-refresh', deploymentBlocker: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const reportSurface = JSON.stringify({
|
||||
@@ -1240,7 +1286,8 @@ export class AcceptanceRun {
|
||||
deploymentBlockers: this.deploymentBlockers, coverageGaps: this.coverageGaps,
|
||||
createdResources: this.createdResources,
|
||||
})
|
||||
const forbiddenValues = [this.options.password, this.token, this.overseasToken].filter((value) => typeof value === 'string' && value.length >= 8)
|
||||
const forbiddenValues = [this.options.password, this.token, this.refreshToken, this.overseasToken]
|
||||
.filter((value) => typeof value === 'string' && value.length >= 8)
|
||||
const sensitivePatterns = [
|
||||
/"(?:password|newPassword|temporaryPassword|accessToken|refreshToken|authorization)"\s*:/i,
|
||||
/\bBearer\s+[A-Za-z0-9._~-]{16,}/i,
|
||||
|
||||
@@ -30,6 +30,16 @@ test('系统验收覆盖后端四种权限资源类型并清理测试资源', ()
|
||||
assert.match(system, /DELETE.*permissions\/\$\{additionalPermissionId\}/s)
|
||||
})
|
||||
|
||||
test('system后刷新双Token且受限海外权限只接受403语义', () => {
|
||||
assert.match(acceptance, /module === 'system' && laterOverseasModule/)
|
||||
assert.match(acceptance, /refreshSession\('system模块权限与角色变更完成'\)/)
|
||||
assert.match(acceptance, /system后新admin海外Token只读探针可用/)
|
||||
assert.match(acceptance, /Date\.now\(\) \+ 120_000 >= this\.overseasExpiresAt/)
|
||||
assert.match(system, /缺少canonical海外订单权限必须403/)
|
||||
assert.match(system, /denied\.http === 403 \|\| \['403', 'A0403'\]\.includes/)
|
||||
assert.doesNotMatch(system, /A0401[^\r\n]{0,120}(必须403|includes)/)
|
||||
})
|
||||
|
||||
test('客户验收覆盖客户维度操作日志列表和详情快照', () => {
|
||||
assert.match(acceptance, /audit\/logs\?accountId=\$\{tenantId\}&eventType=PLATFORM_TENANT_UPDATE&resourceType=TENANT&resourceId=\$\{tenantId\}/)
|
||||
assert.match(acceptance, /PLATFORM_TENANT_UPDATE/)
|
||||
|
||||
@@ -28,6 +28,13 @@ function Assert-Contains([string]$text, [string]$needle, [string]$scope) {
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-NotContains([string]$text, [string]$needle, [string]$scope) {
|
||||
$script:checked++
|
||||
if ($text.Contains($needle)) {
|
||||
Add-Failure "$scope 仍残留: $needle"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-Matches([string]$text, [string]$pattern, [string]$scope) {
|
||||
$script:checked++
|
||||
if ($text -notmatch $pattern) {
|
||||
@@ -156,14 +163,14 @@ foreach ($gatewayPath in @(
|
||||
Assert-Contains $overseasGatewayConfig 'uri: lb://lastleg-service' 'Overseas Gateway Lastleg 路由'
|
||||
Assert-Contains $overseasGatewayConfig 'uri: lb://warehouse-overseas-service' 'Overseas Gateway Warehouse 路由'
|
||||
|
||||
# 海外安全投影是独立部署单元,服务映射、海外编排和 Jenkins 构建入口必须同时存在。
|
||||
# 海外安全投影已并入 Lastleg,Gateway 校验和 Event Bridge 消费均指向同一海外服务。
|
||||
$serviceMap = Read-RequiredText (Join-Path $backendRoot 'deploy/service-map.yml')
|
||||
$overseasCompose = Read-RequiredText (Join-Path $backendRoot 'deploy/docker-compose.overseas.yml')
|
||||
$serviceJenkinsfile = Read-RequiredText (Join-Path $backendRoot 'Jenkinsfile.service')
|
||||
Assert-Contains $serviceMap 'overseas-security-service:' '服务部署映射'
|
||||
Assert-Contains $overseasCompose 'overseas-security-service:' '海外 Compose'
|
||||
Assert-Contains $overseasCompose '/internal/overseas-security/v1/events' '海外安全事件消费地址'
|
||||
Assert-Contains $serviceJenkinsfile "'overseas-security-service'" '服务 Jenkins 构建入口'
|
||||
Assert-Contains $serviceMap 'lastleg-service:' 'Lastleg 服务部署映射'
|
||||
Assert-Contains $overseasCompose '/internal/lastleg/security/v1/events' 'Lastleg 安全事件消费地址'
|
||||
Assert-Contains $overseasCompose 'LASTLEG_SECURITY_SERVICE_URL' 'Lastleg 安全校验地址'
|
||||
Assert-NotContains $serviceJenkinsfile "'overseas-security-service'" '独立海外安全服务构建入口'
|
||||
|
||||
# Maven 聚合声明的全部历史/当前模块必须在后端根指引中可定位。
|
||||
[xml]$pom = Get-Content -LiteralPath (Join-Path $backendRoot 'pom.xml') -Raw
|
||||
@@ -238,11 +245,9 @@ $criticalPaths = @(
|
||||
'baiyue-firstleg/common-openapi/src/test/java/com/baiyue/common/openapi/PdaOpenApiGroupContractTest.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/overseas-security-service/src/main/java/com/baiyue/overseassecurity/controller/OverseasSecurityController.java',
|
||||
'baiyue-firstleg/overseas-security-service/src/main/java/com/baiyue/overseassecurity/service/SecurityProjectionService.java',
|
||||
'baiyue-firstleg/overseas-security-service/src/main/java/com/baiyue/overseassecurity/service/OverseasSecurityValidationService.java',
|
||||
'baiyue-firstleg/overseas-security-service/src/main/resources/openapi/internal-overseas-security-v1.yaml',
|
||||
'baiyue-firstleg/overseas-security-service/src/main/resources/db/overseas-security.sql',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/security/controller/LastlegSecurityController.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/security/service/LastlegSecurityProjectionService.java',
|
||||
'baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/security/service/LastlegSecurityValidationService.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',
|
||||
@@ -309,7 +314,6 @@ $indexedAgentFiles = @(
|
||||
(Join-Path $backendRoot 'common-security/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'common-openapi/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'warehouse-overseas-service/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'overseas-security-service/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'event-bridge-service/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'file/AGENTS.md'),
|
||||
(Join-Path $backendRoot 'common-event-contract/AGENTS.md')
|
||||
@@ -349,5 +353,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 模块、Lastleg 海外安全投影映射、关键入口和排除目录。" -ForegroundColor Green
|
||||
exit 0
|
||||
|
||||
@@ -58,9 +58,9 @@ test('aggregates code modules, report presence and the OpenAPI service matrix',
|
||||
|
||||
test('repository freezes the current topology and non-blocking aggregate summary wiring', async () => {
|
||||
const summary = await buildQualitySummary(repositoryRoot)
|
||||
assert.deepEqual(summary.topology, { modules: 23, codeModules: 22, services: 16 })
|
||||
assert.equal(summary.openapi.documents, 27)
|
||||
assert.equal(summary.openapi.coveredServices.length, 8)
|
||||
assert.deepEqual(summary.topology, { modules: 22, codeModules: 21, services: 15 })
|
||||
assert.equal(summary.openapi.documents, 26)
|
||||
assert.equal(summary.openapi.coveredServices.length, 7)
|
||||
assert.equal(summary.openapi.codeFirstServices.length, 8)
|
||||
|
||||
const gitlab = (await readFile(path.join(repositoryRoot, '.gitlab-ci.yml'), 'utf8')).replaceAll('\r\n', '\n')
|
||||
|
||||
@@ -62,13 +62,13 @@ const parseCompose = (yaml) => {
|
||||
}))
|
||||
}
|
||||
|
||||
test('root POM freezes 23 modules and service-map freezes 16 executable services', async () => {
|
||||
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, 23)
|
||||
assert.equal(new Set(modules).size, 23)
|
||||
assert.equal(services.size, 16)
|
||||
assert.equal(modules.length, 22)
|
||||
assert.equal(new Set(modules).size, 22)
|
||||
assert.equal(services.size, 15)
|
||||
|
||||
const bootModules = []
|
||||
for (const module of modules) {
|
||||
|
||||
@@ -8,18 +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', 'overseas-security-service']) {
|
||||
for (const module of ['gateway-overseas', 'lastleg-service', 'warehouse-overseas-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.doesNotMatch(controller, /overseas-security-service|SERVICE_OVERSEAS_SECURITY/)
|
||||
assert.doesNotMatch(service, /overseas-security-service/)
|
||||
assert.match(controller, /EVENT_BRIDGE_REGION/)
|
||||
assert.match(service, /EVENT_BRIDGE_REGION_OVERRIDE=\$\{bridgeRegion\}/)
|
||||
assert.match(controller, /choices: \['BOTH', 'CN', 'OVERSEAS'\]/)
|
||||
@@ -32,12 +32,11 @@ 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.doesNotMatch(map, /overseas-security-service|9307/)
|
||||
assert.match(map, /^ event-bridge-service:[\s\S]*?regions: \[CN, OVERSEAS\]$/m)
|
||||
})
|
||||
|
||||
@@ -50,7 +49,6 @@ 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\}/)
|
||||
@@ -58,8 +56,9 @@ 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, /LASTLEG_SECURITY_SERVICE_URL: \$\{LASTLEG_SECURITY_SERVICE_URL:-http:\/\/lastleg-service:9303\}/)
|
||||
assert.match(compose, /LASTLEG_SECURITY_EVENT_CONSUMER_URL: \$\{LASTLEG_SECURITY_EVENT_CONSUMER_URL:-http:\/\/lastleg-service:9303\/internal\/lastleg\/security\/v1\/events\}/)
|
||||
assert.doesNotMatch(compose, /overseas-security-service|9307|OVERSEAS_SECURITY_DB|OVERSEAS_SECURITY_SERVICE_URL|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:/)
|
||||
@@ -72,7 +71,11 @@ test('CN and overseas event bridges have explicit opposite-region consumers', as
|
||||
const cn = await read('config/nacos/cn/event-bridge-service.yml')
|
||||
const overseas = await read('config/nacos/overseas/event-bridge-service.yml')
|
||||
const firstleg = await read('config/nacos/cn/firstleg-service.yml')
|
||||
const iam = await read('config/nacos/cn/iam-service.yml')
|
||||
const iamApplication = await read('iam/src/main/resources/application.yml')
|
||||
const iamClient = await read('iam/src/main/java/com/baiyue/iam/client/SecurityEventBridgeClient.java')
|
||||
const lastleg = await read('config/nacos/overseas/lastleg-service.yml')
|
||||
const gatewayOverseas = await read('config/nacos/overseas/gateway-overseas.yml')
|
||||
const scripts = `${await read('deploy/scripts/deploy-built-service.sh')}\n${await read('deploy/scripts/deploy-services.sh')}`
|
||||
assert.match(cn, /EVENT_CONSUMER_URL:http:\/\/firstleg:9202\/internal\/firstleg\/v1\/container-events/)
|
||||
assert.match(overseas, /EVENT_CONSUMER_URL:http:\/\/lastleg-service:9303\/internal\/lastleg\/events/)
|
||||
@@ -80,9 +83,17 @@ test('CN and overseas event bridges have explicit opposite-region consumers', as
|
||||
assert.match(cnCompose, /aliases: \[event-bridge-cn\]/)
|
||||
assert.match(overseasCompose, /EVENT_CONSUMER_URL: \$\{EVENT_CONSUMER_URL:-http:\/\/lastleg-service:9303\/internal\/lastleg\/events\}/)
|
||||
assert.match(firstleg, /LASTLEG_EVENT_BRIDGE_URL/)
|
||||
assert.match(iamClient, /url = "\$\{baiyue\.security-event\.event-bridge-url:\}"/)
|
||||
assert.match(iam, /SECURITY_EVENT_BRIDGE_URL:http:\/\/event-bridge-overseas:9312/)
|
||||
assert.match(iamApplication, /SECURITY_EVENT_BRIDGE_URL:http:\/\/event-bridge-overseas:9312/)
|
||||
assert.match(cnCompose, /SECURITY_EVENT_BRIDGE_URL: \$\{SECURITY_EVENT_BRIDGE_URL:-http:\/\/event-bridge-overseas:9312\}/)
|
||||
assert.match(overseasCompose, /aliases: \[event-bridge-overseas\]/)
|
||||
assert.match(firstleg, /LASTLEG_OUTBOX_RELAY_LEASE_SECONDS/)
|
||||
assert.match(lastleg, /CN_EVENT_BRIDGE_URL:\$\{FIRSTLEG_EVENT_BRIDGE_URL\}/)
|
||||
assert.match(lastleg, /WAREHOUSE_OVERSEAS_SERVICE_URL:http:\/\/warehouse-overseas-service:9305/)
|
||||
assert.match(gatewayOverseas, /LASTLEG_SECURITY_SERVICE_URL:http:\/\/lastleg-service:9303/)
|
||||
assert.match(overseas, /LASTLEG_SECURITY_EVENT_CONSUMER_URL:http:\/\/lastleg-service:9303\/internal\/lastleg\/security\/v1\/events/)
|
||||
assert.doesNotMatch(`${gatewayOverseas}\n${overseas}`, /overseas-security-service|9307|OVERSEAS_SECURITY_/)
|
||||
assert.match(scripts, /FIRSTLEG_EVENT_BRIDGE_URL/)
|
||||
assert.match(scripts, /LASTLEG_EVENT_BRIDGE_URL/)
|
||||
assert.match(scripts, /docker-compose\.overseas\.yml/)
|
||||
|
||||
@@ -44,10 +44,47 @@ test('entry accepts a separate overseas Gateway without exposing token state', (
|
||||
assert.equal(options.gateway, 'http://localhost:9000')
|
||||
assert.equal(options.overseasGateway, 'http://localhost:9300')
|
||||
assert.match(source, /this\.overseasToken = login\.overseasAccessToken \|\| ''/)
|
||||
assert.match(source, /this\.refreshToken = login\.refreshToken \|\| ''/)
|
||||
assert.match(source, /this\.overseasExpiresAt = Number\(login\.overseasExpiresIn\)/)
|
||||
assert.match(source, /async refreshSession\(reason\)/)
|
||||
assert.match(source, /refreshToken: this\.refreshToken/)
|
||||
assert.match(source, /const replacement = \{[\s\S]*token: next\.accessToken[\s\S]*refreshToken: next\.refreshToken[\s\S]*overseasToken: next\.overseasAccessToken/)
|
||||
assert.match(source, /module === 'system' && laterOverseasModule/)
|
||||
assert.match(source, /system后新admin海外Token只读探针可用/)
|
||||
assert.match(source, /module\.startsWith\('phase4-overseas-'\)[\s\S]*Date\.now\(\) \+ 120_000 >= this\.overseasExpiresAt/)
|
||||
assert.match(source, /海外模块 \$\{module\} 执行前Token临近到期/)
|
||||
assert.match(source, /async requestOverseas\(/)
|
||||
assert.match(source, /async requestOverseasBinary\(/)
|
||||
assert.match(source, /async requestOverseasMultipart\(/)
|
||||
assert.match(source, /forbiddenValues = \[this\.options\.password, this\.token, this\.overseasToken\]/)
|
||||
assert.match(source, /forbiddenValues = \[this\.options\.password, this\.token, this\.refreshToken, this\.overseasToken\]/)
|
||||
})
|
||||
|
||||
test('dual-token refresh replaces the whole session only after a complete response', async () => {
|
||||
const run = new AcceptanceRun({ gateway: 'http://localhost:9000', overseasGateway: 'http://localhost:9300',
|
||||
username: 'contract-user', password: 'contract-password', modules: [], output: '' })
|
||||
Object.assign(run, { token: 'old-access', refreshToken: 'old-refresh', overseasToken: 'old-overseas', overseasExpiresAt: 1 })
|
||||
run.request = async (method, path, body, token) => {
|
||||
assert.equal(method, 'POST'); assert.equal(path, '/api/auth/refresh')
|
||||
assert.deepEqual(body, { refreshToken: 'old-refresh' }); assert.equal(token, '')
|
||||
return { method, path, http: 200, body: { code: '200', data: {
|
||||
accessToken: 'new-access', refreshToken: 'new-refresh', overseasAccessToken: 'new-overseas',
|
||||
overseasExpiresIn: 900,
|
||||
} }, requestId: 'refresh-request', traceId: 'refresh-trace' }
|
||||
}
|
||||
assert.equal(await run.refreshSession('contract'), true)
|
||||
assert.equal(run.token, 'new-access')
|
||||
assert.equal(run.refreshToken, 'new-refresh')
|
||||
assert.equal(run.overseasToken, 'new-overseas')
|
||||
assert.ok(run.overseasExpiresAt > Date.now())
|
||||
|
||||
const frozen = { token: run.token, refreshToken: run.refreshToken,
|
||||
overseasToken: run.overseasToken, overseasExpiresAt: run.overseasExpiresAt }
|
||||
run.request = async (method, path) => ({ method, path, http: 200,
|
||||
body: { code: '200', data: { accessToken: 'partial-only' } },
|
||||
requestId: 'partial-request', traceId: 'partial-trace' })
|
||||
assert.equal(await run.refreshSession('incomplete-contract'), false)
|
||||
assert.deepEqual({ token: run.token, refreshToken: run.refreshToken,
|
||||
overseasToken: run.overseasToken, overseasExpiresAt: run.overseasExpiresAt }, frozen)
|
||||
})
|
||||
|
||||
test('entry has no built-in environment address or password and requires explicit execution options', () => {
|
||||
|
||||
@@ -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', 'overseas-security-service', 'event-bridge-service', 'audit-service'
|
||||
'integration-service', 'lastleg-service', 'warehouse-overseas-service', 'event-bridge-service', 'audit-service'
|
||||
]
|
||||
|
||||
Jenkins jenkins = Jenkins.get()
|
||||
|
||||
@@ -121,9 +121,9 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
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/版本单调幂等'),
|
||||
'event-bridge-service', 'lastleg-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 实时校验,失败关闭'),
|
||||
'gateway-overseas', 'lastleg-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',
|
||||
|
||||
@@ -13,7 +13,7 @@ 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', 'overseas-security-service'
|
||||
'warehouse-overseas-service'
|
||||
]
|
||||
|
||||
const canonicalSource = new Map(serviceModules.map(name => [name, `${name}${name.endsWith('-service') ? '' : '-service'}`]))
|
||||
@@ -45,7 +45,6 @@ const expectedOnlineEdges = [
|
||||
const expectedBackgroundEdges = [
|
||||
'event-bridge-service->firstleg-service',
|
||||
'event-bridge-service->lastleg-service',
|
||||
'event-bridge-service->overseas-security-service',
|
||||
'file-service->finance-service',
|
||||
'file-service->firstleg-service',
|
||||
'file-service->iam-service',
|
||||
@@ -131,6 +130,7 @@ test('every reviewed synchronous client has a complete and exclusive executable
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
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'],
|
||||
@@ -170,7 +170,7 @@ test('REQUEST, BACKGROUND and full graphs remain separate executable baselines',
|
||||
assert.deepEqual(graphEdges('INFRASTRUCTURE'), [
|
||||
'common-audit->audit-service',
|
||||
'gateway-overseas->auth-service',
|
||||
'gateway-overseas->overseas-security-service'
|
||||
'gateway-overseas->lastleg-service'
|
||||
])
|
||||
assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [
|
||||
['auth-service', 'iam-service'],
|
||||
@@ -185,10 +185,10 @@ test('REQUEST, BACKGROUND and full graphs remain separate executable baselines',
|
||||
]])
|
||||
})
|
||||
|
||||
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('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('known user entries expose every longest simple REQUEST path over the two-hop budget', () => {
|
||||
@@ -242,3 +242,4 @@ test('shared Feign timeout defaults and long-running export overrides do not dri
|
||||
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)
|
||||
})
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ test('Java builds use the shared builder pool and serialize incremental target d
|
||||
assert.doesNotMatch(directDeployScript, /docker save "\$IMAGE"/)
|
||||
assert.match(directDeployScript, /up -d --no-deps --pull never/)
|
||||
assert.doesNotMatch(directDeployScript, /--remove-orphans/)
|
||||
assert.match(directDeployScript, /--project-name "\$PROJECT_NAME"/)
|
||||
assert.doesNotMatch(directDeployScript, /overseas-security-service|remove_legacy_overseas_security_container/)
|
||||
assert.match(directDeployScript, /--pull never/)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user