fix phase3 phase4 development acceptance blockers

This commit is contained in:
2026-08-24 10:33:05 +08:00
parent 81899015cf
commit ccfd024aef
13 changed files with 112 additions and 28 deletions
@@ -38,7 +38,8 @@ test('Firstleg booking supplier snapshot requires ACTIVE BOOKING and has no dome
test('HTTP ocean booking acceptance selects an ACTIVE BOOKING supplier without a geographic scope', () => {
assert.match(oceanBookingFixture, /category=BOOKING&status=ACTIVE/)
assert.match(oceanBookingFixture, /bookingAgentId: bookingAgent\.supplierId/)
assert.match(oceanBookingFixture, /订舱持久化启用订舱供应商快照/)
assert.match(oceanBookingFixture, /bookingAgentId: id\(agent\.id\), bookingAgentName: agent\.name/)
assert.match(oceanBookingFixture, /shippingCompanyId: id\(agent\.id\), shippingCompanyName: agent\.name/)
assert.match(oceanBookingFixture, /使用 ACTIVE BOOKING 订舱代理事实源/)
assert.doesNotMatch(oceanBookingFixture, /category=BOOKING[^'\n]*scope=/)
})
@@ -475,8 +475,8 @@ export async function run(context) {
const confirmedSummary = confirmedNetSummary(afterConfirm.workspace)
context.assert(MODULE, '货物总重量按全部已确认净重由服务端汇总', afterConfirm.response,
confirmedSummary.complete && confirmedSummary.count === baselineImageSummary.count + 1
&& closeNumber(afterConfirm.workspace?.actualGrossWeightKg, confirmedSummary.total, 0.0011),
`confirmedCount=${confirmedSummary.count}, confirmedNetTotal=${confirmedSummary.total}, actual=${afterConfirm.workspace?.actualGrossWeightKg ?? 'missing'}`,
&& closeNumber(afterConfirm.workspace?.actualNetWeightKg, confirmedSummary.total, 0.0011),
`confirmedCount=${confirmedSummary.count}, confirmedNetTotal=${confirmedSummary.total}, actual=${afterConfirm.workspace?.actualNetWeightKg ?? 'missing'}`,
NEW_CONTRACT_OPTIONS)
const staleDelete = await context.request('DELETE',
@@ -47,7 +47,10 @@ export async function run(context) {
step(context, '使用 ACTIVE BOOKING 订舱代理事实源', agentResponse, Boolean(agent?.id && agent?.name),
`supplierId=${id(agent?.id) || 'missing'}, scope=${agent?.scope || 'not-exposed'}`)
if (!agent?.id || !agent?.name) return
const data = fullDraft(context, suffix, master, { bookingAgentId: id(agent.id), bookingAgentName: agent.name })
const data = fullDraft(context, suffix, master, {
bookingAgentId: id(agent.id), bookingAgentName: agent.name,
shippingCompanyId: id(agent.id), shippingCompanyName: agent.name,
})
const create = await request(context, 'POST', BASE, { expectedVersion: 0, data },
{ 'Idempotency-Key': `${context.marker}-ocean-draft-create`.slice(0, 128) })
const created = payload(create); const bookingId = id(created?.oceanBooking?.id)
@@ -36,12 +36,12 @@ test('状态矩阵复用订单共享的逐仓真实报价选择,不假设首
test('入库通过真实订单测量链保留测量态并最终进入 WAIT_SPLIT', () => {
assert.match(source, /result\.inbounds\.WAIT_MEASURE/)
assert.match(source, /processStatus: 'WAIT_MEASURE'/)
for (const status of ['MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT']) {
for (const status of ['MEASURING', 'MEASURE_PENDING', 'WAIT_SPLIT']) {
assert.match(source, new RegExp(`'${status}'`))
}
assert.match(source, /\.\/inbound-measurement\.mjs/)
assert.match(source, /`\/api\/platform\/v1\/inbounds\/\$\{query\(inboundId\)\}\/measurements\/reject\?tenantId=\$\{query\(tenantId\)\}`/)
assert.doesNotMatch(source, /path\.replace\('\/measurement\/submit', '\/measurements\/reject'\)/)
assert.match(source, /入库列表保留 MEASURE_REJECTED/)
assert.match(source, /稳定 Admin 契约未开放 measurements\/reject/)
})
test('FBA 状态样本为订单头和每条货物行提供四个必填标识', () => {
@@ -60,9 +60,7 @@ test('测量状态截停发生在正确的 HTTP 边界', () => {
assert.match(source, /target !== 'MEASURING'/)
assert.doesNotMatch(source, /target === 'MEASURING' && ok\(response\)/)
assert.match(source, /target === 'MEASURE_PENDING' && isSubmit/)
assert.match(source, /target === 'MEASURE_REJECTED' && isSubmit/)
assert.match(source, /measurements\/reject/)
assert.match(source, /expectedVersion: version/)
assert.doesNotMatch(source, /target === 'MEASURE_REJECTED' && isSubmit/)
})
test('入库不可独立保留状态显式阻断', () => {
@@ -191,18 +191,6 @@ async function prepareInboundStatus(context, target) {
if (!ok(response)) throw new Error(`MEASURE_PENDING 提交失败: HTTP=${response?.http}, code=${response?.body?.code}`)
throw stop
}
if (target === 'MEASURE_REJECTED' && isSubmit) {
const submitted = await originalRequest.call(context, method, path, body, token, headers)
if (!ok(submitted)) throw new Error(`MEASURE_REJECTED 提交失败: HTTP=${submitted?.http}, code=${submitted?.body?.code}`)
const latest = await originalRequest.call(context, 'GET', detailPath, undefined, token)
const version = Number(data(latest)?.summary?.version)
const rejectPath = `/api/platform/v1/inbounds/${query(inboundId)}/measurements/reject?tenantId=${query(tenantId)}`
const rejected = await originalRequest.call(context, 'POST', rejectPath,
{ measurementId: body?.measurementId, expectedVersion: version, reason: `${fixtureMarker} 保留审核驳回样本` },
token, { 'Idempotency-Key': `${fixtureMarker}-reject`.slice(0, 128) })
if (!ok(rejected)) throw new Error(`MEASURE_REJECTED 审核驳回失败: HTTP=${rejected?.http}, code=${rejected?.body?.code}, requestId=${rejected?.requestId || 'missing'}, traceId=${rejected?.traceId || 'missing'}`)
throw stop
}
return originalRequest.call(context, method, path, body, token, headers)
}
if (target !== 'MEASURING') {
@@ -303,7 +291,7 @@ export async function run(context, matrix = context.createdResources.manualState
id: waitMeasure.orderId, status: 'WAIT_MEASURE', detailPath: `/orders/${waitMeasure.orderId}`,
}
for (const status of ['MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT']) {
for (const status of ['MEASURING', 'MEASURE_PENDING', 'WAIT_SPLIT']) {
const sample = await prepareInboundStatus(context, status)
result.inbounds[status] = sample
matrix.pages.inbounds.fixtures[status] = {
@@ -317,6 +305,9 @@ export async function run(context, matrix = context.createdResources.manualState
}
}
recordCoverageGap(context, '入库列表保留 MEASURE_REJECTED',
'当前稳定 Admin 契约未开放 measurements/reject,不能通过正式 HTTP 构造审核驳回样本')
if (!result.inbounds.WAIT_SPLIT) {
const legacyResponse = await context.request('GET',
`/api/platform/v1/inbounds?tenantId=${query(fixture.tenantId)}&processStatus=WAIT_SPLIT&page=1&size=20`)