import { CONTAINER_CUSTOMS_FIXTURE_KINDS, CONTAINER_CUSTOMS_MODULE, EXACT_50_MB, validateContainerCustomsFixtures, validateRouteContract, xlsxMagic, zeroDrift, } from './container-customs-acceptance-matrix.mjs' const MODULE = CONTAINER_CUSTOMS_MODULE const OPTIONS = { scenario: MODULE, deploymentBlocker: true } const id = (value) => value == null ? '' : String(value) const code = (response) => String(response?.body?.code ?? '') const success = (response) => response?.http === 200 && code(response) === '200' const a0409 = (response) => response?.http === 200 && code(response) === 'A0409' const evidence = (response, extra = '') => `${extra}${extra ? '; ' : ''}HTTP=${response?.http ?? 'missing'}, code=${code(response) || 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` const key = (context, value) => `${context.marker}-container-customs-${value}`.slice(0, 128) function blocker(context, name, reason) { context.addCoverageGap(MODULE, MODULE, name, reason, 'contract-or-fixture', { deploymentBlocker: true }) context.addStep(MODULE, name, null, false, reason, OPTIONS) throw new Error(`blocking coverage gap: ${name}: ${reason}`) } function resolvePreparation(context) { const fixtures = context.createdResources?.phase3ContainerCustomsFixtures const routes = context.context?.phase3ContainerCustomsRoutes if (!validateContainerCustomsFixtures(fixtures, context.marker)) { blocker(context, '三类LOADED整柜fixture', '缺少本轮全买单、纯退税、混合三柜正式链路数据') } if (!validateRouteContract(routes)) { blocker(context, '整柜报关最终HTTP契约', '后端Controller/OpenAPI与Admin/Client最终路径尚未对齐,禁止猜测URL发请求') } return { fixtures, routes } } async function platformDetail(context, routes, fixture) { return routes.platformDetail(context, { loadingPlanId: id(fixture.loadingPlanId) }) } async function assertZeroDrift(context, name, routes, fixture, before, response, acceptedCodes = ['A0409']) { const after = await platformDetail(context, routes, fixture) context.assert(MODULE, name, response, response?.http === 200 && acceptedCodes.includes(code(response)) && success(after) && zeroDrift(before.body?.data, after.body?.data), evidence(response, `afterRequestId=${after.requestId || 'missing'}, afterTraceId=${after.traceId || 'missing'}`), OPTIONS) } async function runAllBuy(context, routes, fixture) { const detail = await platformDetail(context, routes, fixture) context.assert(MODULE, '全买单柜LOADED详情', detail, success(detail) && detail.body?.data?.inbounds?.every?.((item) => item.taxMode === 'BUY_DECLARATION'), evidence(detail), OPTIONS) const xlsx = await routes.buyDeclarationXlsx(context, { loadingPlanId: id(fixture.loadingPlanId) }) context.assert(MODULE, '买单XLSX下载窗口返回真实工作簿', xlsx, xlsx.http === 200 && xlsxMagic(xlsx.bytes), `HTTP=${xlsx.http}, bytes=${xlsx.bytes?.length ?? 0}, contentType=${xlsx.contentType || 'missing'}`, OPTIONS) const startBody = { expectedVersion: Number(detail.body?.data?.version), remark: context.marker } const started = await routes.platformStart(context, fixture, startBody, key(context, 'all-buy-start')) context.assert(MODULE, '全买单柜开始报关', started, success(started) && started.body?.data?.status === 'DECLARING', evidence(started), OPTIONS) const completeBody = { expectedVersion: Number(started.body?.data?.version), remark: context.marker } const completed = await routes.platformComplete(context, fixture, completeBody, key(context, 'all-buy-complete')) context.assert(MODULE, '全买单柜完成并触发自动离仓意图', completed, success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS) const replay = await routes.platformComplete(context, fixture, completeBody, key(context, 'all-buy-complete')) context.assert(MODULE, '全买单柜完成同键重放', replay, success(replay) && id(replay.body?.data?.aggregateId) === id(completed.body?.data?.aggregateId), evidence(replay), OPTIONS) let outbound = await routes.outboundState(context, fixture) if (fixture.autoDispatchMode === 'WAIT_CONDITION') { context.assert(MODULE, '自动离仓失败不回滚报关完成', outbound, success(outbound) && outbound.body?.data?.customsStatus === 'COMPLETED' && outbound.body?.data?.departed !== true && Boolean(outbound.body?.data?.retryable), evidence(outbound), OPTIONS) await routes.satisfyOutboundPrerequisites(context, fixture) for (let attempt = 0; attempt < 12 && outbound.body?.data?.departed !== true; attempt += 1) { if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, 1_000)) outbound = await routes.outboundState(context, fixture) } } context.assert(MODULE, '自动或待条件重试后整柜单次离仓', outbound, success(outbound) && outbound.body?.data?.departed === true && Number(outbound.body?.data?.departureHistoryCount) === 1, evidence(outbound), OPTIONS) } async function uploadBoundary(context, routes, fixture, customer, size, suffix) { const content = new Uint8Array(size) content[0] = 0x43; content[content.length - 1] = 0x58 return routes.merchantUploadDocument(context, fixture, customer, { fileName: `${context.marker}-${suffix}.customblob`, contentType: 'application/octet-stream', content, inboundIds: customer.inboundIds.map(id), expectedVersion: customer.expectedVersion, }, key(context, suffix)) } async function runRefund(context, routes, fixture) { const customers = fixture.customers || [] if (customers.length < 2) blocker(context, '纯退税多客户fixture', '至少需要两个客户和可交叉验证的多对多入仓范围') const first = customers[0] const exact = await uploadBoundary(context, routes, fixture, first, EXACT_50_MB, 'exact-50m') context.assert(MODULE, '任意格式50MiB边界成功', exact, success(exact), evidence(exact), OPTIONS) const tooLargeBefore = await platformDetail(context, routes, fixture) const tooLarge = await uploadBoundary(context, routes, fixture, first, EXACT_50_MB + 1, 'over-50m') await assertZeroDrift(context, '50MiB+1拒绝且零漂移', routes, fixture, tooLargeBefore, tooLarge, ['A0400', 'A0409', 'A0422']) const crossCustomer = await routes.merchantDetail(context, fixture, customers[1], first.customerToken) context.assert(MODULE, '客户隔离阻止查看其他客户资料', crossCustomer, !success(crossCustomer), evidence(crossCustomer), OPTIONS) const hiddenBuy = await routes.buyDeclarationXlsx(context, { loadingPlanId: id(fixture.loadingPlanId), token: first.customerToken }) context.assert(MODULE, '客户侧隐藏买单XLSX', hiddenBuy, hiddenBuy.http !== 200 || !xlsxMagic(hiddenBuy.bytes), `HTTP=${hiddenBuy.http}, bytes=${hiddenBuy.bytes?.length ?? 0}`, OPTIONS) for (const customer of customers) { const document = await routes.merchantUploadDocument(context, fixture, customer, customer.document, key(context, `upload-${customer.customerId}`)) context.assert(MODULE, `客户${customer.customerId}上传多对多资料`, document, success(document), evidence(document), OPTIONS) customer.documentId = id(document.body?.data?.documentId) customer.documentVersion = Number(document.body?.data?.source?.version) } const mutable = customers[0] const replaced = await routes.merchantReplaceDocument(context, fixture, mutable, { ...mutable.replacement, documentId: mutable.documentId, expectedVersion: mutable.documentVersion, }, key(context, 'replace')) context.assert(MODULE, '客户覆盖资料生成新版本', replaced, success(replaced), evidence(replaced), OPTIONS) const scoped = await routes.merchantUpdateScope(context, fixture, mutable, { documentId: mutable.documentId, inboundIds: mutable.updatedInboundIds.map(id), expectedVersion: Number(replaced.body?.data?.source?.version), }, key(context, 'scope')) context.assert(MODULE, '客户修改多对多关联', scoped, success(scoped), evidence(scoped), OPTIONS) const deleted = await routes.merchantDeleteDocument(context, fixture, mutable, { documentId: mutable.documentId, expectedVersion: Number(scoped.body?.data?.source?.version), }, key(context, 'delete')) context.assert(MODULE, '客户删除未冻结资料', deleted, success(deleted), evidence(deleted), OPTIONS) const beforeMissingStart = await platformDetail(context, routes, fixture) const missingStart = await routes.platformStart(context, fixture, { expectedVersion: Number(beforeMissingStart.body?.data?.version), remark: 'missing-document-negative', }, key(context, 'missing-start')) await assertZeroDrift(context, '缺退税资料禁止开始且零漂移', routes, fixture, beforeMissingStart, missingStart) const restored = await routes.merchantUploadDocument(context, fixture, mutable, mutable.document, key(context, `restore-${mutable.customerId}`)) context.assert(MODULE, '删除后重新上传恢复完整退税覆盖', restored, success(restored), evidence(restored), OPTIONS) mutable.documentId = id(restored.body?.data?.documentId) mutable.documentVersion = Number(restored.body?.data?.source?.version) const ready = await platformDetail(context, routes, fixture) const started = await routes.platformStart(context, fixture, { expectedVersion: Number(ready.body?.data?.version), remark: context.marker, }, key(context, 'refund-start')) context.assert(MODULE, '纯退税柜资料齐全后开始并冻结', started, success(started) && started.body?.data?.status === 'DECLARING' && started.body?.data?.documents?.every?.((item) => item.frozen === true), evidence(started), OPTIONS) const frozenSnapshot = started.body?.data const lockedOperations = [ ['上传', () => routes.merchantUploadDocument(context, fixture, mutable, { ...mutable.document, fileName: `${context.marker}-locked.customblob`, content: Uint8Array.from([1]), }, key(context, 'locked-upload'))], ['覆盖', () => routes.merchantReplaceDocument(context, fixture, mutable, { ...mutable.replacement, documentId: mutable.documentId, expectedVersion: mutable.documentVersion, }, key(context, 'locked-replace'))], ['改关联', () => routes.merchantUpdateScope(context, fixture, mutable, { documentId: mutable.documentId, inboundIds: mutable.inboundIds.map(id), expectedVersion: mutable.documentVersion, }, key(context, 'locked-scope'))], ['删除', () => routes.merchantDeleteDocument(context, fixture, mutable, { documentId: mutable.documentId, expectedVersion: mutable.documentVersion, }, key(context, 'locked-delete'))], ] for (const [label, request] of lockedOperations) { const response = await request() await assertZeroDrift(context, `开始后${label}锁定A0409零漂移`, routes, fixture, { body: { data: frozenSnapshot } }, response) } const documents = frozenSnapshot?.documents || [] for (const [index, document] of documents.entries()) { const completion = await routes.platformUploadCompletion(context, fixture, { documentId: id(document.documentId), expectedVersion: Number(document.source?.version), fileName: `${context.marker}-completion-${index + 1}.done`, contentType: 'application/x-customs-completion', content: Uint8Array.from([0x43, index + 1]), }, key(context, `completion-${index + 1}`)) context.assert(MODULE, `逐份上传完成文件${index + 1}`, completion, success(completion) && zeroDrift(completion.body?.data?.inboundIds, document.inboundIds), evidence(completion, `sourceDocumentId=${id(document.documentId)}`), OPTIONS) if (index + 1 < documents.length) { const beforeEarlyComplete = await platformDetail(context, routes, fixture) const early = await routes.platformComplete(context, fixture, { expectedVersion: Number(beforeEarlyComplete.body?.data?.version), remark: 'not-last-completion', }, key(context, `early-complete-${index + 1}`)) await assertZeroDrift(context, '最后一份完成文件前禁止整柜完成', routes, fixture, beforeEarlyComplete, early) } } const completedReady = await platformDetail(context, routes, fixture) const completed = await routes.platformComplete(context, fixture, { expectedVersion: Number(completedReady.body?.data?.version), remark: context.marker, }, key(context, 'refund-complete')) context.assert(MODULE, '最后一份完成文件后纯退税柜完成', completed, success(completed) && completed.body?.data?.status === 'COMPLETED', evidence(completed), OPTIONS) const firstDocument = documents[0] if (firstDocument) { const platformDownload = await routes.platformDownload(context, fixture, { documentId: id(firstDocument.documentId), completion: true, }) context.assert(MODULE, 'Admin下载完成文件窗口', platformDownload, platformDownload.http === 200 && platformDownload.bytes?.length > 0, `HTTP=${platformDownload.http}, bytes=${platformDownload.bytes?.length ?? 0}`, OPTIONS) const merchantDownload = await routes.merchantDownload(context, fixture, mutable, { documentId: id(firstDocument.documentId), completion: true, }) context.assert(MODULE, 'Client仅下载本客户完成文件窗口', merchantDownload, merchantDownload.http === 200 && merchantDownload.bytes?.length > 0, `HTTP=${merchantDownload.http}, bytes=${merchantDownload.bytes?.length ?? 0}`, OPTIONS) } } async function runMixedConcurrency(context, routes, fixture) { const before = await platformDetail(context, routes, fixture) const body = { expectedVersion: Number(before.body?.data?.version), remark: context.marker } const concurrentKey = key(context, 'mixed-start-concurrent') const responses = await Promise.all([ routes.platformStart(context, fixture, body, concurrentKey), routes.platformStart(context, fixture, body, concurrentKey), ]) context.assert(MODULE, '开始并发双击收敛同一结果', responses[1], responses.every(success) && zeroDrift(responses[0].body?.data, responses[1].body?.data), responses.map((response) => evidence(response)).join('; '), OPTIONS) const different = await routes.platformStart(context, fixture, { ...body, remark: 'different' }, concurrentKey) context.assert(MODULE, '同幂等键异载荷A0409', different, a0409(different), evidence(different), OPTIONS) const frozen = responses[0].body?.data const customer = fixture.customers?.[0] if (!customer?.documentId) blocker(context, '混合柜冻结文件fixture', '混合柜缺少已上传退税客户资料') const locked = await routes.merchantUpdateScope(context, fixture, customer, { documentId: customer.documentId, inboundIds: customer.inboundIds.map(id), expectedVersion: customer.documentVersion, }, key(context, 'locked-scope')) await assertZeroDrift(context, '开始后资料范围锁定A0409零漂移', routes, fixture, { body: { data: frozen } }, locked) } export async function run(context) { const { fixtures, routes } = resolvePreparation(context) const byKind = new Map(fixtures.containers.map((item) => [item.kind, item])) await runAllBuy(context, routes, byKind.get(CONTAINER_CUSTOMS_FIXTURE_KINDS[0])) await runRefund(context, routes, byKind.get(CONTAINER_CUSTOMS_FIXTURE_KINDS[1])) await runMixedConcurrency(context, routes, byKind.get(CONTAINER_CUSTOMS_FIXTURE_KINDS[2])) }