完成单机TEST统一入口与可靠投递回归收尾

This commit is contained in:
2026-09-29 19:48:34 +08:00
parent 64a70c9f28
commit 6c0a026cc4
89 changed files with 968 additions and 983 deletions
@@ -111,7 +111,7 @@ async function createAndVerifyPartnerWarehouse(context, resource) {
}
async function captureWarehouseOptionsBeforeCreate(context, resource) {
const response = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const response = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const options = requireData(context, '创建合作仓前查询报价候选缓存基线', response)
context.assert(MODULE, '合作仓候选基线为规范数组', response,
Array.isArray(options) && options.every((item) => asId(item?.warehouseId)
@@ -144,7 +144,7 @@ function quoteUpdatePayload(created, context, version) {
}
async function quoteLifecycle(context, resource) {
const optionsResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const optionsResponse = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const options = requireData(context, '查询海外报价合作仓候选', optionsResponse)
context.assert(MODULE, '报价候选只来自海外正式接口', optionsResponse,
Array.isArray(options) && options.every((item) => asId(item.warehouseId)
@@ -156,37 +156,35 @@ async function quoteLifecycle(context, resource) {
optionIds(options).includes(createdId), `createdId=${createdId}, ids=${optionIds(options).join(',') || 'empty'}`, BLOCKER)
}
const refreshResponse = await context.requestOverseas('GET',
`${OVERSEAS_BASE}/overseas-quotes/warehouse-options?refresh=true`)
const refreshResponse = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options?refresh=true`)
const refreshed = requireData(context, '强制刷新海外报价合作仓候选', refreshResponse)
const option = Array.isArray(refreshed) ? refreshed.find((item) => asId(item.warehouseId) === createdId) : null
context.assert(MODULE, '刷新后新建合作仓立即可见', refreshResponse, Boolean(option),
`createdId=${createdId}, ids=${optionIds(refreshed).join(',') || 'empty'}`, BLOCKER)
if (!option) throw new Error(`warehouse-options refresh=true 未返回本轮合作仓 ${createdId}`)
const cachedResponse = await context.requestOverseas('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const cachedResponse = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes/warehouse-options`)
const cached = requireData(context, '刷新后读取30分钟正缓存', cachedResponse)
context.assert(MODULE, '30分钟正缓存与刷新快照一致', cachedResponse,
JSON.stringify(cached) === JSON.stringify(refreshed),
`refreshed=${optionIds(refreshed).join(',')}, cached=${optionIds(cached).join(',')}`, BLOCKER)
const payload = quoteCreatePayload(context, asId(option.warehouseId))
const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/overseas-quotes`, payload)
const createResponse = await context.request('POST', `${OVERSEAS_BASE}/overseas-quotes`, payload)
let detail = requireData(context, '新增海外报价', createResponse)
const id = asId(detail?.summary?.id)
if (!id || !recordVersion(detail?.summary?.recordVersion)) throw new Error('报价创建响应缺少字符串ID或recordVersion')
resource.quote = { id, warehouseId: payload.warehouseId, recordVersion: Number(detail.summary.recordVersion), finalStatus: detail.summary.status }
const path = `${OVERSEAS_BASE}/overseas-quotes/${encodeURIComponent(id)}`
const detailResponse = await context.requestOverseas('GET', path)
const detailResponse = await context.request('GET', path)
detail = requireData(context, '查询海外报价详情', detailResponse)
context.assert(MODULE, '报价详情长ID、费用和版本', detailResponse,
asId(detail?.summary?.id) === id && recordVersion(detail?.summary?.recordVersion)
&& Array.isArray(detail?.warehouseFees) && detail.warehouseFees.length > 0,
`id=${asId(detail?.summary?.id) || 'missing'}, version=${detail?.summary?.recordVersion ?? 'missing'}`, BLOCKER)
const listResponse = await context.requestOverseas('GET',
`${OVERSEAS_BASE}/overseas-quotes?page=1&size=20&keyword=${encodeURIComponent(context.marker)}`)
const listResponse = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes?page=1&size=20&keyword=${encodeURIComponent(context.marker)}`)
const page = requireData(context, '分页查询marker海外报价', listResponse)
context.assert(MODULE, '报价列表包含本轮长ID与recordVersion', listResponse,
Array.isArray(page?.records) && page.records.some((item) => asId(item.id) === id && recordVersion(item.recordVersion)),
@@ -194,22 +192,22 @@ async function quoteLifecycle(context, resource) {
const staleVersion = Number(detail.summary.recordVersion)
const updatePayload = quoteUpdatePayload({ ...payload, transitPrices: payload.transitPrices }, context, staleVersion)
const updateResponse = await context.requestOverseas('PUT', path, updatePayload)
const updateResponse = await context.request('PUT', path, updatePayload)
detail = requireData(context, '编辑海外报价', updateResponse)
resource.quote.recordVersion = Number(detail.summary.recordVersion)
context.assert(MODULE, '报价编辑版本递增', updateResponse,
detail.summary.name === updatePayload.name && resource.quote.recordVersion === staleVersion + 1,
`before=${staleVersion}, after=${resource.quote.recordVersion}`, BLOCKER)
const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, name: `${context.marker} stale` })
const conflictResponse = await context.request('PUT', path, { ...updatePayload, name: `${context.marker} stale` })
context.expectConflict(MODULE, '报价旧recordVersion编辑冲突', conflictResponse, BLOCKER.scenario)
const afterConflictResponse = await context.requestOverseas('GET', path)
const afterConflictResponse = await context.request('GET', path)
detail = requireData(context, '报价冲突后重新查询', afterConflictResponse)
context.assert(MODULE, '报价冲突未覆盖新值', afterConflictResponse,
detail.summary.name === updatePayload.name && Number(detail.summary.recordVersion) === resource.quote.recordVersion,
`name=${detail.summary.name}, version=${detail.summary.recordVersion}`, BLOCKER)
const activateResponse = await context.requestOverseas('PATCH', `${path}/status`, {
const activateResponse = await context.request('PATCH', `${path}/status`, {
status: 'ACTIVE', recordVersion: resource.quote.recordVersion,
})
detail = requireData(context, '启用海外报价', activateResponse)
@@ -218,7 +216,7 @@ async function quoteLifecycle(context, resource) {
context.assert(MODULE, '报价状态切换为ACTIVE', activateResponse,
detail.summary.status === 'ACTIVE', `status=${detail.summary.status}`, BLOCKER)
const csv = await context.requestOverseasBinary('GET', `${OVERSEAS_BASE}/overseas-quotes/export?keyword=${encodeURIComponent(context.marker)}`)
const csv = await context.requestBinary('GET', `${OVERSEAS_BASE}/overseas-quotes/export?keyword=${encodeURIComponent(context.marker)}`)
assertBinaryCsv(context, '导出海外报价CSV', csv,
'报价ID,报价名称,合作仓编码,合作仓名称,报价方,币种,生效开始,生效结束,版本号,状态,更新时间',
[id, context.marker])
@@ -232,7 +230,7 @@ async function chooseSafeExchangeMonth(context) {
const serial = ((seed + offset) % 120)
const month = `${2090 + Math.floor(serial / 12)}-${String((serial % 12) + 1).padStart(2, '0')}`
const path = `${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=${month}&monthTo=${month}`
const response = await context.requestOverseas('GET', path)
const response = await context.request('GET', path)
const page = requireData(context, `探测安全汇率月份${month}`, response)
if (Array.isArray(page?.records) && page.records.length === 0 && Number(page.total) === 0) return month
}
@@ -242,12 +240,12 @@ async function chooseSafeExchangeMonth(context) {
async function exchangeLifecycle(context, resource) {
const month = await chooseSafeExchangeMonth(context)
const createPayload = { month, usdToCnyAverage: 7.123456, remark: `${context.marker} safe marker month` }
const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/exchange-rates`, createPayload)
const createResponse = await context.request('POST', `${OVERSEAS_BASE}/exchange-rates`, createPayload)
let rate = requireData(context, '新增安全marker月汇率', createResponse)
resource.exchangeRate = { month, recordVersion: Number(rate.recordVersion), finalStatus: rate.status }
const path = `${OVERSEAS_BASE}/exchange-rates/${encodeURIComponent(month)}`
const detailResponse = await context.requestOverseas('GET', path)
const detailResponse = await context.request('GET', path)
rate = requireData(context, '查询marker月汇率详情', detailResponse)
context.assert(MODULE, '汇率详情月份、decimal与版本', detailResponse,
rate.month === month && Number(rate.usdToCnyAverage) > 0 && recordVersion(rate.recordVersion),
@@ -255,7 +253,7 @@ async function exchangeLifecycle(context, resource) {
const staleVersion = Number(rate.recordVersion)
const updatePayload = { recordVersion: staleVersion, usdToCnyAverage: 7.234567, remark: `${context.marker} overwritten once` }
const updateResponse = await context.requestOverseas('PUT', path, updatePayload)
const updateResponse = await context.request('PUT', path, updatePayload)
rate = requireData(context, '覆盖维护marker月汇率', updateResponse)
resource.exchangeRate.recordVersion = Number(rate.recordVersion)
context.assert(MODULE, '汇率覆盖版本递增', updateResponse,
@@ -263,16 +261,16 @@ async function exchangeLifecycle(context, resource) {
&& resource.exchangeRate.recordVersion === staleVersion + 1,
`before=${staleVersion}, after=${resource.exchangeRate.recordVersion}`, BLOCKER)
const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, usdToCnyAverage: 7.345678 })
const conflictResponse = await context.request('PUT', path, { ...updatePayload, usdToCnyAverage: 7.345678 })
context.expectConflict(MODULE, '汇率旧recordVersion覆盖冲突', conflictResponse, BLOCKER.scenario)
const afterConflictResponse = await context.requestOverseas('GET', path)
const afterConflictResponse = await context.request('GET', path)
rate = requireData(context, '汇率冲突后重新查询', afterConflictResponse)
context.assert(MODULE, '汇率冲突未覆盖新值', afterConflictResponse,
Number(rate.usdToCnyAverage) === updatePayload.usdToCnyAverage
&& Number(rate.recordVersion) === resource.exchangeRate.recordVersion,
`rate=${rate.usdToCnyAverage}, version=${rate.recordVersion}`, BLOCKER)
const statusResponse = await context.requestOverseas('PATCH', `${path}/status`, {
const statusResponse = await context.request('PATCH', `${path}/status`, {
status: 'INACTIVE', recordVersion: resource.exchangeRate.recordVersion,
remark: `${context.marker} lifecycle complete`,
})
@@ -282,14 +280,13 @@ async function exchangeLifecycle(context, resource) {
context.assert(MODULE, '汇率状态切换为INACTIVE', statusResponse,
rate.status === 'INACTIVE', `status=${rate.status}`, BLOCKER)
const listResponse = await context.requestOverseas('GET',
`${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=${month}&monthTo=${month}&status=INACTIVE`)
const listResponse = await context.request('GET', `${OVERSEAS_BASE}/exchange-rates?page=1&size=20&monthFrom=${month}&monthTo=${month}&status=INACTIVE`)
const page = requireData(context, '分页查询marker月汇率', listResponse)
context.assert(MODULE, '汇率列表包含本轮月份与recordVersion', listResponse,
Array.isArray(page?.records) && page.records.some((item) => item.month === month && recordVersion(item.recordVersion)),
`month=${month}, total=${page?.total ?? 'missing'}`, BLOCKER)
const csv = await context.requestOverseasBinary('GET',
const csv = await context.requestBinary('GET',
`${OVERSEAS_BASE}/exchange-rates/export?monthFrom=${month}&monthTo=${month}`)
assertBinaryCsv(context, '导出marker月汇率CSV', csv,
'月份,基准币种,报价币种,月平均汇率,状态,更新人,更新时间', [month, 'INACTIVE'])
@@ -311,14 +308,14 @@ function vehiclePayload(context, edited = false) {
async function vehicleLifecycle(context, resource) {
const createPayload = vehiclePayload(context)
const createResponse = await context.requestOverseas('POST', `${OVERSEAS_BASE}/vehicles`, createPayload)
const createResponse = await context.request('POST', `${OVERSEAS_BASE}/vehicles`, createPayload)
let vehicle = requireData(context, '新增marker车辆', createResponse)
const id = asId(vehicle.id)
if (!id || !recordVersion(vehicle.recordVersion)) throw new Error('车辆创建响应缺少字符串ID或recordVersion')
resource.vehicle = { id, plateNumber: createPayload.plateNumber, recordVersion: Number(vehicle.recordVersion), finalStatus: vehicle.status }
const path = `${OVERSEAS_BASE}/vehicles/${encodeURIComponent(id)}`
const detailResponse = await context.requestOverseas('GET', path)
const detailResponse = await context.request('GET', path)
vehicle = requireData(context, '查询marker车辆详情', detailResponse)
context.assert(MODULE, '车辆详情长ID、五态字段与版本', detailResponse,
asId(vehicle.id) === id && ['IN_SERVICE', 'DISPATCHED', 'MAINTENANCE', 'INACTIVE', 'RETURNED'].includes(vehicle.status)
@@ -327,7 +324,7 @@ async function vehicleLifecycle(context, resource) {
const staleVersion = Number(vehicle.recordVersion)
const updatePayload = { recordVersion: staleVersion, ...vehiclePayload(context, true) }
const updateResponse = await context.requestOverseas('PUT', path, updatePayload)
const updateResponse = await context.request('PUT', path, updatePayload)
vehicle = requireData(context, '编辑marker车辆', updateResponse)
const updatedVersion = Number(vehicle.recordVersion)
const updatedMileage = Number(vehicle.mileageKm)
@@ -337,15 +334,15 @@ async function vehicleLifecycle(context, resource) {
&& updatedVersion === Number(staleVersion) + 1,
`before=${staleVersion}, after=${updatedVersion}, mileage=${updatedMileage}`, BLOCKER)
const conflictResponse = await context.requestOverseas('PUT', path, { ...updatePayload, model: 'stale overwrite' })
const conflictResponse = await context.request('PUT', path, { ...updatePayload, model: 'stale overwrite' })
context.expectConflict(MODULE, '车辆旧recordVersion编辑冲突', conflictResponse, BLOCKER.scenario)
const afterConflictResponse = await context.requestOverseas('GET', path)
const afterConflictResponse = await context.request('GET', path)
vehicle = requireData(context, '车辆冲突后重新查询', afterConflictResponse)
context.assert(MODULE, '车辆冲突未覆盖新值', afterConflictResponse,
vehicle.model === updatePayload.model && Number(vehicle.recordVersion) === resource.vehicle.recordVersion,
`model=${vehicle.model}, version=${vehicle.recordVersion}`, BLOCKER)
const statusResponse = await context.requestOverseas('PATCH', `${path}/status`, {
const statusResponse = await context.request('PATCH', `${path}/status`, {
status: 'MAINTENANCE', recordVersion: resource.vehicle.recordVersion,
})
vehicle = requireData(context, '车辆切换安全维护状态', statusResponse)
@@ -354,14 +351,13 @@ async function vehicleLifecycle(context, resource) {
context.assert(MODULE, '车辆状态切换为MAINTENANCE', statusResponse,
vehicle.status === 'MAINTENANCE', `status=${vehicle.status}`, BLOCKER)
const listResponse = await context.requestOverseas('GET',
`${OVERSEAS_BASE}/vehicles?page=1&size=20&keyword=${encodeURIComponent(createPayload.plateNumber)}`)
const listResponse = await context.request('GET', `${OVERSEAS_BASE}/vehicles?page=1&size=20&keyword=${encodeURIComponent(createPayload.plateNumber)}`)
const page = requireData(context, '分页查询marker车辆', listResponse)
context.assert(MODULE, '车辆列表包含本轮长ID与recordVersion', listResponse,
Array.isArray(page?.records) && page.records.some((item) => asId(item.id) === id && recordVersion(item.recordVersion)),
`id=${id}, total=${page?.total ?? 'missing'}`, BLOCKER)
const csv = await context.requestOverseasBinary('GET', `${OVERSEAS_BASE}/vehicles/export?keyword=${encodeURIComponent(createPayload.plateNumber)}`)
const csv = await context.requestBinary('GET', `${OVERSEAS_BASE}/vehicles/export?keyword=${encodeURIComponent(createPayload.plateNumber)}`)
assertBinaryCsv(context, '导出marker车辆CSV', csv,
'车辆ID,车牌号,车型,油耗(L/100km),当前公里码(km),司机或外派单位,联系电话,区域,车辆归属,状态,更新时间',
[id, createPayload.plateNumber, 'MAINTENANCE'])
@@ -392,9 +388,8 @@ async function verifyCanonicalPermissionDenial(context, resource) {
const loginResponse = await context.request('POST', '/api/auth/login', { loginId: username, password }, '')
const login = requireData(context, '无主数据权限用户登录', loginResponse)
if (!login?.overseasAccessToken) throw new Error('无主数据权限用户未签发海外Token')
const denied = await context.requestOverseas('GET',
`${OVERSEAS_BASE}/overseas-quotes?page=1&size=1`, undefined, {}, login.overseasAccessToken)
if (!login?.accessToken) throw new Error('无主数据权限用户未签发统一Token')
const denied = await context.request('GET', `${OVERSEAS_BASE}/overseas-quotes?page=1&size=1`, undefined, login.accessToken, {})
assertForbidden(context, '无quote:list权限访问海外报价被拒绝', denied)
}
@@ -409,7 +404,7 @@ async function cleanupPartnerWarehouse(context, resource) {
async function cleanupVersioned(context, resource, key, path, extract) {
const current = resource[key]
if (!current) return
const detailResponse = await context.requestOverseas('GET', path)
const detailResponse = await context.request('GET', path)
const detail = data(detailResponse)
const value = extract(detail)
if (!success(detailResponse) || !value || !recordVersion(value.recordVersion)) {
@@ -420,7 +415,7 @@ async function cleanupVersioned(context, resource, key, path, extract) {
current.recordVersion = Number(value.recordVersion)
current.finalStatus = value.status
if (value.status === 'INACTIVE') return
const response = await context.requestOverseas('PATCH', `${path}/status`, {
const response = await context.request('PATCH', `${path}/status`, {
status: 'INACTIVE', recordVersion: current.recordVersion,
...(key === 'exchangeRate' ? { remark: `${context.marker} finally cleanup` } : {}),
})