From 055cd29e29be31cbbb9d5498cfa519e17eb4db0a Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sun, 4 Oct 2026 14:40:40 +0800 Subject: [PATCH] fix(test): resolve loading customer credentials per run --- scripts/admin-test-env-modules/README.md | 2 ++ .../phase3-loading-credentials.test.mjs | 31 +++++++++++++++++++ .../admin-test-env-modules/phase3-loading.mjs | 16 +++++++--- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 scripts/admin-test-env-modules/phase3-loading-credentials.test.mjs diff --git a/scripts/admin-test-env-modules/README.md b/scripts/admin-test-env-modules/README.md index 02eb06ead..8a8aed754 100644 --- a/scripts/admin-test-env-modules/README.md +++ b/scripts/admin-test-env-modules/README.md @@ -46,6 +46,8 @@ `phase3-loading.mjs` 的 PDA 原子证据上传在未取得 HTTP 响应时最多尝试三次,冻结原 URL、作业员身份、幂等键、文件名/内容、证据版本和封签号;不重试 HTTP/业务拒绝或已收到响应后的读取失败。`createdResources.loadingEvidenceTransport` 保存各次结果及白名单网络错误码,不保存凭据或原始错误对象。`phase3-evidence-transport-contract.test.mjs` 用持久幂等模型验证响应丢失重放仅一条证据、持续传输失败有界终止及拒绝不重试;离线模型不替代真实 HTTP。 +装柜模块每次执行时读取当前客户凭据,依次选本轮 `merchantQualificationFixture`、显式客户参数、显式加载的环境变量;选中的身份缺用户名或密码时在业务请求前停止,不能借另一客户补齐。不得在模块导入时缓存客户账号;`phase3-loading-credentials.test.mjs` 覆盖同进程切换客户、显式配置、当前环境读取及缺凭据写前拒绝。 + `orders.mjs` 每次运行优先使用当前 `merchantQualificationFixture` 的完整账号对,其次为明确配置;不在模块 import 时缓存客户凭据。当前 fixture 不完整时停止,不与上一轮或环境账号拼接。`orders-session-contract.test.mjs` 在同进程重复调用真实订单登录入口,覆盖连续客户隔离,防止多模式串行时沿用前一轮身份。 `phase4-overseas-pda.mjs` 的收货入口对本轮 `containerBookingId + containerNo` 做有界候选收敛等待(最多 30 次、间隔 1 秒),只有唯一且可操作的原柜才继续;鉴权失败、畸形响应和重复候选立即阻断。它用于等待异步船节点投影,不构成事件重放证据。F40 在成功和失败时均保留 `receivingCandidateEvidence` 的逐次查询身份、结果和诊断信息,不覆盖已有收货会话,不借其他柜或历史柜继续。 diff --git a/scripts/admin-test-env-modules/phase3-loading-credentials.test.mjs b/scripts/admin-test-env-modules/phase3-loading-credentials.test.mjs new file mode 100644 index 000000000..58d3a3063 --- /dev/null +++ b/scripts/admin-test-env-modules/phase3-loading-credentials.test.mjs @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import * as loading from './phase3-loading.mjs' + +test('cached loading module resolves each fresh customer independently', () => { + const context={context:{merchantQualificationFixture:{username:'customer-a',password:'test-a'}},options:{merchantUsername:'configured',merchantPassword:'configured-test'}} + assert.deepEqual(loading.loadingMerchantCredentials(context),{username:'customer-a',password:'test-a'}) + context.context.merchantQualificationFixture={username:'customer-b',password:'test-b'} + assert.deepEqual(loading.loadingMerchantCredentials(context),{username:'customer-b',password:'test-b'}) +}) + +test('explicit credentials take precedence; environment is read at invocation', () => { + const env={MERCHANT_TEST_USERNAME:'env-a',MERCHANT_TEST_PASSWORD:'test-env-a'} + assert.deepEqual(loading.loadingMerchantCredentials({options:{merchantUsername:'configured',merchantPassword:'test-configured'}},env),{username:'configured',password:'test-configured'}) + assert.deepEqual(loading.loadingMerchantCredentials({},env),{username:'env-a',password:'test-env-a'}) + env.MERCHANT_TEST_USERNAME='env-b';env.MERCHANT_TEST_PASSWORD='test-env-b' + assert.deepEqual(loading.loadingMerchantCredentials({},env),{username:'env-b',password:'test-env-b'}) +}) + +test('incomplete selected identity cannot fall back to another customer', () => { + const env={MERCHANT_TEST_USERNAME:'unrelated',MERCHANT_TEST_PASSWORD:'unrelated-test'} + for(const context of [{context:{merchantQualificationFixture:{username:'current'}}},{options:{merchantPassword:'orphan-test'}}]) { + assert.throws(()=>loading.loadingMerchantCredentials(context,env),/完整客户登录凭据/) + } +}) + +test('loading stops before business requests when its current customer lacks credentials', async () => { + let requests=0 + await assert.rejects(loading.run({context:{merchantQualificationFixture:{username:'missing-password'}},options:{},createdResources:{},addStep(){},request:async()=>{requests++;throw Error('business request must not run')}}),/完整客户登录凭据/) + assert.equal(requests,0) +}) diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index 720e549fe..86fc02d2f 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -7,12 +7,19 @@ import { createHash } from 'node:crypto' const MODULE = 'phase3-loading' const OPTIONS = { scenario: 'phase3-split-preload-loading', deploymentBlocker: true } -const MERCHANT_USERNAME = process.env.MERCHANT_TEST_USERNAME || '' -const MERCHANT_PASSWORD = process.env.MERCHANT_TEST_PASSWORD || '' const MATSON_SUPPLIER_NAME = 'MATSON 开发联调船司' const LATE_CONTAINER_GATE_ONLY = process.env.LATE_CONTAINER_GATE_ONLY === '1' function id(value) { return value == null ? '' : String(value) } +export function loadingMerchantCredentials(context, environment = process.env) { + const fixture = context.context?.merchantQualificationFixture + const options = context.options || {} + const selected = fixture || (options.merchantUsername || options.merchantPassword + ? { username: options.merchantUsername, password: options.merchantPassword } + : { username: environment.MERCHANT_TEST_USERNAME, password: environment.MERCHANT_TEST_PASSWORD }) + if (!selected.username || !selected.password) throw new Error('装柜验收当前运行缺少完整客户登录凭据') + return { username: selected.username, password: selected.password } +} export function loadingBookingCustomerFields(scope = 'CUSTOMER', customerId, customerName) { if (scope === 'PLATFORM') return {} if (scope !== 'CUSTOMER') throw new Error('三期订舱范围必须明确为 CUSTOMER 或 PLATFORM') @@ -184,6 +191,7 @@ function taskItemsSignature(task) { } export async function run(context) { + const merchantCredentials = loadingMerchantCredentials(context) const source = fixture(context) const tenantId = id(source?.tenantId) const inboundId = id(source?.inboundId) @@ -940,12 +948,12 @@ export async function run(context) { } const merchantLogin = await context.request('POST', '/api/auth/login', { - loginId: MERCHANT_USERNAME, password: MERCHANT_PASSWORD, + loginId: merchantCredentials.username, password: merchantCredentials.password, }, '') const merchantToken = merchantLogin.http === 200 && codeOf(merchantLogin) === '200' ? merchantLogin.body?.data?.accessToken : null context.addStep(MODULE, '取得客户侧隔离令牌用于 PDA 批次越权验证', merchantLogin, - Boolean(merchantToken), `username=${MERCHANT_USERNAME}`, OPTIONS) + Boolean(merchantToken), 'current-run customer credentials', OPTIONS) const customerIsolatedBatch = merchantToken ? await request(context, 'POST', batchPath, batchPayload, { Authorization: `Bearer ${merchantToken}`,