1
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test environment administrator / 测试环境管理员
|
||||
// Username: platform_admin
|
||||
// Password: ChangeMe123!
|
||||
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import process from 'node:process'
|
||||
|
||||
const DEFAULT_GATEWAY = 'http://8.163.30.128:9000'
|
||||
const DEFAULT_OVERSEAS_GATEWAY = 'http://8.163.30.128:9300'
|
||||
const DEFAULT_USERNAME = 'platform_admin'
|
||||
const DEFAULT_PASSWORD = 'ChangeMe123!'
|
||||
const CONFIGURED_MODULES = ['merchant-qualification-fixture', 'customers', 'overseas-storage-fees', 'system', 'notifications', 'master-data', 'suppliers', 'domestic-container', 'orders', 'fee-items', 'pricing', 'downloads', 'inbound-weight-ocr', 'inbound-measurement', 'inbound-fees', 'phase3-container-loading', 'phase3-container-overview', 'phase3-loading', 'phase3-loading-model-bridge', 'phase3-pda-convergence', 'phase3-evidence-gate', 'supplemental', 'supplemental-boundaries', 'ocean-bookings', 'truck-appointments', 'service-center-flow', 'phase3-loading-finalize', 'phase3-customs', 'phase3-port-schedules', 'phase3-carrier-schedule-queries', 'phase3-outbound-dispatch', 'phase3-outbound-aggregate', 'phase3-dispatch-gate', 'phase3-waiting-for-loading', 'phase3-warehouse-rbac', 'phase3-order-splitting', 'manual-state-matrix', 'phase4-overseas-warehouse', 'phase4-overseas-pda', 'phase4-cross-region-events', 'phase4-overseas-business', 'phase4-overseas-master-data', 'phase4-carrier-nodes', 'finance', 'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-downloads', 'customer-operating-statistics']
|
||||
|
||||
export function normalizeModuleOrder(modules) {
|
||||
@@ -65,6 +57,13 @@ export function normalizeModuleOrder(modules) {
|
||||
&& !normalized.includes('merchant-qualification-fixture')) {
|
||||
normalized.splice(normalized.indexOf('phase4-overseas-business'), 0, 'merchant-qualification-fixture')
|
||||
}
|
||||
if (normalized.includes('phase4-overseas-pda')) {
|
||||
const prerequisites = ['merchant-qualification-fixture', 'phase3-loading', 'phase3-pda-convergence',
|
||||
'phase3-evidence-gate', 'phase3-loading-finalize']
|
||||
const targetIndex = normalized.indexOf('phase4-overseas-pda')
|
||||
const missing = prerequisites.filter((name) => !normalized.includes(name))
|
||||
if (missing.length > 0) normalized.splice(targetIndex, 0, ...missing)
|
||||
}
|
||||
if (normalized.includes('phase4-cross-region-events')) {
|
||||
const prerequisites = ['system', 'merchant-qualification-fixture', 'orders', 'inbound-measurement',
|
||||
'phase3-container-loading', 'phase3-loading', 'phase3-pda-convergence', 'phase3-evidence-gate',
|
||||
@@ -130,7 +129,7 @@ export function normalizeModuleOrder(modules) {
|
||||
}
|
||||
|
||||
const DEFAULT_MODULES = normalizeModuleOrder(CONFIGURED_MODULES)
|
||||
const WEIGHT_IMAGE_PATH = 'C:/Users/BY-OY/Desktop/上传图片包/测量图片/测量图片/orig_1cf27695-b2c3-44b3-98e3-c0be8b80f79a.JPG'
|
||||
const WEIGHT_IMAGE_PATH = process.env.ADMIN_TEST_WEIGHT_IMAGE_PATH || ''
|
||||
const CUSTOMER_FIELDS = [
|
||||
'id', 'tenantCode', 'tenantName', 'status', 'legalName', 'legalRepresentative',
|
||||
'contactName', 'contactPhone', 'contactEmail', 'contactWechat',
|
||||
@@ -148,21 +147,20 @@ const BOX_DEFAULTS = [
|
||||
function usage() {
|
||||
console.log(`后台管理测试环境 HTTP API 验收
|
||||
|
||||
测试管理员: ${DEFAULT_USERNAME} / ${DEFAULT_PASSWORD}
|
||||
|
||||
用法:
|
||||
node scripts/admin-test-env-acceptance.mjs [options]
|
||||
|
||||
选项:
|
||||
--gateway <url> Gateway 地址,默认 ${DEFAULT_GATEWAY}
|
||||
--overseas-gateway <url> 海外 Gateway 地址,默认 ${DEFAULT_OVERSEAS_GATEWAY}
|
||||
--username <name> 管理员账号,默认 ${DEFAULT_USERNAME}
|
||||
--password <value> 管理员密码,默认使用上方测试密码
|
||||
--gateway <url> 必填,国内 Gateway 地址
|
||||
--overseas-gateway <url> 四期海外模块必填,海外 Gateway 地址
|
||||
--username <name> 必填,管理员账号
|
||||
--password <value> 必填,管理员密码
|
||||
--modules <names> 逗号分隔,默认 ${DEFAULT_MODULES.join(',')}
|
||||
--output <path> JSON 报告路径,默认 reports/
|
||||
--help 显示帮助
|
||||
|
||||
环境变量覆盖: ADMIN_TEST_GATEWAY, ADMIN_OVERSEAS_GATEWAY, ADMIN_TEST_USERNAME, ADMIN_TEST_PASSWORD`)
|
||||
也可使用环境变量: ADMIN_TEST_GATEWAY, ADMIN_OVERSEAS_GATEWAY, ADMIN_TEST_USERNAME, ADMIN_TEST_PASSWORD,
|
||||
ADMIN_TEST_WEIGHT_IMAGE_PATH`)
|
||||
}
|
||||
|
||||
export function parseArguments(argv) {
|
||||
@@ -182,15 +180,26 @@ export function parseArguments(argv) {
|
||||
}
|
||||
}
|
||||
return {
|
||||
gateway: (values.get('gateway') || process.env.ADMIN_TEST_GATEWAY || DEFAULT_GATEWAY).replace(/\/$/, ''),
|
||||
overseasGateway: (values.get('overseas-gateway') || process.env.ADMIN_OVERSEAS_GATEWAY || DEFAULT_OVERSEAS_GATEWAY).replace(/\/$/, ''),
|
||||
username: values.get('username') || process.env.ADMIN_TEST_USERNAME || DEFAULT_USERNAME,
|
||||
password: values.get('password') || process.env.ADMIN_TEST_PASSWORD || DEFAULT_PASSWORD,
|
||||
gateway: (values.get('gateway') || process.env.ADMIN_TEST_GATEWAY || '').replace(/\/$/, ''),
|
||||
overseasGateway: (values.get('overseas-gateway') || process.env.ADMIN_OVERSEAS_GATEWAY || '').replace(/\/$/, ''),
|
||||
username: values.get('username') || process.env.ADMIN_TEST_USERNAME || '',
|
||||
password: values.get('password') || process.env.ADMIN_TEST_PASSWORD || '',
|
||||
modules: normalizeModuleOrder((values.get('modules') || DEFAULT_MODULES.join(',')).split(',').map((v) => v.trim()).filter(Boolean)),
|
||||
output: values.get('output') || '',
|
||||
}
|
||||
}
|
||||
|
||||
export function validateExecutionOptions(options) {
|
||||
for (const [name, value] of [['gateway', options.gateway], ['username', options.username], ['password', options.password]]) {
|
||||
if (typeof value !== 'string' || value.trim() === '') throw new Error(`缺少必填执行参数: ${name}`)
|
||||
}
|
||||
if (options.modules.some((module) => module.startsWith('phase4-overseas-'))
|
||||
&& (!options.overseasGateway || options.overseasGateway.trim() === '')) {
|
||||
throw new Error('四期海外模块缺少必填执行参数: overseas-gateway')
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
function own(value, field) { return value !== null && typeof value === 'object' && Object.hasOwn(value, field) }
|
||||
function hasFields(value, fields) { return fields.every((field) => own(value, field)) }
|
||||
function numberEquals(actual, expected) { return Math.abs(Number(actual) - Number(expected)) < 0.0005 }
|
||||
@@ -383,7 +392,13 @@ export class AcceptanceRun {
|
||||
}
|
||||
|
||||
addCoverageGap(module, scenario, name, reason, category = 'not-implemented', metadata = {}) {
|
||||
this.coverageGaps.push({ module, scenario, name, reason, category, ...metadata })
|
||||
const nonBlocking = new Set(['device', 'external-third-party', 'visual'])
|
||||
const blocking = metadata.blocking ?? !nonBlocking.has(category)
|
||||
this.coverageGaps.push({ module, scenario, name, reason, category, blocking, ...metadata })
|
||||
if (blocking) {
|
||||
this.addStep(module, `覆盖阻断:${name}`, null, false, reason,
|
||||
{ scenario, deploymentBlocker: true })
|
||||
}
|
||||
}
|
||||
|
||||
apiSuccess(module, name, response, evidence = '', options = {}) {
|
||||
@@ -757,9 +772,9 @@ export class AcceptanceRun {
|
||||
}
|
||||
}
|
||||
|
||||
async preparePhase3LoadingFixture() {
|
||||
async preparePhase3LoadingFixture(suffix = '') {
|
||||
const originalMarker = this.marker
|
||||
const fixtureMarker = `${originalMarker}-LOAD`
|
||||
const fixtureMarker = `${originalMarker}-LOAD${suffix}`
|
||||
const createdKeys = ['orders', 'ocrInbound', 'testInbound', 'inboundMeasurement']
|
||||
const contextKeys = ['inboundWeightOcr', 'ocrInbound', 'testInbound', 'createdInbound',
|
||||
'phase3LoadingRequireSharedOverflow', 'phase3LoadingIncrementalPackageCount']
|
||||
@@ -798,7 +813,7 @@ export class AcceptanceRun {
|
||||
internalRemark: fixtureOrder.internalRemark,
|
||||
cargoRemark: fixtureOrder.cargoRemark,
|
||||
} : { marker: originalMarker, sourceMarker: fixtureMarker, failed: true }
|
||||
const incrementalMarker = `${originalMarker}-LOAD-INC`
|
||||
const incrementalMarker = `${originalMarker}-LOAD${suffix}-INC`
|
||||
this.marker = incrementalMarker
|
||||
this.context.phase3LoadingRequireSharedOverflow = false
|
||||
this.context.phase3LoadingIncrementalPackageCount = 2
|
||||
@@ -834,6 +849,59 @@ export class AcceptanceRun {
|
||||
}
|
||||
}
|
||||
|
||||
async runPhase4ProducerVariant(label, destinationType) {
|
||||
const matrixMarker = this.marker
|
||||
const variantMarker = `${matrixMarker}-P4-${label}`
|
||||
const contextKeys = ['orderDestinationType', 'phase4ProducerMatrixMarker']
|
||||
const createdKeys = ['phase3Loading', 'phase3LoadingCompletedProducer']
|
||||
const contextSnapshot = new Map(contextKeys.map((key) => [key, {
|
||||
present: Object.hasOwn(this.context, key), value: this.context[key],
|
||||
}]))
|
||||
const createdSnapshot = new Map(createdKeys.map((key) => [key, {
|
||||
present: Object.hasOwn(this.createdResources, key), value: this.createdResources[key],
|
||||
}]))
|
||||
let restoreFixture = null
|
||||
this.marker = variantMarker
|
||||
this.context.orderDestinationType = destinationType
|
||||
this.context.phase4ProducerMatrixMarker = matrixMarker
|
||||
try {
|
||||
restoreFixture = await this.preparePhase3LoadingFixture()
|
||||
const loading = await import(new URL('./admin-test-env-modules/phase3-loading.mjs', import.meta.url))
|
||||
await this.executeModule(`phase3-loading-phase4-${label}`, () => loading.run(this))
|
||||
const finalize = await import(new URL('./admin-test-env-modules/phase3-loading-finalize.mjs', import.meta.url))
|
||||
await this.executeModule(`phase3-loading-finalize-phase4-${label}`, () => finalize.run(this))
|
||||
} finally {
|
||||
if (restoreFixture) restoreFixture()
|
||||
this.marker = matrixMarker
|
||||
for (const [key, snapshot] of contextSnapshot) {
|
||||
if (snapshot.present) this.context[key] = snapshot.value
|
||||
else delete this.context[key]
|
||||
}
|
||||
for (const [key, snapshot] of createdSnapshot) {
|
||||
if (snapshot.present) this.createdResources[key] = snapshot.value
|
||||
else delete this.createdResources[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async ensurePhase4PdaProducerMatrix() {
|
||||
const readiness = () => this.createdResources.phase4InboundProducerFixtures?.readiness || {}
|
||||
if (Number(readiness().fbaContainerCount || 0) < 1) {
|
||||
await this.runPhase4ProducerVariant('FBA-PRIMARY', 'FBA')
|
||||
}
|
||||
if (Number(readiness().overseasContainerCount || 0) < 1) {
|
||||
await this.runPhase4ProducerVariant('OVERSEAS-PRIMARY', 'OVERSEAS')
|
||||
}
|
||||
if (Number(readiness().distinctContainerCount || 0) < 3) {
|
||||
await this.runPhase4ProducerVariant('SECONDARY', 'FBA')
|
||||
}
|
||||
const matrix = this.createdResources.phase4InboundProducerFixtures
|
||||
this.assert('phase4-overseas-pda-fixture', '本轮三期跨区生产矩阵已就绪', null,
|
||||
matrix?.marker === this.marker && matrix?.readiness?.readyForPhase4Pda === true,
|
||||
`marker=${matrix?.marker || 'missing'}, FBA=${matrix?.readiness?.fbaContainerCount ?? 0}, OVERSEAS=${matrix?.readiness?.overseasContainerCount ?? 0}, containers=${matrix?.readiness?.distinctContainerCount ?? 0}`,
|
||||
{ scenario: 'phase3-phase4-producer-fixture', deploymentBlocker: true })
|
||||
}
|
||||
|
||||
async disableAvailableLocationsForA0(warehouseId) {
|
||||
const module = 'phase3-loading-fixture'
|
||||
const options = { scenario: 'phase3-loading-a0-fixture', deploymentBlocker: true }
|
||||
@@ -912,6 +980,15 @@ export class AcceptanceRun {
|
||||
continue
|
||||
}
|
||||
let restoreFixture = null
|
||||
if (module === 'phase4-overseas-pda') {
|
||||
try {
|
||||
await this.ensurePhase4PdaProducerMatrix()
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error)
|
||||
this.addStep('phase4-overseas-pda-fixture', '创建FBA/OVERSEAS/第二柜三期生产矩阵', null,
|
||||
false, reason, { scenario: 'phase3-phase4-producer-fixture', deploymentBlocker: true })
|
||||
}
|
||||
}
|
||||
const ocrIndex = this.options.modules.indexOf('inbound-weight-ocr')
|
||||
if (module === 'inbound-measurement' && ocrIndex >= 0 && ocrIndex < moduleIndex) {
|
||||
try {
|
||||
@@ -1001,7 +1078,7 @@ export class AcceptanceRun {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArguments(process.argv.slice(2))
|
||||
const options = validateExecutionOptions(parseArguments(process.argv.slice(2)))
|
||||
const timestamp = new Date().toISOString().replace(/\D/g, '').slice(0, 14)
|
||||
const outputPath = resolve(options.output || `reports/admin-test-env-acceptance-${timestamp}.json`)
|
||||
const report = await new AcceptanceRun(options).run()
|
||||
|
||||
Reference in New Issue
Block a user