This commit is contained in:
2026-08-13 09:49:55 +08:00
parent f3366cb2fa
commit 95849f248f
69 changed files with 1224 additions and 221 deletions
@@ -0,0 +1,20 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const sql = readFileSync(new URL('../../deploy/sql/头程数据库统一增量-MySQL8.sql', import.meta.url), 'utf8')
test('统一初始化 SQL 保留最小登录与客户主体', () => {
assert.match(sql, /一个平台系统管理员和一个 IT 测试客户管理员/)
assert.equal((sql.match(/'platform_admin'/g) || []).length, 1)
assert.equal((sql.match(/'merchant_admin'/g) || []).length, 1)
assert.match(sql, /\(10001, 'IT_TEST', 'IT 测试客户'/)
})
test('统一初始化 SQL 提供人工验收所需公共仓储主数据', () => {
for (const marker of [
"'TP-001'", "'TP-002'", "'A1 普通库位'", "'A0 共享溢出位'",
"'CUS-001'", "'CUSTOMER'", "'SHIPPER'", "'EXP-TEST'", "'IT 测试快递'",
]) assert.ok(sql.includes(marker), `missing initialization marker: ${marker}`)
assert.match(sql, /'EXP-TEST', 'IT 测试快递'[\s\S]*?'ACTIVE'/)
})
@@ -25,3 +25,19 @@ test('does not mutate historical fixtures or remove retained records', () => {
assert.match(source, /fixtureMarker/)
assert.match(source, /context\.marker = originalMarker/)
})
test('covers simplified clearance and customs actions through public multipart upload and complete APIs', () => {
assert.match(source, /new FormData\(\)/)
assert.match(source, /form\.append\('file'/)
assert.match(source, /form\.append\('expectedVersion'/)
assert.match(source, /form\.append\('operationReason'/)
assert.match(source, /'Idempotency-Key'/)
assert.match(source, /clearancePath\}\/document\/upload/)
assert.match(source, /clearancePath\}\/complete/)
assert.match(source, /customsPath\}\/document\/upload\?\$\{warehouse\}/)
assert.match(source, /customsPath\}\/complete\?\$\{warehouse\}/)
assert.match(source, /matrix\.pages\.containerLoading\.actions/)
assert.match(source, /matrix\.pages\.loadingLists\.actions/)
assert.doesNotMatch(source, /internal\/file|business-files/)
assert.doesNotMatch(source, /\{\s*fileId\s*:/)
})
@@ -22,6 +22,32 @@ async function post(context, path, body, label) {
{ 'Idempotency-Key': operationId(context, label) })
}
async function uploadDocument(context, path, expectedVersion, operationReason, label) {
const form = new FormData()
form.append('file', new Blob([`${context.marker}-${label}`], { type: 'text/plain' }), `${label}.txt`)
form.append('expectedVersion', String(expectedVersion))
if (operationReason) form.append('operationReason', operationReason)
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method: 'POST',
headers: {
Accept: 'application/json', Authorization: `Bearer ${context.token}`,
'Idempotency-Key': operationId(context, `${label}-upload`),
},
body: form,
signal: AbortSignal.timeout(60_000),
})
const raw = await response.text()
let body = null
try { body = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
return { method: 'POST', path, http: response.status, body, raw: body ? null : raw.slice(0, 500),
requestId: body?.requestId || response.headers.get('x-request-id'),
traceId: body?.traceId || response.headers.get('x-trace-id') }
} catch (error) {
return { method: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
}
}
async function prepareInbound(context, label) {
const originalMarker = context.marker
const fixtureMarker = `${originalMarker}-MAN-${label}`
@@ -224,10 +250,71 @@ function fixture(value, status, detailPath, extra = {}) {
return { id: id(value), status, detailPath, ...extra }
}
async function completeSimpleDocuments(context, chain, matrix) {
const reason = `${context.marker} 人工页面动作验收`
const clearancePath = `${CONTAINERS}/${chain.containerId}/clearance`
const clearanceUpload = await uploadDocument(context, `${clearancePath}/document/upload`, 0,
reason, 'container-clearance')
const clearanceDocument = context.apiSuccess(MODULE, '货柜公开接口上传目的港清关资料',
clearanceUpload, '', OPTIONS)
context.assert(MODULE, '货柜清关上传返回真实文件事实', clearanceUpload,
Boolean(clearanceDocument?.id) && Boolean(clearanceDocument?.fileId)
&& clearanceDocument?.documentType === 'DESTINATION_CLEARANCE'
&& /^[0-9a-f]{64}$/i.test(String(clearanceDocument?.fileHash || ''))
&& !clearanceDocument?.completedAt,
`documentId=${id(clearanceDocument?.id) || 'missing'}, version=${clearanceDocument?.version ?? 'missing'}`, OPTIONS)
if (!clearanceDocument?.id || !clearanceDocument?.fileId || !Number.isInteger(Number(clearanceDocument?.version))) return
const clearanceCompleteResponse = await post(context, `${clearancePath}/complete`, {
expectedVersion: Number(clearanceDocument?.version), operationReason: reason,
}, 'container-clearance-complete')
const clearanceCompleted = context.apiSuccess(MODULE, '货柜点击完成目的港清关',
clearanceCompleteResponse, '', OPTIONS)
context.assert(MODULE, '货柜清关完成事实可回读', clearanceCompleteResponse,
id(clearanceCompleted?.id) === id(clearanceDocument?.id) && Boolean(clearanceCompleted?.completedAt)
&& Number(clearanceCompleted?.version) === Number(clearanceDocument?.version) + 1,
`documentId=${id(clearanceCompleted?.id) || 'missing'}, completedAt=${clearanceCompleted?.completedAt || 'missing'}`, OPTIONS)
const customsPath = `${LOADING}/loading-lists/${chain.plan.id}/customs`
const warehouse = `warehouseId=${encodeURIComponent(chain.source.warehouseId)}`
const customsUpload = await uploadDocument(context, `${customsPath}/document/upload?${warehouse}`, 0,
reason, 'loading-customs')
const customsDocument = context.apiSuccess(MODULE, '装柜清单公开接口上传出口报关文档',
customsUpload, '', OPTIONS)
context.assert(MODULE, '装柜清单报关上传返回真实文件事实', customsUpload,
Boolean(customsDocument?.id) && Boolean(customsDocument?.fileId)
&& customsDocument?.documentType === 'CUSTOMS_DECLARATION'
&& /^[0-9a-f]{64}$/i.test(String(customsDocument?.fileHash || ''))
&& !customsDocument?.completedAt,
`documentId=${id(customsDocument?.id) || 'missing'}, version=${customsDocument?.version ?? 'missing'}`, OPTIONS)
if (!customsDocument?.id || !customsDocument?.fileId || !Number.isInteger(Number(customsDocument?.version))) return
const customsCompleteResponse = await post(context, `${customsPath}/complete?${warehouse}`, {
expectedVersion: Number(customsDocument?.version), operationReason: reason,
}, 'loading-customs-complete')
const customsCompleted = context.apiSuccess(MODULE, '装柜清单点击完成出口报关',
customsCompleteResponse, '', OPTIONS)
context.assert(MODULE, '装柜清单报关完成事实可回读', customsCompleteResponse,
id(customsCompleted?.id) === id(customsDocument?.id) && Boolean(customsCompleted?.completedAt)
&& Number(customsCompleted?.version) === Number(customsDocument?.version) + 1,
`documentId=${id(customsCompleted?.id) || 'missing'}, completedAt=${customsCompleted?.completedAt || 'missing'}`, OPTIONS)
matrix.pages.containerLoading.actions = { clearance: fixture(chain.containerId, 'COMPLETED',
`/container-loading/${chain.containerId}/smart-loading`, {
documentId: id(clearanceCompleted?.id), documentVersion: Number(clearanceCompleted?.version),
completedAt: clearanceCompleted?.completedAt || null,
}) }
matrix.pages.loadingLists.actions = { customsDeclaration: fixture(chain.plan.id, 'COMPLETED',
`/loading-lists?planId=${chain.plan.id}&warehouseId=${chain.source.warehouseId}`, {
warehouseId: chain.source.warehouseId, documentId: id(customsCompleted?.id),
documentVersion: Number(customsCompleted?.version), completedAt: customsCompleted?.completedAt || null,
}) }
}
export async function run(context, matrix = context.createdResources.manualStateMatrix) {
matrix.pages.containerLoading = { requiredStatuses: CONTAINER_STATUSES, fixtures: {} }
matrix.pages.containerLoading = { requiredStatuses: CONTAINER_STATUSES,
requiredActions: ['clearance'], fixtures: {}, actions: {} }
matrix.pages.preloadPlans = { requiredStatuses: PRELOAD_STATUSES, fixtures: {} }
matrix.pages.loadingLists = { requiredStatuses: ['COMPLETED'], fixtures: {} }
matrix.pages.loadingLists = { requiredStatuses: ['COMPLETED'],
requiredActions: ['customsDeclaration'], fixtures: {}, actions: {} }
matrix.pages.pdaTasks = { requiredStatuses: PDA_STATUSES, fixtures: {} }
const warehouseId = '193000000000200001'
@@ -283,4 +370,6 @@ export async function run(context, matrix = context.createdResources.manualState
context.assert(MODULE, 'COMPLETED 装柜清单详情身份正确', listDetailResponse,
id(listDetail?.summary?.planId) === listFixture.id && listDetail?.summary?.status === 'COMPLETED',
`planId=${listFixture.id}, actual=${listDetail?.summary?.status || 'missing'}`, OPTIONS)
await completeSimpleDocuments(context, completed, matrix)
}
@@ -18,4 +18,7 @@ test('matrix delegates bounded groups and asserts every declared status has a re
assert.match(source, /createdResources\.manualStateMatrix/)
assert.match(source, /requiredStatuses/)
assert.match(source, /fixtures\?\.\[status\]\?\.id/)
assert.match(source, /requiredActions/)
assert.match(source, /actions\?\.\[action\]\?\.documentId/)
assert.match(source, /actions\?\.\[action\]\?\.completedAt/)
})
@@ -23,7 +23,11 @@ export async function run(context) {
const missing = pages.flatMap(([page, value]) => rows(value?.requiredStatuses)
.filter((status) => !value?.fixtures?.[status]?.id)
.map((status) => `${page}:${status}`))
const missingActions = pages.flatMap(([page, value]) => rows(value?.requiredActions)
.filter((action) => !value?.actions?.[action]?.id
|| !value?.actions?.[action]?.documentId || !value?.actions?.[action]?.completedAt)
.map((action) => `${page}:action:${action}`))
context.assert(MODULE, '人工验收列表状态数据全部生成并保留', null,
pages.length > 0 && missing.length === 0,
`pages=${pages.length}, missing=${missing.join(',') || 'none'}`, OPTIONS)
pages.length > 0 && missing.length === 0 && missingActions.length === 0,
`pages=${pages.length}, missing=${[...missing, ...missingActions].join(',') || 'none'}`, OPTIONS)
}