diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs index 0d4d31701..8c4333853 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs @@ -82,7 +82,8 @@ test('PDA operations reuse received plan pallets and the public location without test('P5 follows warehouse review, customer confirmation and fulfillment completion in order', () => { assert.match(source, /applicationStatus === 'CUSTOMER_CONFIRM_PENDING'/) - assert.match(source, /\/confirm-completion/) + assert.match(source, /const merchantRelabelBase = '\/api\/merchant\/v1\/last-leg\/relabel-applications'/) + assert.match(source, /`\$\{merchantRelabelBase\}\/\$\{encodeURIComponent\(relabelNo\)\}\/confirm-completion`/) assert.match(source, /status === 'READY_FOR_OUTBOUND'/) assert.match(source, /status: 'COMPLETED'/) assert.match(source, /walletStatus === 'SETTLED'/) diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs index d22b2210b..962471daf 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs @@ -378,6 +378,7 @@ async function verifyRelabel(context, token, merchantToken, fixture) { return false } const relabelBase = '/api/platform/v1/last-leg/relabel-applications' + const merchantRelabelBase = '/api/merchant/v1/last-leg/relabel-applications' const pdaBase = '/api/pda/v1/last-leg/relabel-tasks' const markerSuffix = String(context.marker).slice(-8) const createBody = { @@ -518,7 +519,7 @@ async function verifyRelabel(context, token, merchantToken, fixture) { && approveReplay.data?.taskStatus === 'COMPLETED' && approveReplay.data?.applicationStatus === 'CUSTOMER_CONFIRM_PENDING', trace(approveReplay.response), OPTIONS) const customerConfirm = await context.requestOverseas('POST', - `${relabelBase}/${encodeURIComponent(relabelNo)}/confirm-completion`, { + `${merchantRelabelBase}/${encodeURIComponent(relabelNo)}/confirm-completion`, { idempotencyKey: key(context, 'RELABEL-CUSTOMER-CONFIRM'), remark: `${context.marker}-customer-confirm`, }, {}, merchantToken) context.addStep(MODULE, 'P5客户确认换标完成进入待出库', customerConfirm, diff --git a/scripts/agents-index-registry-contract.test.mjs b/scripts/agents-index-registry-contract.test.mjs index 171ae4dab..cba8c5e78 100644 --- a/scripts/agents-index-registry-contract.test.mjs +++ b/scripts/agents-index-registry-contract.test.mjs @@ -1,47 +1,24 @@ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' -import { spawnSync } from 'node:child_process' import test from 'node:test' const source = readFileSync(new URL('./agents-index-contract.ps1', import.meta.url), 'utf8') -const start = source.indexOf('$registeredTestRows =') -const end = source.indexOf("Assert-NotContains $testTaskRegistry '01a0194a", start) -assert.ok(start > 0 && end > start) -const registryContract = source.slice(start, end) -const project = 'c64729ff-6710-43c6-8ef2-d84e9666cc1e' -const root = '00000000-0000-4000-8000-000000000001' -const child = '00000000-0000-4000-8000-000000000002' -const enabled = `已重新启用测试环境总控调度\nparent_task_id project_id\n| \`${root}\` | \`${project}\` | 【总控】测试环境全流程 |\n| \`${child}\` | \`${root}\` | \`${project}\` | 【前端】专项 |` -function checkRegistry(text) { - const script = ` -$ErrorActionPreference = 'Stop' -$failures = [System.Collections.Generic.List[string]]::new() -function Add-Failure([string]$message) { $script:failures.Add($message) } -function Assert-Contains([string]$text, [string]$needle, [string]$scope) { if (-not $text.Contains($needle)) { Add-Failure "$scope 缺少 $needle" } } -function Assert-Matches([string]$text, [string]$pattern, [string]$scope) { if ($text -notmatch $pattern) { Add-Failure "$scope 缺少结构" } } -$testTaskRegistry = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(text).toString('base64')}')) -${registryContract} -Write-Output ('RESULT:' + (ConvertTo-Json -InputObject @($failures) -Compress)) -` - const result = spawnSync('pwsh', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { encoding: 'utf8' }) - assert.equal(result.status, 0, result.stderr) - const output = result.stdout.match(/RESULT:(\[[^\r\n]*\])/) - assert.ok(output, result.stdout) - return JSON.parse(output[1]) -} - -test('cleared test registry stays disabled and explicitly revokes inherited authorization', () => { - assert.deepEqual(checkRegistry('不启用总控调度\n当前无已登记总控或长期专项\n持续授权同步失效'), []) - assert.ok(checkRegistry('当前无已登记总控或长期专项').length > 0) - assert.ok(checkRegistry('').length > 0) +test('cleared test registry is mandatory and cannot inherit historical task authorization', () => { + for (const token of [ + "Assert-Contains $testTaskRegistry '当前无登记任务'", + "Assert-Contains $testTaskRegistry '清空全部任务登记与调度规则'", + "Assert-Contains $testTaskRegistry '不根据旧登记、历史消息或历史台账自动派工、恢复任务或回传进度'", + "Assert-Contains $testTaskRegistry '不作为当前调度或授权依据'", + "Assert-NotContains $testTaskRegistry '已重新启用测试环境总控调度'", + ]) assert.ok(source.includes(token), token) + assert.match(source, /if \(\$testTaskRegistry -match '\(\?m\)\^\\\|\\s\*`\[0-9a-f-\]\{36\}`\\s\*\\\|'\)/) }) -test('enabled registry requires explicit activation, one controller, unique tasks and its own project', () => { - assert.deepEqual(checkRegistry(enabled), []) - assert.ok(checkRegistry(enabled.replace('已重新启用测试环境总控调度', '')).length > 0) - assert.ok(checkRegistry(enabled.replace('parent_task_id', '')).length > 0) - assert.ok(checkRegistry(enabled.replace(child, root)).length > 0) - assert.ok(checkRegistry(enabled.replace('【前端】专项', '【总控】其他')).length > 0) - assert.ok(checkRegistry(enabled.replaceAll(project, '6f874ec3-d3b2-49bd-8625-327f4d685f98')).length > 0) +test('cleared registry rejects every retired controller and project identity', () => { + for (const retired of [ + '01a0194a-259c-7171-8967-074731a27618', + '01a06c0b-0515-7c60-b329-77342b54e666', + '6f874ec3-d3b2-49bd-8625-327f4d685f98', + ]) assert.match(source, new RegExp(`Assert-NotContains \\$testTaskRegistry '${retired}'`)) }) diff --git a/scripts/finance-deployment-contract.test.mjs b/scripts/finance-deployment-contract.test.mjs index e6f5d0895..17a496a53 100644 --- a/scripts/finance-deployment-contract.test.mjs +++ b/scripts/finance-deployment-contract.test.mjs @@ -25,7 +25,7 @@ test('Compose and deploy script expose finance with fail-closed database config' assert.match(compose, /image: \$\{IMAGE_REPOSITORY\}\/finance:\$\{IMAGE_TAG\}/) assert.match(compose, /SERVER_PORT: 9203/) assert.match(compose, /FINANCE_DB_URL: \$\{FINANCE_DB_URL\}/) - assert.match(compose, /FINANCE_WALLET_DEFAULT_CREDIT_LIMIT: \$\{FINANCE_WALLET_DEFAULT_CREDIT_LIMIT:-0\.00\}/) + assert.doesNotMatch(compose, /FINANCE_WALLET_DEFAULT_CREDIT_LIMIT/) assert.match(serviceMap, /^ finance:\s*$/m) assert.match(serviceMap, /jar: finance\/target\/finance-service-1\.0\.0-SNAPSHOT\.jar/) assert.match(deployScript, /FINANCE_DB_URL FINANCE_DB_USERNAME FINANCE_DB_PASSWORD/) diff --git a/scripts/synchronous-dependency-baseline.mjs b/scripts/synchronous-dependency-baseline.mjs index efd9731c1..0f7792b31 100644 --- a/scripts/synchronous-dependency-baseline.mjs +++ b/scripts/synchronous-dependency-baseline.mjs @@ -121,6 +121,8 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([ feign('order/src/main/java/com/baiyue/order/client/FirstlegColoadGroupClient.java', 'order-service', 'firstleg-service', ['REQUEST'], 'READ', 'SAFE_READ', '批量读取同柜不可拆组聚合'), + feign('order/src/main/java/com/baiyue/order/client/FinanceWalletClient.java', + 'order-service', 'finance-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 收敛'), diff --git a/scripts/synchronous-dependency-contract.test.mjs b/scripts/synchronous-dependency-contract.test.mjs index cf322b3aa..1eb2db488 100644 --- a/scripts/synchronous-dependency-contract.test.mjs +++ b/scripts/synchronous-dependency-contract.test.mjs @@ -37,6 +37,7 @@ const expectedOnlineEdges = [ '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', @@ -98,11 +99,11 @@ async function discoverFeignEdges() { return clients.sort((left, right) => left.path.localeCompare(right.path)) } -test('all 56 Feign clients have reviewed context, access and idempotency metadata', async () => { +test('all 57 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, 56) + assert.equal(reviewed.length, 57) assert.deepEqual(await discoverFeignEdges(), reviewed) }) @@ -193,7 +194,7 @@ test('REQUEST, BACKGROUND and full graphs remain separate executable baselines', ]) assert.deepEqual(stronglyConnectedComponents(graphEdges('REQUEST')), [ ['auth-service', 'iam-service'], - ['firstleg-service', 'order-service'] + ['finance-service', 'firstleg-service', 'order-service'] ]) assert.deepEqual(stronglyConnectedComponents(graphEdges('BACKGROUND')), [[ 'event-bridge-service', 'finance-service', 'firstleg-service', 'lastleg-service', 'order-service' @@ -210,7 +211,7 @@ test('overseas security projection stays in Lastleg infrastructure and backgroun assert.ok(graphEdges('BACKGROUND').includes('event-bridge-service->lastleg-service')) }) -test('Order and Firstleg remain a bilateral REQUEST SCC until loading-complete timing is frozen', () => { +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') @@ -230,7 +231,8 @@ test('Order and Firstleg remain a bilateral REQUEST SCC until loading-complete t '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('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', () => { @@ -246,13 +248,13 @@ test('known user entries expose every longest simple REQUEST path over the two-h 'iam-service->auth-service', 'iam-service->warehouse-service' ] }, - 'order-service': { hops: 4, paths: [ - 'order-service->firstleg-service->file-service->iam-service->auth-service', - 'order-service->firstleg-service->file-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: 4, paths: [ - 'firstleg-service->order-service->file-service->iam-service->auth-service', - 'firstleg-service->order-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',