fix: close phase4 overseas acceptance gaps
This commit is contained in:
@@ -35,20 +35,58 @@ async function expected(context, token, method, name, path, body, predicate) {
|
||||
return response
|
||||
}
|
||||
|
||||
async function loginPda(context) {
|
||||
async function loginMerchantForNegativeProbe(context) {
|
||||
const fixture = context.context?.merchantQualificationFixture
|
||||
if (!fixture?.username || !fixture?.password) {
|
||||
blockerGap(context, 'PDA客户账号', '缺少 merchant-qualification-fixture 生成的本轮客户账号;请保留默认全量前置模块')
|
||||
return null
|
||||
}
|
||||
if (!fixture?.username || !fixture?.password) return null
|
||||
const response = await context.request('POST', '/api/auth/login', { username: fixture.username, password: fixture.password }, '')
|
||||
const data = success(response) ? response.body?.data : null
|
||||
context.addStep(MODULE, '本轮客户登录并取得海外PDA令牌', response,
|
||||
context.addStep(MODULE, '本轮客户登录取得海外令牌用于PDA越权反证', response,
|
||||
success(response) && nonBlank(data?.overseasAccessToken),
|
||||
`overseasTokenPresent=${nonBlank(data?.overseasAccessToken)}; ${trace(response)}`, OPTIONS)
|
||||
return nonBlank(data?.overseasAccessToken) ? data.overseasAccessToken : null
|
||||
}
|
||||
|
||||
async function createPicker(context, index) {
|
||||
const rolesResponse = await context.request('GET', '/api/platform/v1/iam/roles')
|
||||
const roles = context.apiSuccess(MODULE, `查询海外PDA仓库操作员角色${index}`, rolesResponse, '', OPTIONS)
|
||||
const role = (Array.isArray(roles) ? roles : []).find((item) => item?.roleCode === 'WAREHOUSE_PICKER' && item?.status === 'ACTIVE')
|
||||
const permissionsResponse = await context.request('GET', '/api/platform/v1/iam/permissions')
|
||||
const permissions = context.apiSuccess(MODULE, `查询海外PDA权限资源${index}`, permissionsResponse, '', OPTIONS)
|
||||
const operate = (Array.isArray(permissions) ? permissions : []).find((item) => item?.permissionCode === 'lastleg:pda:operate')
|
||||
const assigned = Boolean(role && operate && (role.permissionIds || []).map(id).includes(id(operate.id)))
|
||||
context.assert(MODULE, `WAREHOUSE_PICKER精确具备海外PDA权限${index}`, rolesResponse, assigned,
|
||||
`roleId=${id(role?.id) || 'missing'}, permissionId=${id(operate?.id) || 'missing'}`, OPTIONS)
|
||||
if (!assigned) {
|
||||
blockerGap(context, '海外PDA仓库操作员权限', 'WAREHOUSE_PICKER未获得lastleg:pda:operate;必须先执行最新国内统一增量SQL并部署IAM')
|
||||
return null
|
||||
}
|
||||
const suffix = markerSuffix(context)
|
||||
const username = `pda_overseas_${suffix.toLowerCase()}_${index}`.slice(0, 64)
|
||||
const password = `Pda${suffix.replace(/[^A-Za-z0-9]/g, '')}A${index}9`
|
||||
const phone = await context.findAvailablePhone(MODULE)
|
||||
const payload = { username, displayName: `海外PDA验收操作员${index}-${suffix}`, phone,
|
||||
email: `${username}@example.com`, tenantId: '0', roleIds: [id(role.id)], password, status: 'ACTIVE' }
|
||||
const createdResponse = await context.request('POST', '/api/platform/v1/iam/users', payload)
|
||||
const created = context.apiSuccess(MODULE, `创建海外PDA仓库操作员${index}`, createdResponse, '', OPTIONS)
|
||||
const membershipId = id(created?.membershipId)
|
||||
context.assert(MODULE, `海外PDA操作员${index}冻结平台范围与角色`, createdResponse,
|
||||
longId(membershipId) && id(created?.tenantId) === '0' && (created?.roleIds || []).map(id).includes(id(role.id)),
|
||||
`membershipId=${membershipId || 'missing'}, tenantId=${id(created?.tenantId) || 'missing'}`, OPTIONS)
|
||||
if (!longId(membershipId)) return null
|
||||
const login = await context.request('POST', '/api/auth/login', { username, password }, '')
|
||||
const token = success(login) ? login.body?.data?.overseasAccessToken : null
|
||||
context.addStep(MODULE, `海外PDA操作员${index}登录取得双区域海外令牌`, login, nonBlank(token),
|
||||
`overseasTokenPresent=${nonBlank(token)}; ${trace(login)}`, OPTIONS)
|
||||
return { membershipId, username, token: nonBlank(token) ? token : null }
|
||||
}
|
||||
|
||||
async function disablePicker(context, picker, index) {
|
||||
if (!longId(picker?.membershipId)) return
|
||||
const response = await context.request('PUT', `/api/platform/v1/iam/users/${picker.membershipId}/status`, { status: 'DISABLED' })
|
||||
const disabled = success(response) && response.body?.data?.status === 'DISABLED'
|
||||
context.addStep(MODULE, `finally停用海外PDA仓库操作员${index}`, response, disabled, trace(response), OPTIONS)
|
||||
}
|
||||
|
||||
async function createPallet(context, inbound, index) {
|
||||
const warehouseId = id(inbound?.destinationWarehouseId)
|
||||
if (!longId(warehouseId)) return null
|
||||
@@ -160,17 +198,14 @@ async function receiveOverseas(context, token, inbound, pallet) {
|
||||
return true
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
context.createdResources.phase4OverseasPda = {
|
||||
marker: context.marker, containerNo: null, customerId: context.context?.merchantQualificationFixture?.tenantId || null,
|
||||
palletIds: [], locationIds: [], finalStatus: null,
|
||||
cleanup: 'Pallets and locations used by completed inventory are retained as acceptance evidence',
|
||||
}
|
||||
const token = await loginPda(context)
|
||||
if (!token) return
|
||||
async function runReceiving(context, token, secondaryToken, merchantToken) {
|
||||
if (context.overseasToken) {
|
||||
const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, context.overseasToken)
|
||||
context.addStep(MODULE, '平台海外令牌不能冒充客户PDA账号', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS)
|
||||
context.addStep(MODULE, '平台管理员海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS)
|
||||
}
|
||||
if (merchantToken) {
|
||||
const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, merchantToken)
|
||||
context.addStep(MODULE, '客户海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS)
|
||||
}
|
||||
const before = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/current', undefined, {}, token)
|
||||
const noCurrent = notFound(before)
|
||||
@@ -203,6 +238,11 @@ export async function run(context) {
|
||||
`/api/pda/v1/last-leg/receiving-containers/${encodeURIComponent(containerNo)}/prepare`, prepareBody)
|
||||
context.assert(MODULE, '选择柜号幂等结果一致', prepareReplay.response,
|
||||
success(prepareReplay.response) && prepareReplay.data?.containerNo === containerNo && prepareReplay.data?.status === 'WAIT_SIGN', trace(prepareReplay.response), OPTIONS)
|
||||
if (secondaryToken) {
|
||||
await expected(context, secondaryToken, 'POST', '第二仓库操作员抢占同柜返回409',
|
||||
`/api/pda/v1/last-leg/receiving-containers/${encodeURIComponent(containerNo)}/prepare`,
|
||||
{ idempotencyKey: key(context, 'secondary-prepare') }, conflict)
|
||||
}
|
||||
const other = candidates.data.find((item) => item.containerNo !== containerNo)
|
||||
if (other) await expected(context, token, 'GET', '当前账号跨柜切换锁返回409',
|
||||
`/api/pda/v1/last-leg/receiving-containers/${encodeURIComponent(other.containerNo)}`, undefined, conflict)
|
||||
@@ -268,3 +308,27 @@ export async function run(context) {
|
||||
context.addStep(MODULE, '完成后current返回404正常空态', after, notFound(after), trace(after), OPTIONS)
|
||||
context.createdResources.phase4OverseasPda.finalStatus = completed.data?.status || null
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
context.createdResources.phase4OverseasPda = {
|
||||
marker: context.marker, containerNo: null, customerId: null,
|
||||
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [],
|
||||
cleanup: 'Pallets and locations used by completed inventory are retained as acceptance evidence',
|
||||
}
|
||||
const merchantToken = await loginMerchantForNegativeProbe(context)
|
||||
const primary = await createPicker(context, 1)
|
||||
const secondary = primary?.token ? await createPicker(context, 2) : null
|
||||
for (const picker of [primary, secondary]) if (picker?.membershipId) {
|
||||
context.createdResources.phase4OverseasPda.pickerMembershipIds.push(picker.membershipId)
|
||||
}
|
||||
try {
|
||||
if (!primary?.token || !secondary?.token) {
|
||||
blockerGap(context, '海外PDA双操作员账号', '未能创建并登录两个WAREHOUSE_PICKER平台账号,无法验证全局一柜锁')
|
||||
return
|
||||
}
|
||||
await runReceiving(context, primary.token, secondary.token, merchantToken)
|
||||
} finally {
|
||||
await disablePicker(context, secondary, 2)
|
||||
await disablePicker(context, primary, 1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user