test: prepare isolated gray domestic PDA H5 tasks without auto-picking
This commit is contained in:
@@ -205,6 +205,9 @@ Client 全量、Client 入库测量与计费、待审核订单和批量待分柜
|
||||
|
||||
### 灰度环境 FBA 定向闭环
|
||||
|
||||
同一入口可显式添加 `--prepare-domestic-pda-h5 true`,仅允许单独指定 `phase5-root-fulfillment`:创建本轮客户和两箱根订单,经测量、分柜、订舱放舱和计划确认生成一箱待拣任务及箱唛后停止,不领取/拣货/完成任务、不进入海外链。报告标记 `fullAcceptance=false`、`PREPARED_NOT_VERIFIED`,`createdResources.phase5RootFulfillment.domesticPdaH5Fixture` 保存任务、柜次、计划、仓库、入仓来源及箱信息供 H5;成功只代表准备完成。样本保留,失败不自动重试或清理,默认不传该参数时继续完整链。
|
||||
|
||||
|
||||
`admin-test-env-acceptance.mjs --environment gray` 显式支持临时灰度环境,当前仅开放 `--modules phase5-root-fulfillment` 及自动展开的本轮客户资质 fixture。该链会通过正式 HTTP 新建隔离客户、两箱根订单及分柜子来源,推进国内测量、装柜、报关、人工船节点、海外收货、排车、出库和签收;不是默认全量验收。默认全量中包含 TEST 专用故障注入和未经灰度副作用核对的模块,因此 gray 拒绝省略模块、附加模块、批量订单和卸柜故障开关,也不会创建 TEST fault executor。`--f40-tax-mode TAX_REFUND` 覆盖退税文件确认链;真实邮件和供应商自动查询不在此闭环的完成声明中。
|
||||
|
||||
明确的仓库外环境文件须配置 `ADMIN_GRAY_GATEWAY`(统一 HTTPS 源站,不带 `/api`)、`ADMIN_GRAY_USERNAME`、`ADMIN_GRAY_PASSWORD`、`ADMIN_GRAY_PDA_USERNAME`、`ADMIN_GRAY_PDA_PASSWORD`;可选 `ADMIN_GRAY_CUSTOMER_NAME`。gray 不读取 TEST Gateway、管理员或 PDA 凭据。F40 始终创建本轮客户,不需要传已有 merchant 账号。数据库只读取证使用 `JENKINS_URL`、`JENKINS_USER`、`JENKINS_API_TOKEN` 及 Jenkins 已登记的 production SSH/host/known-hosts/env 凭据,由 `gray-regression-evidence.mjs` 实现,不调用 TEST agent 或写业务 SQL。
|
||||
|
||||
@@ -313,6 +313,7 @@ function usage() {
|
||||
--merchant-username <name> 复用既有已审核客户账号,不再创建客户
|
||||
--merchant-password <value> 复用客户账号密码,不写入报告
|
||||
--origin-warehouse-name <name> 固定国内起运仓名称
|
||||
--prepare-domestic-pda-h5 true 仅准备新待拣任务供H5,不执行完整履约验收
|
||||
--channel-name <name> 固定物流渠道名称
|
||||
--channel-code <code> 固定物流渠道编码
|
||||
--order-profile-mode <mode> 订单档案模式:fixed 或 randomized,默认fixed
|
||||
@@ -408,6 +409,7 @@ export function parseArguments(argv) {
|
||||
const fixtureIndex = modules.indexOf('merchant-qualification-fixture')
|
||||
if (fixtureIndex >= 0) modules.splice(fixtureIndex, 1)
|
||||
}
|
||||
if (values.has('prepare-domestic-pda-h5') && values.get('prepare-domestic-pda-h5') !== 'true') throw new Error('prepare-domestic-pda-h5 必须明确为 true;完整验收请省略此参数')
|
||||
return {
|
||||
environment,
|
||||
gateway: (values.get('gateway') || envValue('ADMIN_TEST_GATEWAY', 'ADMIN_GRAY_GATEWAY')).replace(/\/$/, ''),
|
||||
@@ -437,6 +439,7 @@ export function parseArguments(argv) {
|
||||
returnMainChainOnly: values.get('return-main-chain-only') === 'true',
|
||||
verifyUnloadEventRecovery: values.get('verify-unload-event-recovery') === 'true',
|
||||
f40TaxMode: values.get('f40-tax-mode') || '',
|
||||
prepareDomesticPdaH5: values.get('prepare-domestic-pda-h5') === 'true',
|
||||
manualSplitFixtureReport: values.get('manual-split-fixture-report') || '',
|
||||
manualSplitPlanId: values.get('manual-split-plan-id') || '',
|
||||
manualPreloadSourceReport: values.get('manual-preload-source-report') || '',
|
||||
@@ -465,6 +468,7 @@ export function validateExecutionOptions(options) {
|
||||
}
|
||||
if (options.environment && !['test', 'gray'].includes(options.environment)) throw new Error('Only test or explicit gray acceptance is supported')
|
||||
if (options.f40TaxMode && !['BUY_DECLARATION', 'TAX_REFUND'].includes(options.f40TaxMode)) throw new Error('f40-tax-mode 必须为 BUY_DECLARATION 或 TAX_REFUND')
|
||||
if (options.prepareDomesticPdaH5 && (options.requestedModules?.length !== 1 || options.requestedModules[0] !== 'phase5-root-fulfillment')) throw new Error('prepare-domestic-pda-h5 仅允许单独指定 phase5-root-fulfillment')
|
||||
if ('overseasGateway' in options) throw new Error('Use the single gateway option')
|
||||
if (options.modules.some((module) => ['phase3-loading', 'phase3-container-loading', 'phase5-root-fulfillment'].includes(module))
|
||||
&& (!options.pdaUsername?.trim() || !options.pdaPassword?.trim())) {
|
||||
@@ -624,7 +628,7 @@ export class AcceptanceRun {
|
||||
this.phase4ProducerAttempts = new Map()
|
||||
this.deploymentBlockers = []
|
||||
this.coverageGaps = []
|
||||
this.context = options.f40TaxMode ? { f40TaxMode: options.f40TaxMode } : {}
|
||||
this.context = { ...(options.f40TaxMode ? { f40TaxMode: options.f40TaxMode } : {}), ...(options.prepareDomesticPdaH5 ? { prepareDomesticPdaH5: true } : {}) }
|
||||
this.token = ''
|
||||
this.pdaToken = ''
|
||||
this.refreshToken = ''
|
||||
@@ -1932,10 +1936,11 @@ export class AcceptanceRun {
|
||||
|
||||
const summary = buildAcceptanceSummary(this.steps, this.modules, this.coverageGaps)
|
||||
return {
|
||||
reportName: this.options.environment === 'gray' ? '灰度环境 FBA 定向闭环 HTTP API 验收' : '后台管理测试环境 HTTP API 验收', marker: this.marker,
|
||||
reportName: this.options.prepareDomesticPdaH5 ? '国内 PDA H5 待拣任务准备(非全链路验收)' : this.options.environment === 'gray' ? '灰度环境 FBA 定向闭环 HTTP API 验收' : '后台管理测试环境 HTTP API 验收', marker: this.marker,
|
||||
environment: this.options.environment || 'test', gateway: this.options.gateway,
|
||||
generatedAt: new Date().toISOString(), startedAt: this.startedAt,
|
||||
success: this.fatal === null && summary.failed === 0, fatal: this.fatal,
|
||||
...(this.options.prepareDomesticPdaH5 ? { fullAcceptance: false, purpose: 'DOMESTIC_PDA_H5_FIXTURE' } : {}),
|
||||
summary,
|
||||
modules: this.modules, scenarios: this.scenarios, deploymentBlockers: this.deploymentBlockers,
|
||||
coverageGaps: this.coverageGaps,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Admin 测试环境 HTTP 模块
|
||||
|
||||
`phase5-root-fulfillment` 可由总入口显式 `--prepare-domestic-pda-h5 true` 仅准备本轮国内H5待拣任务。调用 `phase3-order-splitting.loadOneFragment` 到正式计划确认、待拣任务及箱唛就绪即返回,不领取、不拣货、不继续海外;结果固定 `PREPARED_NOT_VERIFIED`,不算全链验收。默认完整链不变,范围及授权见根脚本README。
|
||||
|
||||
`fleet-capacity-pool-schema-contract.test.mjs` 是离线数据库合同:验证发布与持久命令唯一约束、唯一商业履约来源、可空实际数量、服务与统一基线一致、非破坏性结构补齐及独立销售/调度权限;不访问环境、不证明真实履约已通过。
|
||||
|
||||
## 当前新增脚本的执行边界
|
||||
|
||||
@@ -136,3 +136,16 @@ test('booking fixture never invents supplier names without formal IDs', () => {
|
||||
assert.doesNotMatch(source, /SPLIT-SHIPPING|SPLIT-AGENT/)
|
||||
assert.doesNotMatch(source, /shippingCompanyName: `|bookingAgentName: `/)
|
||||
})
|
||||
|
||||
|
||||
test('H5 preparation stops after real labels but before claim and refuses already picked data', () => {
|
||||
const checkpoint = source.indexOf('if (context.context?.prepareDomesticPdaH5)')
|
||||
const claim = source.indexOf('const claim = await request', checkpoint)
|
||||
assert.ok(checkpoint > source.indexOf('const labelResponse ='))
|
||||
assert.ok(claim > checkpoint)
|
||||
const boundary = source.slice(checkpoint, claim)
|
||||
assert.match(boundary, /pickedPackageCount\) !== 0/)
|
||||
assert.match(boundary, /throw new Error/)
|
||||
assert.match(boundary, /return \{ preparedForH5: true/)
|
||||
assert.doesNotMatch(boundary, /request\(/)
|
||||
})
|
||||
|
||||
@@ -392,6 +392,14 @@ export async function loadOneFragment(context, state) {
|
||||
`HTTP=${labelResponse.http}, contentType=${labelResponse.contentType || 'missing'}, bytes=${labelResponse.bytes?.length ?? 0}`, OPTIONS)
|
||||
if (labelResponse.http !== 200 || !Number(labelResponse.bytes?.length)) return null
|
||||
|
||||
if (context.context?.prepareDomesticPdaH5) {
|
||||
if (task?.taskStatus === 'COMPLETED' || !task.items?.length || task.items.some(row => Number(row.pickedPackageCount) !== 0))
|
||||
throw new Error('H5准备只允许尚未拣货的新任务')
|
||||
return { preparedForH5: true, taskId, planId, containerId, oceanBookingId,
|
||||
warehouseId, sourceInboundId: id(candidate.inboundId), sourceOrderId,
|
||||
containerNo: realContainerNo, taskNo: task.taskNo, items: task.items }
|
||||
}
|
||||
|
||||
const claim = await request(context, 'POST', `${PDA}/tasks/${taskId}/claim`, {
|
||||
expectedAssignmentVersion: 0, operationId: `${operationBase}-claim`,
|
||||
})
|
||||
|
||||
@@ -400,6 +400,7 @@ async function fragment(context, source, rootOrderId, sequence) {
|
||||
warehouseId: id(summary.warehouseId), warehouseName: summary.warehouseName,
|
||||
sourcePackageCount: sequence === 0 ? 2 : 1, sequence,
|
||||
})
|
||||
if (context.context?.prepareDomesticPdaH5 && result?.preparedForH5) return result
|
||||
check(context, 'F40 一箱来源通过真实 PDA 完成装柜', Boolean(result?.taskId && result?.sourceInboundId), `sequence=${sequence}`)
|
||||
const current = await api(context, 'GET', '回查该片真实入仓来源',
|
||||
query(`/api/platform/inbounds/${result.sourceInboundId}`, { tenantId: id(summary.tenantId) }))
|
||||
@@ -618,6 +619,14 @@ async function runRootFulfillment(context) {
|
||||
&& Number(scope.createdResources.inboundMeasurement.actualPackageCount) === 2, `root=${rootOrderId}`)
|
||||
proof.currentStage = 'ROOT_LOAD_AND_SHIP'
|
||||
const first = await fragment(scope, rootInbound, rootOrderId, 0)
|
||||
if (scope.context.prepareDomesticPdaH5) {
|
||||
check(scope, '国内H5待拣任务已准备且未执行拣货', first?.preparedForH5 === true && Boolean(first.taskId), 'PREPARED_NOT_VERIFIED')
|
||||
proof.domesticPdaH5Fixture = first
|
||||
proof.finalStatus = 'PREPARED_NOT_VERIFIED'
|
||||
proof.currentStage = 'DOMESTIC_PDA_H5_READY'
|
||||
proof.cleanup = 'Fresh customer/order/container/task retained for manual H5 execution; no picking, completion or overseas work executed'
|
||||
return proof
|
||||
}
|
||||
proof.fragments.push(first)
|
||||
proof.currentStage = 'REMAINDER_IDENTITY'
|
||||
const tree = await api(scope, 'GET', '首柜 PDA 完成后读取正式余量树',
|
||||
|
||||
@@ -86,3 +86,14 @@ test('direct gray evidence use cannot bypass module/fault restrictions or failed
|
||||
})), /gray SSH unavailable/)
|
||||
assert.equal(run.context.phase4ReceivingEvidenceReader, undefined)
|
||||
})
|
||||
|
||||
|
||||
test('domestic H5 fixture is opt-in and cannot expand the gray module scope', () => {
|
||||
const defaults = validateExecutionOptions(parseArguments(args))
|
||||
assert.equal(defaults.prepareDomesticPdaH5, false)
|
||||
const prepared = validateExecutionOptions(parseArguments([...args, '--prepare-domestic-pda-h5', 'true']))
|
||||
assert.equal(new AcceptanceRun(prepared).context.prepareDomesticPdaH5, true)
|
||||
for (const invalid of ['yes', 'false', '1']) assert.throws(() => parseArguments([...args, '--prepare-domestic-pda-h5', invalid]), /必须明确为 true/)
|
||||
assert.deepEqual(prepared.requestedModules, ['phase5-root-fulfillment'])
|
||||
assert.throws(() => validateExecutionOptions({ ...prepared, environment: 'test', requestedModules: ['orders'] }), /prepare-domestic-pda-h5/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user