test: align phase3 and overseas business acceptance
This commit is contained in:
@@ -20,6 +20,7 @@ function fixture(options = {}) {
|
||||
const state = {
|
||||
warehouseStatus: 'ACTIVE', purchase: null, purchaseHash: '', truck: null, truckHash: '', looseboard: null, looseboardHash: '',
|
||||
purchaseHistories: [], truckHistories: [], looseboardHistories: [],
|
||||
purchaseReviews: new Map(), truckReviews: new Map(), truckStatuses: new Map(), looseboardStatuses: new Map(),
|
||||
}
|
||||
const activeCustomers = options.activeCustomers ?? [{ id: '10001', tenantName: '客户甲', status: 'ACTIVE' }]
|
||||
const warehouseOptions = options.warehouseOptions ?? [
|
||||
@@ -71,11 +72,19 @@ function fixture(options = {}) {
|
||||
if (requestPath.includes('/warehouse-options')) return response(warehouseOptions)
|
||||
if (requestPath.includes('/overseas-procurements?') && method === 'GET') return response(page(state.purchase ? [state.purchase] : []))
|
||||
if (requestPath.includes('/overseas-procurements/OP1/review?') && method === 'POST') {
|
||||
assert.equal(Number.isInteger(Number(body.version)), true)
|
||||
assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true)
|
||||
const hash = JSON.stringify(body)
|
||||
const replay = state.purchaseReviews.get(body.idempotencyKey)
|
||||
if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409')
|
||||
if (state.purchase.status !== 'PENDING_REVIEW') return response(null, 'A0409')
|
||||
if (Number(body.version) !== state.purchase.version) return response(null, 'A0409')
|
||||
const next = body.decision === 'REJECT' ? 'REJECTED' : 'APPROVED'
|
||||
state.purchase = { ...state.purchase, status: next, version: state.purchase.version + 1 }
|
||||
state.purchaseHistories.push({ operationType: 'REVIEW', fromStatus: 'PENDING_REVIEW', toStatus: next, remark: body.remark })
|
||||
return response(purchaseDetail())
|
||||
const result = structuredClone(purchaseDetail())
|
||||
state.purchaseReviews.set(body.idempotencyKey, { hash, result })
|
||||
return response(result)
|
||||
}
|
||||
if (requestPath.includes('/overseas-procurements/OP1?') && method === 'GET') return response(purchaseDetail())
|
||||
if (requestPath.includes('/overseas-procurements/OP1?') && method === 'PATCH') {
|
||||
@@ -102,15 +111,37 @@ function fixture(options = {}) {
|
||||
return response(truckDetail())
|
||||
}
|
||||
if (requestPath.includes('/us-truck-orders?') && method === 'GET') return response(page(state.truck ? [state.truck] : []))
|
||||
if (requestPath.includes('/us-truck-orders/TK1/status?') && method === 'PATCH') {
|
||||
if (Number(body.version) !== state.truck.version) return response(null, 'A0409')
|
||||
const allowed = (state.truck.status === 'PENDING_REVIEW' && ['WAITING_INBOUND', 'CANCELLED'].includes(body.status))
|
||||
|| (state.truck.status === 'WAITING_INBOUND' && body.status === 'CANCELLED')
|
||||
if (!allowed) return response(null, 'A0409')
|
||||
if (requestPath.includes('/us-truck-orders/TK1/review?') && method === 'POST') {
|
||||
assert.equal(Number.isInteger(Number(body.version)), true)
|
||||
assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true)
|
||||
const hash = JSON.stringify(body)
|
||||
const replay = state.truckReviews.get(body.idempotencyKey)
|
||||
if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409')
|
||||
if (state.truck.status !== 'PENDING_REVIEW' || Number(body.version) !== state.truck.version) {
|
||||
return response(null, 'A0409')
|
||||
}
|
||||
const next = body.decision === 'APPROVE' ? 'WAITING_INBOUND' : 'REJECTED'
|
||||
const from = state.truck.status
|
||||
state.truck = { ...state.truck, status: body.status, version: state.truck.version + 1 }
|
||||
state.truckHistories.push({ fromStatus: from, toStatus: body.status, remark: body.remark })
|
||||
return response(truckDetail())
|
||||
state.truck = { ...state.truck, status: next, version: state.truck.version + 1 }
|
||||
state.truckHistories.push({ fromStatus: from, toStatus: next, remark: body.remark })
|
||||
const result = structuredClone(truckDetail())
|
||||
state.truckReviews.set(body.idempotencyKey, { hash, result })
|
||||
return response(result)
|
||||
}
|
||||
if (requestPath.includes('/us-truck-orders/TK1/status?') && method === 'PATCH') {
|
||||
assert.equal(body.status, 'CANCELLED')
|
||||
assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true)
|
||||
const hash = JSON.stringify(body)
|
||||
const replay = state.truckStatuses.get(body.idempotencyKey)
|
||||
if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409')
|
||||
if (Number(body.version) !== state.truck.version) return response(null, 'A0409')
|
||||
if (!['PENDING_REVIEW', 'WAITING_INBOUND'].includes(state.truck.status)) return response(null, 'A0409')
|
||||
const from = state.truck.status
|
||||
state.truck = { ...state.truck, status: 'CANCELLED', version: state.truck.version + 1 }
|
||||
state.truckHistories.push({ fromStatus: from, toStatus: 'CANCELLED', remark: body.remark })
|
||||
const result = structuredClone(truckDetail())
|
||||
state.truckStatuses.set(body.idempotencyKey, { hash, result })
|
||||
return response(result)
|
||||
}
|
||||
if (requestPath.includes('/us-truck-orders/TK1?') && method === 'GET') return response(truckDetail())
|
||||
if (requestPath.includes('/us-truck-orders/TK1?') && method === 'PATCH') {
|
||||
@@ -130,6 +161,10 @@ function fixture(options = {}) {
|
||||
}
|
||||
if (requestPath.includes('/local-looseboard-orders?') && method === 'GET') return response(page(state.looseboard ? [state.looseboard] : []))
|
||||
if (requestPath.includes('/local-looseboard-orders/LB1/status?') && method === 'PATCH') {
|
||||
assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true)
|
||||
const hash = JSON.stringify(body)
|
||||
const replay = state.looseboardStatuses.get(body.idempotencyKey)
|
||||
if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409')
|
||||
if (Number(body.version) !== state.looseboard.version) return response(null, 'A0409')
|
||||
const allowed = (state.looseboard.status === 'PENDING_ACCEPTANCE' && ['DISPATCHED', 'CANCELLED'].includes(body.status))
|
||||
|| (state.looseboard.status === 'DISPATCHED' && body.status === 'CANCELLED')
|
||||
@@ -137,7 +172,9 @@ function fixture(options = {}) {
|
||||
const from = state.looseboard.status
|
||||
state.looseboard = { ...state.looseboard, status: body.status, version: state.looseboard.version + 1 }
|
||||
state.looseboardHistories.push({ operationType: 'STATUS', fromStatus: from, toStatus: body.status, remark: body.remark })
|
||||
return response(looseboardDetail())
|
||||
const result = structuredClone(looseboardDetail())
|
||||
state.looseboardStatuses.set(body.idempotencyKey, { hash, result })
|
||||
return response(result)
|
||||
}
|
||||
if (requestPath.includes('/local-looseboard-orders/LB1?') && method === 'GET') return response(looseboardDetail())
|
||||
if (requestPath.includes('/local-looseboard-orders/LB1?') && method === 'PUT') {
|
||||
@@ -189,20 +226,41 @@ test('complete procurement truck and looseboard lifecycles pass with legal marke
|
||||
|
||||
const procurementWrites = context.calls.filter((call) => call.path.includes('/overseas-procurements') && call.method !== 'GET')
|
||||
assert.deepEqual(procurementWrites.map((call) => call.method),
|
||||
['POST', 'POST', 'POST', 'PATCH', 'PATCH', 'PATCH', 'PATCH', 'POST', 'POST'])
|
||||
['POST', 'POST', 'POST', 'PATCH', 'PATCH', 'PATCH', 'PATCH', 'POST', 'POST', 'POST', 'POST'])
|
||||
assert.equal(procurementWrites.filter((call) => call.method === 'PATCH').length, 4)
|
||||
assert.ok(procurementWrites.some((call) => call.method === 'PATCH' && call.body.productSpecification === null && call.body.productUrl === ''))
|
||||
const procurementReviews = procurementWrites.filter((call) => call.path.includes('/review?'))
|
||||
assert.equal(procurementReviews.length, 4)
|
||||
assert.ok(procurementReviews.every((call) => Number.isInteger(Number(call.body.version))
|
||||
&& typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0))
|
||||
assert.equal(new Set(procurementReviews.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1)
|
||||
assert.notEqual(procurementReviews[3].body.idempotencyKey, procurementReviews[0].body.idempotencyKey)
|
||||
|
||||
const truckCalls = context.calls.filter((call) => call.path.includes('/us-truck-orders'))
|
||||
assert.equal(truckCalls.filter((call) => call.method === 'POST').length, 3)
|
||||
assert.equal(truckCalls.filter((call) => call.method === 'POST').length, 7)
|
||||
assert.equal(truckCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1)
|
||||
assert.equal(truckCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?')).length, 3)
|
||||
const truckReviews = truckCalls.filter((call) => call.method === 'POST' && call.path.includes('/review?'))
|
||||
assert.equal(truckReviews.length, 4)
|
||||
assert.ok(truckReviews.every((call) => Number.isInteger(Number(call.body.version))
|
||||
&& typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0))
|
||||
assert.equal(new Set(truckReviews.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1)
|
||||
assert.notEqual(truckReviews[3].body.idempotencyKey, truckReviews[0].body.idempotencyKey)
|
||||
const truckStatuses = truckCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?'))
|
||||
assert.equal(truckStatuses.length, 1)
|
||||
assert.ok(truckStatuses.every((call) => call.body.status === 'CANCELLED'
|
||||
&& typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0))
|
||||
|
||||
const looseboardCalls = context.calls.filter((call) => call.path.includes('/local-looseboard-orders'))
|
||||
assert.equal(looseboardCalls.filter((call) => call.method === 'POST').length, 3)
|
||||
assert.equal(looseboardCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1)
|
||||
assert.equal(looseboardCalls.filter((call) => call.method === 'PUT').length, 2)
|
||||
assert.equal(looseboardCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?')).length, 3)
|
||||
const looseboardStatuses = looseboardCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?'))
|
||||
assert.equal(looseboardStatuses.length, 5)
|
||||
assert.ok(looseboardStatuses.every((call) => typeof call.body.idempotencyKey === 'string'
|
||||
&& call.body.idempotencyKey.length > 0))
|
||||
assert.equal(new Set(looseboardStatuses.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1)
|
||||
assert.equal(new Set(looseboardStatuses.slice(3).map((call) => call.body.idempotencyKey)).size, 2)
|
||||
assert.ok(looseboardStatuses.slice(3).every((call) => call.body.idempotencyKey !== looseboardStatuses[0].body.idempotencyKey))
|
||||
|
||||
const registry = context.createdResources.phase4OverseasBusiness
|
||||
assert.equal(registry.purchase.finalStatus, 'REJECTED')
|
||||
@@ -265,5 +323,6 @@ test('contract locks candidates three-state idempotency histories legal terminal
|
||||
'/api/merchant/v1/last-leg/overseas-procurements', '显式 null 与空串清空', '字段缺失不修改',
|
||||
'同幂等键同载荷精确重放', '同幂等键异载荷返回冲突', '旧 version 返回 A0409',
|
||||
'状态正向推进', '状态逆向回退返回冲突', '流水覆盖', "status: 'CANCELLED'",
|
||||
'/review', 'truck-review-approve', 'looseboard-status-dispatched',
|
||||
'requestId=', 'traceId=', 'createdResources.phase4OverseasBusiness']) assert.ok(source.includes(token), token)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user