This commit is contained in:
2026-10-21 21:30:24 +08:00
parent 234216b3eb
commit 5356a8c898
28 changed files with 1543 additions and 45 deletions
+49 -2
View File
@@ -57,6 +57,8 @@ export function planA0LocationIsolation(locations, requiredSlots = 1,
}
const MODULE_DEPENDENCIES = new Map([
['customers', ['merchant-qualification-fixture']],
['overseas-storage-fees', ['merchant-qualification-fixture']],
['inbound-weight-ocr', ['orders']],
['orders', ['merchant-qualification-fixture']],
['phase5-root-fulfillment', ['merchant-qualification-fixture']],
@@ -406,6 +408,11 @@ export function parseArguments(argv) {
const requestedModules = (values.get('modules') || DEFAULT_MODULES.join(','))
.split(',').map((v) => v.trim()).filter(Boolean)
const modules = normalizeModuleOrder(requestedModules)
if (environment === 'test') {
const fixtureIndex = modules.indexOf('merchant-qualification-fixture')
if (fixtureIndex >= 0) modules.splice(fixtureIndex, 1)
modules.unshift('merchant-qualification-fixture')
}
const merchantUsername = values.get('merchant-username') || envValue('MERCHANT_TEST_USERNAME', 'MERCHANT_GRAY_USERNAME')
const merchantPassword = values.get('merchant-password') || envValue('MERCHANT_TEST_PASSWORD', 'MERCHANT_GRAY_PASSWORD')
const requiresMarkerOwnedMerchantFixture = [
@@ -417,7 +424,7 @@ export function parseArguments(argv) {
'phase4-overseas-finance-cross-region',
'phase4-overseas-wallet-and-pallet-cost',
].some((module) => modules.includes(module))
if (merchantUsername && merchantPassword && !requiresMarkerOwnedMerchantFixture) {
if (environment !== 'test' && merchantUsername && merchantPassword && !requiresMarkerOwnedMerchantFixture) {
const fixtureIndex = modules.indexOf('merchant-qualification-fixture')
if (fixtureIndex >= 0) modules.splice(fixtureIndex, 1)
}
@@ -431,6 +438,7 @@ export function parseArguments(argv) {
pdaPassword: values.get('pda-password') || envValue('ADMIN_PDA_PASSWORD', 'ADMIN_GRAY_PDA_PASSWORD'),
modules,
requestedModules,
fixedCustomerId: environment === 'test' ? process.env.ADMIN_TEST_FIXED_CUSTOMER_ID || '' : '',
customerName: values.get('customer-name') || envValue('ADMIN_TEST_CUSTOMER_NAME', 'ADMIN_GRAY_CUSTOMER_NAME'),
merchantUsername,
merchantPassword,
@@ -629,7 +637,12 @@ export function buildAcceptanceSummary(steps = [], modules = [], coverageGaps =
export class AcceptanceRun {
constructor(options) {
assertGrayExecutionScope(options)
this.options = options
this.options = { ...options }
if (options.environment === 'test') {
this.options.fixedCustomerId ||= process.env.ADMIN_TEST_FIXED_CUSTOMER_ID || ''
this.options.merchantUsername ||= process.env.MERCHANT_TEST_USERNAME || ''
this.options.merchantPassword ||= process.env.MERCHANT_TEST_PASSWORD || ''
}
const markerNamespace = String(options.markerNamespace || '').replace(/[^A-Za-z0-9_-]/g, '').slice(0, 12)
this.marker = `${options.environment === 'gray' ? 'GRAY' : 'ADMIN'}-API-${new Date().toISOString().replace(/\D/g, '').slice(0, 17)}${markerNamespace ? `-${markerNamespace}` : ''}`
this.startedAt = new Date().toISOString()
@@ -648,7 +661,17 @@ export class AcceptanceRun {
this.fatal = null
}
assertCustomerWriteAllowed(method, path) {
if (this.options.environment !== 'test') return
const pathname = new URL(path, 'https://acceptance.invalid').pathname.replace(/\/$/, '')
if (String(method).toUpperCase() === 'POST' && (pathname === '/api/platform/v1/iam/tenants'
|| /^\/api\/platform\/v1\/iam\/tenants\/[^/]+\/(archive|submit-review)$/.test(pathname))) {
throw new Error('TEST 固定客户模式禁止创建、归档客户或重提资质;请复用已配置客户')
}
}
async request(method, path, body = undefined, token = this.token, extraHeaders = {}) {
this.assertCustomerWriteAllowed(method, path)
const headers = { Accept: 'application/json', ...extraHeaders }
if (this.pdaToken && /\/pda-loading\/tasks\/\d+/.test(String(path))) token = this.pdaToken
if (token) headers.Authorization = `Bearer ${token}`
@@ -691,6 +714,7 @@ export class AcceptanceRun {
}
async requestWithBody(method, path, body, token = this.token, extraHeaders = {}) {
this.assertCustomerWriteAllowed(method, path)
const headers = { Accept: 'application/json', ...extraHeaders }
if (token) headers.Authorization = `Bearer ${token}`
if (!(body instanceof FormData) && body !== undefined) headers['Content-Type'] = 'application/json; charset=utf-8'
@@ -793,6 +817,14 @@ export class AcceptanceRun {
}
async executeModule(name, handler) {
if (this.options.fixedCustomerId && new Set(['customers', 'supplemental-boundaries', 'system', 'master-data', 'suppliers', 'notifications',
'pricing', 'phase4-overseas-master-data', 'phase4-overseas-wallet-and-pallet-cost']).has(name)) {
this.addCoverageGap(name, 'fixed-resource-lifecycle', '固定资源模式排除新增主档及账户生命周期',
'业务流程复用现有资源;不执行主档创建、账号创建、价格发布或钱包初始化', 'not-implemented', { blocking: false })
this.modules.push({ name, success: false, skipped: true, passed: 0, failed: 0,
reason: '固定资源模式排除生命周期;不计为验收通过' })
return
}
const startedAt = new Date().toISOString(); const start = this.steps.length; let fatal = null
try { await handler() } catch (error) {
fatal = error instanceof Error ? error.message : String(error)
@@ -1124,6 +1156,12 @@ export class AcceptanceRun {
}
async runCustomers() {
if (this.options.fixedCustomerId) {
this.context.customerId = this.context.merchantQualificationFixture?.tenantId
this.addCoverageGap('customers', 'customer-lifecycle', '按用户要求排除建客及归档生命周期',
'复用固定客户;不改其资质、状态和账户,不把该生命周期列为通过', 'not-implemented', {blocking:false})
return
}
const module = 'customers'; const phone = await this.findAvailablePhone(module); const suffix = this.marker.slice(-12)
const payload = {
tenantName: `后台客户验收-${suffix}`, customerNickname: `后台客户-${suffix}`, legalName: `后台客户验收有限公司-${suffix}`,
@@ -1782,6 +1820,9 @@ export class AcceptanceRun {
}
async run() {
if (this.options.environment === 'test' && !this.options.fixedCustomerId) {
throw new Error('TEST 验收必须配置 ADMIN_TEST_FIXED_CUSTOMER_ID;禁止自动创建测试客户')
}
if (this.options.merchantUsername && this.options.merchantPassword) {
process.env.MERCHANT_TEST_USERNAME = this.options.merchantUsername
process.env.MERCHANT_TEST_PASSWORD = this.options.merchantPassword
@@ -1903,6 +1944,11 @@ export class AcceptanceRun {
} finally {
if (restoreFixture) restoreFixture()
}
if (module === 'merchant-qualification-fixture' && this.options.fixedCustomerId
&& !this.context.merchantQualificationFixture?.reused) {
this.fatal = '固定客户身份校验失败,停止后续造数'
break
}
if (module === 'inbound-measurement'
&& this.captureWaitSplitFixture(this.options.waitSplitSequence || 1)) {
await this.createAdditionalWaitSplitFixtures()
@@ -1962,6 +2008,7 @@ export class AcceptanceRun {
},
generatedAt: new Date().toISOString(), startedAt: this.startedAt,
success: this.fatal === null && summary.failed === 0, fatal: this.fatal,
fullCoverage: this.coverageGaps.length === 0 && !this.modules.some(module => module.skipped),
...(this.options.prepareDomesticPdaH5 ? { fullAcceptance: false, purpose: 'DOMESTIC_PDA_H5_FIXTURE' } : {}),
summary,
modules: this.modules, scenarios: this.scenarios, deploymentBlockers: this.deploymentBlockers,