test(acceptance): reconcile dropship runtime facts
This commit is contained in:
@@ -6,17 +6,17 @@ const read = (name) => readFileSync(new URL(name, import.meta.url), 'utf8')
|
||||
|
||||
test('domestic container fixtures use the current workbench detail route', () => {
|
||||
const containerLoading = read('./phase3-container-loading.mjs')
|
||||
assert.ok(containerLoading.includes("context.request('GET', `${CONTAINERS}/domestic/${containerId}`)"))
|
||||
assert.ok(containerLoading.includes("context.request('GET', `${DOMESTIC_CONTAINERS}/${containerId}`)"))
|
||||
assert.ok(!containerLoading.includes("context.request('GET', `${CONTAINERS}/${containerId}`)"))
|
||||
assert.ok(containerLoading.includes('const containerDetail = containerWorkbench?.container'))
|
||||
assert.ok(containerLoading.includes('const simulationBeforeLink = simulationWorkbench?.container'))
|
||||
assert.ok(containerLoading.includes('`${CONTAINERS}/domestic?scope=ACTIVE&page=1&size=50&keyword='))
|
||||
assert.ok(containerLoading.includes('records(activeWorkbench).map((item) => item?.container)'))
|
||||
assert.ok(containerLoading.includes("const containerDetail = containerRecord(context.apiSuccess(MODULE, '回查货柜详情'"))
|
||||
assert.ok(containerLoading.includes("const simulationBeforeLink = containerRecord(context.apiSuccess(MODULE, '预装确认前读取模拟柜最新版本'"))
|
||||
assert.ok(containerLoading.includes('`${DOMESTIC_CONTAINERS}?page=1&size=50&scope=ACTIVE&keyword='))
|
||||
assert.ok(containerLoading.includes('records(activeContainers).map(containerRecord)'))
|
||||
|
||||
const loading = read('./phase3-loading.mjs')
|
||||
assert.ok(loading.includes("context.request('GET', `/api/platform/v1/containers/domestic/${containerBookingId}`)"))
|
||||
assert.ok(!loading.includes("context.request('GET', `/api/platform/v1/containers/${containerBookingId}`)"))
|
||||
assert.ok(loading.includes('const latestContainer = latestWorkbench?.container'))
|
||||
assert.ok(loading.includes("const latestContainer = containerRecord(context.apiSuccess(MODULE, '合并前读取模拟柜最新版本'"))
|
||||
|
||||
const crossRegion = read('./phase4-cross-region-events.mjs')
|
||||
const fixtureId = 'encodeURIComponent(id(phase3.containerBookingId))'
|
||||
|
||||
@@ -36,7 +36,7 @@ test('port link mutates the simulation through the frozen four-field request', (
|
||||
})
|
||||
|
||||
test('HTTP fixture keeps simulation and plan identities and creates allocation at link', () => {
|
||||
assert.ok(fixture.includes("context.request('GET', `${CONTAINERS}/domestic/${containerId}`)"))
|
||||
assert.ok(fixture.includes("context.request('GET', `${DOMESTIC_CONTAINERS}/${containerId}`)"))
|
||||
assert.ok(!fixture.includes("context.request('GET', `${CONTAINERS}/${containerId}`)"))
|
||||
assert.match(fixture, /关联前后内部柜次、计划和排货明细身份稳定/)
|
||||
assert.match(fixture, /订舱 allocation 原子创建并指向稳定模拟柜身份/)
|
||||
|
||||
@@ -110,3 +110,10 @@ test('dropship measurement completes the formal palletization workflow before sm
|
||||
assert.match(source, /dropship \? \['WAIT_PALLETIZE'\] : \['WAIT_SPLIT'\]/)
|
||||
assert.match(source, /一件代发合托完成后进入智能分柜门禁/)
|
||||
})
|
||||
|
||||
test('confirmed package count reconciles an uncertain transport result before retrying', () => {
|
||||
assert.match(source, /async function updateConfirmedPackageCount/)
|
||||
assert.match(source, /recoveredFromTransportFailure: true/)
|
||||
assert.match(source, /Number\(detail\?\.version\) > Number\(body\.measurementVersion\)/)
|
||||
assert.match(source, /await updateConfirmedPackageCount\(context, measurementPath/)
|
||||
})
|
||||
|
||||
@@ -536,6 +536,29 @@ async function requestWithHeaders(context, method, path, body, extraHeaders) {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateConfirmedPackageCount(context, measurementPath, itemId, body) {
|
||||
const updatePath = measurementPath.replace(/\?.*$/, '')
|
||||
+ `/items/${encodeURIComponent(itemId)}/confirmed-package-count?${measurementPath.split('?')[1] || ''}`
|
||||
let lastResponse = null
|
||||
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
||||
lastResponse = await context.request('PUT', updatePath, body)
|
||||
if (lastResponse?.http !== 0) return lastResponse
|
||||
const recovered = await context.request('GET', measurementPath)
|
||||
const detail = recovered?.http === 200 && String(recovered?.body?.code) === '200'
|
||||
? recovered.body?.data : null
|
||||
const item = detail?.items?.find((candidate) => asId(candidate?.id) === asId(itemId))
|
||||
if (Number(item?.confirmedPackageCount) === Number(body.confirmedPackageCount)
|
||||
&& Number(detail?.version) > Number(body.measurementVersion)) {
|
||||
return {
|
||||
...recovered, method: 'GET', path: measurementPath, recoveredFromTransportFailure: true,
|
||||
body: { ...recovered.body, data: { ...item, measurementVersion: Number(detail.version) } },
|
||||
}
|
||||
}
|
||||
if (attempt < 2) await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
}
|
||||
return lastResponse
|
||||
}
|
||||
|
||||
function finishScenario(context, start) {
|
||||
const steps = context.steps.slice(start)
|
||||
context.scenarios.push({
|
||||
@@ -651,11 +674,10 @@ export async function run(context) {
|
||||
&& closeTo(firstItem?.heightCm, firstDimensions.height),
|
||||
`itemFound=${Boolean(firstItem)}, imageFileId=${asId(firstItem?.intelligentImageFileId) || 'missing'}`, OPTIONS)
|
||||
|
||||
const countResponse = await context.request('PUT',
|
||||
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${firstScan.itemId}/confirmed-package-count?${query(tenantId)}`,
|
||||
const countResponse = await updateConfirmedPackageCount(context, measurementPath, firstScan.itemId,
|
||||
{ measurementVersion: Number(firstScan.measurementVersion), confirmedPackageCount: originalPackageCount })
|
||||
const countResult = context.apiSuccess(MODULE, '人工确认当前测量代表箱数', countResponse,
|
||||
`confirmedPackageCount=${originalPackageCount}`, OPTIONS)
|
||||
`confirmedPackageCount=${originalPackageCount}, transportRecovered=${countResponse?.recoveredFromTransportFailure === true}`, OPTIONS)
|
||||
context.assert(MODULE, '人工箱数更新且测量版本递增', countResponse,
|
||||
Number(countResult?.confirmedPackageCount) === originalPackageCount
|
||||
&& Number(countResult?.measurementVersion) === Number(firstScan.measurementVersion) + 1,
|
||||
@@ -722,8 +744,7 @@ export async function run(context) {
|
||||
`detailCount=${incrementalDetailCount}, packageCounts=${detailPackageCounts.join('+')}, frozen=${originalPackageCount}`, OPTIONS)
|
||||
if (expectedPackages === originalPackageCount) {
|
||||
const firstDetailPackageCount = detailPackageCounts[0]
|
||||
const resetFirstCountResponse = await context.request('PUT',
|
||||
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${firstScan.itemId}/confirmed-package-count?${query(tenantId)}`,
|
||||
const resetFirstCountResponse = await updateConfirmedPackageCount(context, measurementPath, firstScan.itemId,
|
||||
{ measurementVersion: Number(measured?.version), confirmedPackageCount: firstDetailPackageCount })
|
||||
const resetFirstCount = context.apiSuccess(MODULE,
|
||||
'增量 fixture 将首条货物确认箱数恢复为自身箱数', resetFirstCountResponse,
|
||||
@@ -751,8 +772,7 @@ export async function run(context) {
|
||||
&& asId(scanned?.itemId) !== asId(firstScan.itemId),
|
||||
`itemId=${asId(scanned?.itemId) || 'missing'}, inboundDetailId=${asId(scanned?.inboundDetailId) || 'pending-query'}`, OPTIONS)
|
||||
if (!scanned?.itemId) continue
|
||||
const countDetailResponse = await context.request('PUT',
|
||||
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${scanned.itemId}/confirmed-package-count?${query(tenantId)}`,
|
||||
const countDetailResponse = await updateConfirmedPackageCount(context, measurementPath, scanned.itemId,
|
||||
{ measurementVersion: Number(scanned.measurementVersion), confirmedPackageCount: detailPackageCount })
|
||||
const countDetail = context.apiSuccess(MODULE,
|
||||
`增量第${detailIndex + 1}条货物确认自身箱数`, countDetailResponse,
|
||||
|
||||
@@ -71,7 +71,7 @@ test('records LOADED and clears the reason only after a real LOADED reread', ()
|
||||
|
||||
test('container loading follows push, claim, real pick, completed sequence', () => {
|
||||
const value = source('./phase3-container-loading.mjs')
|
||||
assert.match(value, /const containerDetail = containerWorkbench\?\.container/)
|
||||
assert.match(value, /const containerDetail = containerRecord\(context\.apiSuccess/)
|
||||
assert.match(value, /\['EDITING', 'WAIT_LOADING'\]\.includes\(containerDetail\?\.planStatus\)/)
|
||||
assertOrder(value, [
|
||||
"pushed?.status === 'EDITING'",
|
||||
|
||||
@@ -183,7 +183,7 @@ test('PDA receiving lifecycle is ordered from candidate claim through photos, mo
|
||||
'/current/receipts/bulk', '/current/receipts/carton',
|
||||
"'FBA模式硬拒绝逐箱收货'", "'OVERSEAS模式硬拒绝批量收货'",
|
||||
'PHOTO_ROLES.every', "finalStatus === 'COMPLETED'", 'completedContainers.push',
|
||||
'completedContainerFacts.push', 'expectedPalletCost', 'unitPrice',
|
||||
'completedContainerFacts.push', 'expectedPalletCost', 'physicalPallets', 'resolvedPallet.data?.unitPrice',
|
||||
]) assert.ok(source.includes(token), token)
|
||||
})
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ test('PDA operations run immediately after receiving and all later consumers dep
|
||||
test('receiving hands off only transient credentials and completed warehouse facts', () => {
|
||||
assert.match(receivingSource, /context\.context\.phase4OverseasPdaOperations = \{/)
|
||||
assert.match(receivingSource, /completedFixtures: \[\]/)
|
||||
assert.match(receivingSource, /merchantToken,/)
|
||||
assert.match(receivingSource, /if \(!handoffReady\) \{[\s\S]*await cleanup\(\)/)
|
||||
assert.doesNotMatch(receivingSource, /createdResources\.phase4OverseasPdaOperations/)
|
||||
assert.doesNotMatch(receivingSource, /primary: primary \? \{[^}]*password/)
|
||||
@@ -47,6 +48,8 @@ test('P2 to P5 cover all frozen endpoints, replays and conflict boundaries witho
|
||||
for (const marker of ['按原数计数', '同键重放', '同键异载荷A0409', '旧版本A0409',
|
||||
'SUBMITTED', '移回原库位', 'WAIT_REVIEW', 'REWORK', 'APPROVE', 'COMPLETED',
|
||||
'chargeAmount: 0.01', 'cartonMark:', 'fbaCartonMark:',
|
||||
'inventory-candidates', 'sourceInventoryBalanceId:', 'sourceInventoryPositionId:',
|
||||
'sourceInboundOrderId:', 'operationWarehouseId:',
|
||||
'P5换标费用冻结后开放PDA作业', 'P5换标完成后钱包结算收敛']) {
|
||||
assert.ok(source.includes(marker), marker)
|
||||
}
|
||||
@@ -78,6 +81,7 @@ test('fixture failure still disables both picker accounts and removes all transi
|
||||
marker: 'PDAOPS-CONTRACT',
|
||||
primary: { membershipId: '1', username: 'primary', token: 'primary-secret-token' },
|
||||
secondary: { membershipId: '2', username: 'secondary', token: 'secondary-secret-token' },
|
||||
merchantToken: 'merchant-secret-token',
|
||||
completedFixtures: [],
|
||||
async cleanup() { cleanups.push('secondary', 'primary') },
|
||||
}
|
||||
@@ -95,6 +99,7 @@ test('fixture failure still disables both picker accounts and removes all transi
|
||||
assert.deepEqual(cleanups, ['secondary', 'primary'])
|
||||
assert.equal(handoff.primary.token, undefined)
|
||||
assert.equal(handoff.secondary.token, undefined)
|
||||
assert.equal(handoff.merchantToken, undefined)
|
||||
assert.equal(context.context.phase4OverseasPdaOperations, undefined)
|
||||
assert.ok(gaps.some((gap) => gap[2] === 'P2-P5本轮PDA收货交接'))
|
||||
assert.ok(steps.some((step) => !step.passed && step.options?.deploymentBlocker))
|
||||
|
||||
@@ -328,29 +328,51 @@ async function verifyPalletOperations(context, token, fixture) {
|
||||
return success(unbound.response)
|
||||
}
|
||||
|
||||
async function verifyRelabel(context, token, fixture) {
|
||||
async function verifyRelabel(context, token, merchantToken, fixture) {
|
||||
const customerId = id(fixture.order?.order?.customerId || context.createdResources?.phase4OverseasPda?.customerId)
|
||||
const customerOrderNo = fixture.order?.order?.orderNo || fixture.order?.order?.sourceOrderNo
|
||||
|| `PDA-${context.marker}`
|
||||
const inventory = fixture.inventory.find((item) => nonBlank(item?.skuCode))
|
||||
if (!customerId || !inventory?.skuCode || !fixture.overseasWarehouseId) {
|
||||
blocker(context, 'P5本轮库存换标申请', '缺少本轮完成库存的客户、SKU或海外仓事实')
|
||||
if (!customerId || !inventory?.skuCode || !fixture.overseasWarehouseId || !nonBlank(merchantToken)) {
|
||||
blocker(context, 'P5本轮库存换标申请', '缺少本轮完成库存的客户、SKU、海外仓或客户令牌事实')
|
||||
return false
|
||||
}
|
||||
if (!await fundRelabelWallet(context, customerId)) return false
|
||||
const inventoryCandidatesResponse = await context.requestOverseas('GET', query(
|
||||
'/api/merchant/v1/last-leg/relabel-applications/inventory-candidates', {
|
||||
operationWarehouseId: fixture.overseasWarehouseId, inboundCode: fixture.inboundCode,
|
||||
}), undefined, {}, merchantToken)
|
||||
const inventoryCandidates = success(inventoryCandidatesResponse)
|
||||
&& Array.isArray(inventoryCandidatesResponse.body?.data) ? inventoryCandidatesResponse.body.data : []
|
||||
const source = inventoryCandidates.find((item) => id(item?.inventoryBalanceId) === id(inventory?.balanceId))
|
||||
|| inventoryCandidates.find((item) => item?.skuCode === inventory.skuCode
|
||||
&& item?.inboundCode === fixture.inboundCode)
|
||||
const sourcePosition = source?.positions?.find((item) => id(item?.positionId) === id(inventory?.positionId)
|
||||
&& Number(item?.availablePackages) >= 3)
|
||||
|| source?.positions?.find((item) => Number(item?.availablePackages) >= 3)
|
||||
context.addStep(MODULE, 'P5客户库存候选返回换标权威来源', inventoryCandidatesResponse,
|
||||
Boolean(source) && Boolean(sourcePosition) && id(source?.inboundOrderId) !== '',
|
||||
`balanceId=${id(source?.inventoryBalanceId) || 'missing'}, positionId=${id(sourcePosition?.positionId) || 'missing'}, inboundOrderId=${id(source?.inboundOrderId) || 'missing'}; ${trace(inventoryCandidatesResponse)}`, OPTIONS)
|
||||
if (!source || !sourcePosition || !id(source.inboundOrderId)) {
|
||||
blocker(context, 'P5本轮库存换标来源', '客户库存候选未返回本轮库存的余额、至少三箱位置和来源入库单', inventoryCandidatesResponse)
|
||||
return false
|
||||
}
|
||||
const relabelBase = '/api/platform/v1/last-leg/relabel-applications'
|
||||
const pdaBase = '/api/pda/v1/last-leg/relabel-tasks'
|
||||
const newSku = `${inventory.skuCode}-NEW-${String(context.marker).slice(-8)}`.slice(0, 128)
|
||||
const createBody = {
|
||||
customerOrderNo: String(customerOrderNo).slice(0, 128), overseasWarehouseId: id(fixture.overseasWarehouseId),
|
||||
customerOrderNo: String(customerOrderNo).slice(0, 128), operationWarehouseId: id(fixture.overseasWarehouseId),
|
||||
labelDeadline: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 19),
|
||||
contactName: 'PDA换标验收', contactPhone: '13800000006', chargeAmount: 0.01, remark: context.marker,
|
||||
idempotencyKey: key(context, 'RELABEL-CREATE'),
|
||||
items: [{ skuCode: inventory.skuCode, productNameZh: inventory.productName || '本轮库存商品',
|
||||
items: [{ sourceInventoryBalanceId: id(source.inventoryBalanceId),
|
||||
sourceInventoryPositionId: id(sourcePosition.positionId), sourceInboundOrderId: id(source.inboundOrderId),
|
||||
skuCode: source.skuCode, productNameZh: source.productNameZh || inventory.productName || '本轮库存商品',
|
||||
totalPackageCount: 3, notifiedPackageCount: 3, shipmentNo: `SHIP-${context.marker}`.slice(0, 128),
|
||||
newSkuCode: newSku, labelRequirement: context.marker,
|
||||
cartonMark: `CM-${String(context.marker).slice(-32)}`,
|
||||
fbaCartonMark: `FBA-${String(context.marker).slice(-32)}` }],
|
||||
fbaCartonMark: `FBA-${String(context.marker).slice(-32)}`,
|
||||
destinationFbaWarehouseCode: source.destinationFbaWarehouseCode }],
|
||||
}
|
||||
const createdResponse = await context.requestOverseas('POST',
|
||||
query(relabelBase, { customerId }), createBody)
|
||||
@@ -469,6 +491,7 @@ async function verifyRelabel(context, token, fixture) {
|
||||
export async function run(context) {
|
||||
const handoff = context.context?.phase4OverseasPdaOperations
|
||||
const token = handoff?.primary?.token
|
||||
const merchantToken = handoff?.merchantToken
|
||||
try {
|
||||
if (handoff?.marker !== context.marker || !nonBlank(token)
|
||||
|| !Array.isArray(handoff.completedFixtures) || handoff.completedFixtures.length === 0) {
|
||||
@@ -488,13 +511,14 @@ export async function run(context) {
|
||||
await verifyStocktake(context, token, fixture)
|
||||
await verifyInventoryMove(context, token, fixture)
|
||||
await verifyPalletOperations(context, token, fixture)
|
||||
await verifyRelabel(context, token, fixture)
|
||||
await verifyRelabel(context, token, merchantToken, fixture)
|
||||
} finally {
|
||||
try {
|
||||
if (typeof handoff?.cleanup === 'function') await handoff.cleanup()
|
||||
} finally {
|
||||
if (handoff?.primary) delete handoff.primary.token
|
||||
if (handoff?.secondary) delete handoff.secondary.token
|
||||
if (handoff) delete handoff.merchantToken
|
||||
delete context.context.phase4OverseasPdaOperations
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ async function verifyWarehouseQueries(context, token, inboundCode, pallet, desti
|
||||
&& Number(resolvedPallet.data?.occupiedPackages) > 0,
|
||||
`palletId=${id(resolvedPallet.data?.palletId) || 'missing'}; ${trace(resolvedPallet.response)}`, OPTIONS)
|
||||
|
||||
if (Number(resolvedPallet.data?.unitPrice) > 0) pallet.unitPrice = Number(resolvedPallet.data.unitPrice)
|
||||
|
||||
const locationCode = resolvedPallet.data?.locationCode || pallet.locationCode
|
||||
const location = await pda(context, token, 'POST', '完成入库后扫码查询库位',
|
||||
'/api/pda/v1/last-leg/locations/resolve', { code: locationCode })
|
||||
@@ -693,12 +695,6 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
|
||||
context.addStep(MODULE, '完成后current返回404正常空态', after, notFound(after), trace(after), OPTIONS)
|
||||
context.createdResources.phase4OverseasPda.finalStatus = completed.data?.status || null
|
||||
if (context.createdResources.phase4OverseasPda.finalStatus === 'COMPLETED') {
|
||||
context.createdResources.phase4OverseasPda.completedContainerFacts.push({
|
||||
containerNo,
|
||||
containerBookingId: id(preferredProducer?.containerBookingId),
|
||||
expectedPalletCost: [...pallets.values()].reduce((sum, item) => sum + Number(item.unitPrice || 0), 0),
|
||||
palletIds: [...pallets.values()].map((item) => item.palletId),
|
||||
})
|
||||
const order = await captureCompletedOrder(context, containerNo, preferredProducer?.tenantId)
|
||||
for (const inbound of current.data.inbounds.filter((item) => pallets.has(item.inboundCode))) {
|
||||
const warehouseFacts = await verifyWarehouseQueries(context, token, inbound.inboundCode,
|
||||
@@ -711,6 +707,14 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
|
||||
})
|
||||
}
|
||||
}
|
||||
const physicalPallets = [...new Map([...pallets.values()]
|
||||
.map((item) => [id(item.palletId), item])).values()]
|
||||
context.createdResources.phase4OverseasPda.completedContainerFacts.push({
|
||||
containerNo,
|
||||
containerBookingId: id(preferredProducer?.containerBookingId),
|
||||
expectedPalletCost: physicalPallets.reduce((sum, item) => sum + Number(item.unitPrice || 0), 0),
|
||||
palletIds: physicalPallets.map((item) => item.palletId),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,6 +751,7 @@ export async function run(context) {
|
||||
primary: primary ? { membershipId: primary.membershipId, username: primary.username, token: primary.token } : null,
|
||||
secondary: secondary ? { membershipId: secondary.membershipId, username: secondary.username, token: secondary.token } : null,
|
||||
overseasWarehouseId: sharedWarehouse.id,
|
||||
merchantToken,
|
||||
completedFixtures: [],
|
||||
cleanup,
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ test('HTTP scenario freezes normal, invalid, replay, concurrency, partial and PD
|
||||
test('container API uses one stable long string identity', () => {
|
||||
const endpoint = openapi.slice(openapi.indexOf(` ${CONTAINER_CONVERT_ENDPOINT}:`),
|
||||
openapi.indexOf(' /api/platform/v1/last-leg/dispatches:', openapi.indexOf(` ${CONTAINER_CONVERT_ENDPOINT}:`)))
|
||||
assert.match(endpoint, /required: \[sourceContainerBookingId\]/)
|
||||
assert.match(endpoint, /required: \[sourceContainerBookingId, snapshotVersion, snapshotHash\]/)
|
||||
assert.match(endpoint, /sourceContainerBookingId:\s*\{ type: string/)
|
||||
assert.match(controller, /convertContainer/)
|
||||
assert.match(adminApi, /sourceContainerBookingId: string/)
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"services": 15
|
||||
},
|
||||
"openapi": {
|
||||
"documents": 39,
|
||||
"coveredServices": 8,
|
||||
"codeFirstServices": 7
|
||||
"documents": 43,
|
||||
"coveredServices": 10,
|
||||
"codeFirstServices": 5
|
||||
},
|
||||
"reports": {
|
||||
"jacocoPresent": 21,
|
||||
|
||||
@@ -65,9 +65,9 @@ test('repository freezes the current topology and non-blocking aggregate summary
|
||||
const summary = await buildQualitySummary(repositoryRoot)
|
||||
const baseline = JSON.parse(await readFile(path.join(repositoryRoot, 'scripts', 'java-quality-baseline.json'), 'utf8'))
|
||||
assert.deepEqual(summary.topology, { modules: 22, codeModules: 21, services: 15 })
|
||||
assert.equal(summary.openapi.documents, 40)
|
||||
assert.equal(summary.openapi.coveredServices.length, 8)
|
||||
assert.equal(summary.openapi.codeFirstServices.length, 7)
|
||||
assert.equal(summary.openapi.documents, 43)
|
||||
assert.equal(summary.openapi.coveredServices.length, 10)
|
||||
assert.equal(summary.openapi.codeFirstServices.length, 5)
|
||||
assert.equal(baseline.mode, 'report-only')
|
||||
assert.deepEqual(baseline.topology, summary.topology)
|
||||
assert.equal(baseline.modules.length, 21)
|
||||
|
||||
@@ -80,6 +80,9 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
'firstleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '价目文件上传无幂等键,读取可重复'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PriceBookWarehouseClient.java',
|
||||
'firstleg-service', 'warehouse-service', ['REQUEST'], 'READ', 'SAFE_READ', '读取仓库/渠道主数据'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PrintableOrderInboundClient.java',
|
||||
'firstleg-service', 'order-service', ['REQUEST'], 'WRITE', 'BUSINESS_KEY',
|
||||
'首次打印按客户与订单预留稳定入仓码,重复调用复用已冻结快照'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/PrintPdfClient.java',
|
||||
'firstleg-service', 'print-service', ['REQUEST'], 'READ', 'SAFE_READ', '无状态 PDF 渲染,POST 为计算型读取'),
|
||||
feign('firstleg/src/main/java/com/baiyue/firstleg/client/TruckAppointmentFileClient.java',
|
||||
@@ -154,6 +157,8 @@ export const SYNCHRONOUS_CLIENTS = Object.freeze([
|
||||
'gateway-overseas', 'auth-service', ['INFRASTRUCTURE'], 'READ', 'SAFE_READ', 'POST PDA 会话校验,失败关闭'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/customervisit/gateway/HttpCustomerVisitFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '回访附件上传无幂等键,归属校验可重复'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/gateway/HttpExceptionEvidenceFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '异常处理证据上传无幂等键,归属校验可重复'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpUnloadPhotoFileGateway.java',
|
||||
'lastleg-service', 'file-service', ['REQUEST'], 'READ_WRITE', 'MIXED', '上传无幂等键,元数据校验可重复'),
|
||||
http('lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java',
|
||||
|
||||
@@ -98,15 +98,15 @@ async function discoverFeignEdges() {
|
||||
return clients.sort((left, right) => left.path.localeCompare(right.path))
|
||||
}
|
||||
|
||||
test('all 55 Feign clients have reviewed context, access and idempotency metadata', async () => {
|
||||
test('all 56 Feign clients have reviewed context, access and idempotency metadata', async () => {
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'FEIGN')
|
||||
.map(({ path, source, target }) => ({ path, source, target }))
|
||||
.sort((left, right) => left.path.localeCompare(right.path))
|
||||
assert.equal(reviewed.length, 55)
|
||||
assert.equal(reviewed.length, 56)
|
||||
assert.deepEqual(await discoverFeignEdges(), reviewed)
|
||||
})
|
||||
|
||||
test('all 19 raw HTTP transports have reviewed context, access and idempotency metadata', async () => {
|
||||
test('all 20 raw HTTP transports have reviewed context, access and idempotency metadata', async () => {
|
||||
const discovered = []
|
||||
for (const module of ['common-audit', 'event-bridge-service', 'gateway-overseas', 'lastleg-service', 'integration-service']) {
|
||||
for (const file of await javaFiles(path.join(root, module, 'src', 'main', 'java'))) {
|
||||
@@ -118,7 +118,7 @@ test('all 19 raw HTTP transports have reviewed context, access and idempotency m
|
||||
}
|
||||
const reviewed = SYNCHRONOUS_CLIENTS.filter(client => client.transport === 'HTTP')
|
||||
.map(client => client.path).sort()
|
||||
assert.equal(reviewed.length, 19)
|
||||
assert.equal(reviewed.length, 20)
|
||||
assert.deepEqual(discovered.sort(), reviewed)
|
||||
})
|
||||
|
||||
@@ -226,7 +226,8 @@ test('Order and Firstleg remain a bilateral REQUEST SCC until loading-complete t
|
||||
assert.deepEqual(firstlegToOrder.map(client => client.path).sort(), [
|
||||
'firstleg/src/main/java/com/baiyue/firstleg/client/OrderCargoClient.java',
|
||||
'firstleg/src/main/java/com/baiyue/firstleg/client/OrderFulfillmentSplitClient.java',
|
||||
'firstleg/src/main/java/com/baiyue/firstleg/client/PendingReviewOrderClient.java'
|
||||
'firstleg/src/main/java/com/baiyue/firstleg/client/PendingReviewOrderClient.java',
|
||||
'firstleg/src/main/java/com/baiyue/firstleg/client/PrintableOrderInboundClient.java'
|
||||
])
|
||||
assert.ok(stronglyConnectedComponents(graphEdges('REQUEST')).some(component =>
|
||||
component.includes('firstleg-service') && component.includes('order-service')))
|
||||
|
||||
Reference in New Issue
Block a user