diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs index 60dd43ca8..3ac58dc79 100644 --- a/scripts/admin-test-env-acceptance.mjs +++ b/scripts/admin-test-env-acceptance.mjs @@ -317,7 +317,7 @@ export function parseArguments(argv) { const modules = normalizeModuleOrder((values.get('modules') || DEFAULT_MODULES.join(',')).split(',').map((v) => v.trim()).filter(Boolean)) const merchantUsername = values.get('merchant-username') || process.env.MERCHANT_TEST_USERNAME || '' const merchantPassword = values.get('merchant-password') || process.env.MERCHANT_TEST_PASSWORD || '' - if (merchantUsername && merchantPassword) { + if (merchantUsername && merchantPassword && !modules.includes('phase4-overseas-business')) { const fixtureIndex = modules.indexOf('merchant-qualification-fixture') if (fixtureIndex >= 0) modules.splice(fixtureIndex, 1) } @@ -1582,6 +1582,19 @@ export class AcceptanceRun { if (this.options.waitSplitCount > 1 || this.options.waitSplitPackageCount > 0 || this.options.orderProfileMode === 'randomized' || this.options.routeMode === 'random') { this.applyWaitSplitVariant(this.options.waitSplitSequence || 1) + } else if (this.options.originWarehouseName || this.options.channelName || this.options.channelCode) { + this.context.waitSplitRoute = { + originWarehouseName: this.options.originWarehouseName || '', + channelName: this.options.channelName || '', + channelCode: this.options.channelCode || '', + randomize: false, + randomIndex: 0, + destinationIndex: 0, + taxModeIndex: 0, + } + // A fixed non-default origin may not carry the Yiwu-only TP-001/YW-TP-002 fixtures. + // Reuse an existing pallet from the selected warehouse while keeping order data deterministic. + this.context.allowAnyExistingPallet = true } const loginResponse = await this.request('POST', '/api/auth/login', { username: this.options.username, password: this.options.password }, '') const login = this.apiSuccess('authentication', '平台管理员登录', loginResponse) diff --git a/scripts/admin-test-env-modules/phase4-overseas-business-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-business-contract.test.mjs index 82436c24a..63a70ecf9 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-business-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-business-contract.test.mjs @@ -210,6 +210,8 @@ test('module is context injected, blocks missing prerequisites and excludes forb assert.doesNotMatch(source, /https?:\/\/|process\.env|fetch\(/) assert.doesNotMatch(source, /addCoverageGap|coverageGap\(/) assert.match(source, /deploymentBlocker: true/) + assert.match(source, /contactPhone: '\d+'/) + assert.doesNotMatch(source, /contactPhone: '\+[^']+'/) for (const forbidden of ['/outbounds', '/receive-pallets', '/outbound', '/delivery', '/dispatch', '/export']) { assert.equal(source.includes(forbidden), false, forbidden) } diff --git a/scripts/admin-test-env-modules/phase4-overseas-business.mjs b/scripts/admin-test-env-modules/phase4-overseas-business.mjs index 0fc381806..41fd98413 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-business.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-business.mjs @@ -56,7 +56,7 @@ function partnerWarehousePayload(context) { return { tenantId: 0, warehouseCode: `P4B-${suffix}`.slice(0, 50), warehouseName: `${context.marker} 采购合作仓`, warehouseType: 'OVERSEAS_PARTNER', countryCode: 'US', timezone: 'UTC-8', contactName: 'Phase4 QA', - contactPhone: '+1-213-555-0177', contactEmail: null, status: 'ACTIVE', remark: `${context.marker}-procurement-fixture`, + contactPhone: '12135550177', contactEmail: null, status: 'ACTIVE', remark: `${context.marker}-procurement-fixture`, address: { addressType: 'WAREHOUSE', countryCode: 'US', stateProvince: 'California', city: 'Los Angeles', district: 'Commerce', postalCode: '90040', addressLine1: '601 Procurement Partner Avenue', addressLine2: null, latitude: null, longitude: null }, diff --git a/scripts/admin-test-env-modules/service-center-flow.mjs b/scripts/admin-test-env-modules/service-center-flow.mjs index 1702f0d5f..eba1206e9 100644 --- a/scripts/admin-test-env-modules/service-center-flow.mjs +++ b/scripts/admin-test-env-modules/service-center-flow.mjs @@ -6,6 +6,16 @@ const TRUCK = '/api/platform/v1/truck-appointments' function id(value) { return value == null ? '' : String(value) } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } +async function requestWithTransientRetry(context, method, path, body) { + let response = null + for (let attempt = 1; attempt <= 3; attempt += 1) { + response = await context.request(method, path, body) + if (response?.http !== 0) return response + if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, 500 * attempt)) + } + return response +} + async function actionRequest(context, path, endpoint, version) { try { const response = await fetch(`${context.options.gateway}${path}/${endpoint}`, { @@ -64,7 +74,7 @@ export async function run(context) { context.assert(MODULE, '已放舱订舱可直接创建订舱级拖车预约', oceanResponse, id(booking?.id) === id(ocean.id) && booking?.status === 'RELEASED', `oceanBookingId=${id(booking?.id) || 'missing'}, status=${booking?.status || 'missing'}`, OPTIONS) - const carrierResponse = await context.request('GET', + const carrierResponse = await requestWithTransientRetry(context, 'GET', '/api/platform/v1/suppliers?tenantId=0&category=TRUCKING&status=ACTIVE&page=1&size=20') const carrierPage = context.apiSuccess(MODULE, '读取 ACTIVE TRUCKING 承运商事实源', carrierResponse, '', OPTIONS) const carrier = carrierPage?.records?.find?.((item) => item?.id && item?.name) diff --git a/scripts/phase4-overseas-acceptance-entry-contract.test.mjs b/scripts/phase4-overseas-acceptance-entry-contract.test.mjs index 11b3dfb67..b4b3256a9 100644 --- a/scripts/phase4-overseas-acceptance-entry-contract.test.mjs +++ b/scripts/phase4-overseas-acceptance-entry-contract.test.mjs @@ -59,6 +59,33 @@ test('entry accepts a separate overseas Gateway without exposing token state', ( assert.match(source, /forbiddenValues = \[this\.options\.password, this\.options\.merchantPassword,[\s\S]*this\.token, this\.refreshToken, this\.overseasToken\]/) }) +test('overseas business keeps its marker-scoped customer fixture with reusable credentials configured', () => { + const previousUsername = process.env.MERCHANT_TEST_USERNAME + const previousPassword = process.env.MERCHANT_TEST_PASSWORD + process.env.MERCHANT_TEST_USERNAME = 'reusable-client' + process.env.MERCHANT_TEST_PASSWORD = 'reusable-password' + try { + const modules = parseArguments(['--modules', 'phase4-overseas-business']).modules + assert.ok(modules.includes('merchant-qualification-fixture')) + assert.ok(modules.indexOf('merchant-qualification-fixture') < modules.indexOf('phase4-overseas-business')) + } finally { + if (previousUsername === undefined) delete process.env.MERCHANT_TEST_USERNAME + else process.env.MERCHANT_TEST_USERNAME = previousUsername + if (previousPassword === undefined) delete process.env.MERCHANT_TEST_PASSWORD + else process.env.MERCHANT_TEST_PASSWORD = previousPassword + } +}) + +test('a fixed origin route does not require randomized order fixtures', () => { + const options = parseArguments(['--origin-warehouse-name', '东莞仓', '--modules', 'phase4-overseas-pda']) + assert.equal(options.originWarehouseName, '东莞仓') + assert.equal(options.routeMode, 'fixed') + assert.equal(options.orderProfileMode, 'fixed') + assert.match(source, /else if \(this\.options\.originWarehouseName \|\| this\.options\.channelName \|\| this\.options\.channelCode\)/) + assert.match(source, /this\.context\.waitSplitRoute = \{[\s\S]*randomize: false/) + assert.match(source, /this\.context\.allowAnyExistingPallet = true/) +}) + 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: '' })