test(firstleg): seed booking file before loading release

This commit is contained in:
2026-09-01 14:31:01 +08:00
parent d2092bf0b6
commit 2ccfc38cdf
2 changed files with 64 additions and 1 deletions
@@ -52,6 +52,25 @@ test('default fixture publishes a second marker-owned inbound for the same-plan
})
test('HTTP chain refreshes after stale inventory and keeps one stable PDA identity', () => {
ordered(loading, [
'创建三期 DRAFT 订舱(不生成真实柜)',
'三期 DRAFT 订舱返回稳定身份和初始版本',
'三期订舱上传当前有效订舱确认文件',
'订舱文件归属当前订舱且上传递增版本',
'订舱文件同键同载荷重放',
'订舱文件精确重放不新增文件或版本',
'回查订舱文件有效归属',
'同键重放后当前订舱仅保留一条目标有效文件',
'三期订舱 RELEASED 冻结运输快照',
])
assert.match(loading, /documentType', 'BOOKING_CONFIRMATION'/)
assert.match(loading, /id\(bookingDocument\?\.document\?\.oceanBookingId\) === bookingId/)
assert.match(loading, /releaseVersion > draftVersion/)
assert.match(loading, /'Idempotency-Key': bookingDocumentKey/)
assert.equal((loading.match(/'Idempotency-Key': bookingDocumentKey/g) || []).length, 2)
assert.match(loading, /Number\(bookingDocumentReplay\?\.bookingVersion\) === releaseVersion/)
assert.match(loading, /expectedVersion: releaseVersion, reason: context\.marker/)
assert.doesNotMatch(loading, /expectedVersion: draftVersion, reason: context\.marker/)
ordered(loading, [
'PDA 原子批次完成拣货并等待人工确认',
'五类装柜证据完整且铅封号持久化',
@@ -440,8 +440,52 @@ export async function run(context) {
const bookingDraftResponse = context.apiSuccess(MODULE, '创建三期 DRAFT 订舱(不生成真实柜)', bookingCreateResponse, '', OPTIONS)
const bookingId = id(bookingDraftResponse?.oceanBooking?.id)
const draftVersion = Number(bookingDraftResponse?.oceanBooking?.version)
context.assert(MODULE, '三期 DRAFT 订舱返回稳定身份和初始版本', bookingCreateResponse,
Boolean(bookingId) && bookingDraftResponse?.oceanBooking?.status === 'DRAFT'
&& Number.isInteger(draftVersion) && draftVersion >= 0,
`bookingId=${bookingId || 'missing'}, status=${bookingDraftResponse?.oceanBooking?.status || 'missing'}, version=${bookingDraftResponse?.oceanBooking?.version ?? 'missing'}`, OPTIONS)
if (!bookingId || !Number.isInteger(draftVersion) || draftVersion < 0) return
const bookingDocumentKey = `${context.marker}-p3-booking-document`.slice(0, 128)
const bookingDocumentForm = () => {
const form = new FormData()
form.append('expectedVersion', String(draftVersion))
form.append('documentType', 'BOOKING_CONFIRMATION')
form.append('file', new Blob([`${context.marker}-BOOKING_CONFIRMATION`], { type: 'text/plain' }),
`booking-confirmation-${context.marker}.txt`)
return form
}
const bookingDocumentResponse = await context.requestMultipart('POST',
`/api/platform/v1/ocean-bookings/${bookingId}/documents/upload`, bookingDocumentForm(), context.token,
{ 'Idempotency-Key': bookingDocumentKey })
const bookingDocument = context.apiSuccess(MODULE, '三期订舱上传当前有效订舱确认文件', bookingDocumentResponse, '', OPTIONS)
const releaseVersion = Number(bookingDocument?.bookingVersion)
context.assert(MODULE, '订舱文件归属当前订舱且上传递增版本', bookingDocumentResponse,
Boolean(bookingDocument?.document?.id) && id(bookingDocument?.document?.oceanBookingId) === bookingId
&& bookingDocument?.document?.documentType === 'BOOKING_CONFIRMATION'
&& bookingDocument?.document?.valid === true && Number.isInteger(releaseVersion)
&& releaseVersion > draftVersion,
`documentId=${id(bookingDocument?.document?.id) || 'missing'}, oceanBookingId=${id(bookingDocument?.document?.oceanBookingId) || 'missing'}, draftVersion=${draftVersion}, bookingVersion=${bookingDocument?.bookingVersion ?? 'missing'}`, OPTIONS)
if (!bookingId || !Number.isInteger(releaseVersion) || releaseVersion <= draftVersion) return
const bookingDocumentReplayResponse = await context.requestMultipart('POST',
`/api/platform/v1/ocean-bookings/${bookingId}/documents/upload`, bookingDocumentForm(), context.token,
{ 'Idempotency-Key': bookingDocumentKey })
const bookingDocumentReplay = context.apiSuccess(MODULE, '订舱文件同键同载荷重放', bookingDocumentReplayResponse, '', OPTIONS)
context.assert(MODULE, '订舱文件精确重放不新增文件或版本', bookingDocumentReplayResponse,
id(bookingDocumentReplay?.document?.id) === id(bookingDocument?.document?.id)
&& id(bookingDocumentReplay?.document?.oceanBookingId) === bookingId
&& Number(bookingDocumentReplay?.bookingVersion) === releaseVersion,
`firstDocumentId=${id(bookingDocument?.document?.id) || 'missing'}, replayDocumentId=${id(bookingDocumentReplay?.document?.id) || 'missing'}, firstVersion=${releaseVersion}, replayVersion=${bookingDocumentReplay?.bookingVersion ?? 'missing'}`, OPTIONS)
const bookingAfterDocumentResponse = await context.request('GET', `/api/platform/v1/ocean-bookings/${bookingId}`)
const bookingAfterDocument = context.apiSuccess(MODULE, '回查订舱文件有效归属', bookingAfterDocumentResponse, '', OPTIONS)
const activeBookingDocuments = (bookingAfterDocument?.documents || []).filter((document) => document?.valid === true)
context.assert(MODULE, '同键重放后当前订舱仅保留一条目标有效文件', bookingAfterDocumentResponse,
activeBookingDocuments.filter((document) => id(document?.id) === id(bookingDocument?.document?.id)
&& id(document?.oceanBookingId) === bookingId && document?.documentType === 'BOOKING_CONFIRMATION').length === 1,
`activeDocuments=${activeBookingDocuments.length}, targetDocumentId=${id(bookingDocument?.document?.id) || 'missing'}`, OPTIONS)
const releaseResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/release`,
{ expectedVersion: draftVersion, reason: context.marker },
{ expectedVersion: releaseVersion, reason: context.marker },
{ 'Idempotency-Key': `${context.marker}-p3-booking-release`.slice(0, 128) })
const bookingConfirmed = context.apiSuccess(MODULE, '三期订舱 RELEASED 冻结运输快照', releaseResponse, '', OPTIONS)
const transportFields = ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt', 'estimatedArrivalAt']