From ef1eef3278007a339e75428e457c29500f4cf28b Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 3 Oct 2026 07:51:17 +0800 Subject: [PATCH] test(fleet): close shared trips and verify resource release --- scripts/README.md | 2 +- scripts/fleet-cross-customer-test-env.mjs | 16 +++++++++++++--- scripts/fleet-cross-customer-test-env.test.mjs | 4 +++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index 7c19839e0..b0ea465c0 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,6 +1,6 @@ # 后端脚本管理与全量 HTTP 验收索引 -`fleet-cross-customer-test-env.mjs` 是最小两客户共享车次的串行 TEST 专项入口,默认 `--plan` 零网络。显式运行参数为 `--execute --environment TEST --run-id <新批次> --warehouse-id <实际海外收货仓ID> --destination-warehouse-id `,凭据仅从显式 `node --env-file="" --env-file=""` 与 `FBA_CORE_JENKINS_AGENT` 提供。通过普通转运来货、PDA实收和客户确认创建两个新客户各一托一箱,分别预排并关联同车;一单未拣齐时验证全车拒绝及完整库存快照不变,再统一发车、原键重放、逐客户签收。完整库存由只读取证器获取,不写业务SQL,不部署或改共享配置。报告位于 `reports/fleet-cross-<新批次>/`,每次请求发送前记录意图;失败保留原现场,不自动重跑或续跑,已存在批次拒绝覆盖。finally停用本轮PDA并退出独立会话;业务样本保留。前置拒绝不代表事务中途回滚,后者另验。对应入口保护合同为 `fleet-cross-customer-test-env.test.mjs`;内部生成与验收函数见模块README。 +`fleet-cross-customer-test-env.mjs` 是最小两客户共享车次的串行 TEST 专项入口,默认 `--plan` 零网络。显式运行参数为 `--execute --environment TEST --run-id <新批次> --warehouse-id <实际海外收货仓ID> --destination-warehouse-id `,凭据仅从显式 `node --env-file="" --env-file=""` 与 `FBA_CORE_JENKINS_AGENT` 提供。通过普通转运来货、PDA实收和客户确认创建两个新客户各一托一箱,分别预排并关联同车;一单未拣齐时验证全车拒绝及完整库存快照不变,再统一发车、原键重放、逐客户签收,最后正式结案并分别释放车辆/司机,核对结案前后库存零变化。完整库存由只读取证器获取,不写业务SQL,不部署或改共享配置。报告位于 `reports/fleet-cross-<新批次>/`,每次请求发送前记录意图;失败保留原现场,不自动重跑或续跑,已存在批次拒绝覆盖。finally停用本轮PDA并退出独立会话;业务样本保留。前置拒绝不代表事务中途回滚,后者另验。对应入口保护合同为 `fleet-cross-customer-test-env.test.mjs`;内部生成与验收函数见模块README。 头程订单提交使用首次原始详情的 `expectedVersion` 与原 `Idempotency-Key`。生产单票的 `SUBMIT_INTENT` 必须先持久化 `submitCommand` 再发送,显式续跑不得使用当前版本替换原命令,缺少原命令的提交意图及后续检查点停止并等待人工核对。源 Excel 造数同样在提交前保存订单条目的 `submitCommand`;中断或失败条目保留原检查点且不自动重跑,不重建订单或换版本继续。 diff --git a/scripts/fleet-cross-customer-test-env.mjs b/scripts/fleet-cross-customer-test-env.mjs index 050319093..59894a155 100644 --- a/scripts/fleet-cross-customer-test-env.mjs +++ b/scripts/fleet-cross-customer-test-env.mjs @@ -8,6 +8,7 @@ import { createTestEvidenceReaders } from './fba-core-regression-evidence.mjs' import { redactEvidence } from './fba-core-regression-test-env.mjs' import { resolvePickerAccess, createPicker, disablePicker } from './admin-test-env-modules/phase4-overseas-pda.mjs' import { verifyFleetCrossCustomer } from './admin-test-env-modules/fleet-cross-customer-acceptance.mjs' +import { finishFleetTrip } from './admin-test-env-modules/fleet-trip-fulfillment.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const ok = response => response?.http === 200 && String(response.body?.code) === '200' @@ -33,12 +34,12 @@ export function parseCrossCustomerArguments(args) { export function crossCustomerPlan(config) { return { environment: 'TEST', execute: config.execute, runId: config.runId, warehouseId: config.warehouseId || null, destinationWarehouseId: config.destinationWarehouseId || null, output: `reports/fleet-cross-${config.runId}`, stages: ['two new customers', 'two ordinary transfer receipts', 'two dispatches on one trip', - 'unready customer gate rejection', 'shared departure and replay', 'independent receipts'], + 'unready customer gate rejection', 'shared departure and replay', 'independent receipts', 'trip completion and separate resource release'], limits: 'Prerequisite rejection is not mid-transaction rollback. No deployment, configuration change, SQL writes or automatic rerun. Retain business samples; disable temporary PDA in finally.' } } export async function runCrossCustomer(config, { env = process.env, outputRoot = ROOT, readers, prepare, verify = verifyFleetCrossCustomer, - contextFactory = options => new AcceptanceRun(options), pickerAccess = resolvePickerAccess, pickerCreate = createPicker, pickerDisable = disablePicker } = {}) { + finish = finishFleetTrip, contextFactory = options => new AcceptanceRun(options), pickerAccess = resolvePickerAccess, pickerCreate = createPicker, pickerDisable = disablePicker } = {}) { const plan = crossCustomerPlan(config) if (!config.execute) return plan if (config.environment !== 'TEST' || env.CHONGCHONG_ENV !== 'test' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(config.runId) @@ -107,7 +108,16 @@ export async function runCrossCustomer(config, { env = process.env, outputRoot = const fixture = await prepare(context, { execute: true, warehouseId: config.warehouseId, destinationWarehouseId: config.destinationWarehouseId, pdaToken: picker.token, readInventory }) save('fixture.json', fixture) await verify(context, fixture, { execute: true, readInventory }) - report = { success: context.createdResources.fleetCrossCustomer?.status === 'VERIFIED' } + const trip = context.createdResources.fleetTrips?.find(row => row.tripNo === fixture.tripNo) + if (!trip) throw Error('Original fixture trip missing for resource completion') + const beforeClosure = [] + for (const member of fixture.members) beforeClosure.push(await readInventory({ marker: context.marker, customerId: member.customerId, warehouseId: config.warehouseId })) + await finish(context, 'fleet-cross-customer-closure', trip) + const afterClosure = [] + for (const member of fixture.members) afterClosure.push(await readInventory({ marker: context.marker, customerId: member.customerId, warehouseId: config.warehouseId })) + if (JSON.stringify(beforeClosure) !== JSON.stringify(afterClosure)) throw Error('Trip closure changed customer inventory') + report = { success: context.createdResources.fleetCrossCustomer?.status === 'VERIFIED' + && trip.closure?.status === 'CARGO_COMPLETED_AND_RESOURCES_RELEASED', closureInventoryUnchanged: true } } catch (error) { report = { success: false, error: error instanceof Error ? error.message : 'CROSS_CUSTOMER_FAILED' } } finally { diff --git a/scripts/fleet-cross-customer-test-env.test.mjs b/scripts/fleet-cross-customer-test-env.test.mjs index 7b2bad171..019839836 100644 --- a/scripts/fleet-cross-customer-test-env.test.mjs +++ b/scripts/fleet-cross-customer-test-env.test.mjs @@ -24,11 +24,13 @@ function harness(root, fail = false) { assert.equal(options.pdaToken, 'private-pda') await context.request('POST', '/api/test-owned-command', { idempotencyKey: 'stable-command', password: 'private-pda-password' }) if (fail) throw Error('FIXTURE_FAILED') - return { pdaToken: options.pdaToken, marker: context.marker } + return { pdaToken: options.pdaToken, marker: context.marker, tripNo: 'TR-OFFLINE', members: [{ customerId: '42' }, { customerId: '43' }] } }, verify: async (_context, _fixture, options) => { await options.readInventory({ marker: context.marker, customerId: '42', warehouseId: '900' }) context.createdResources.fleetCrossCustomer = { status: 'VERIFIED' } + context.createdResources.fleetTrips = [{ tripNo: 'TR-OFFLINE' }] }, + finish: async (_context, _module, trip) => { trip.closure = { status: 'CARGO_COMPLETED_AND_RESOURCES_RELEASED' } }, } } } test('plan is network-free and execution requires explicit TEST, unique run and warehouse', async () => {