test(order): verify warehouse faults and await recovery projection

This commit is contained in:
2026-10-04 00:04:36 +08:00
parent 3f6083ff02
commit e8cf5e70ba
8 changed files with 229 additions and 51 deletions
@@ -6,7 +6,7 @@ import { fixtureInboundCode, orderPersistenceFacts, requestedOrderDestinationTyp
buildOrderDependencyFaultPlan, assertOrderDependencyFaultObservation } from './orders.mjs'
function dependencyFixture() {
const detail = id => ({order:{id,orderNo:`FL${id}`,tenantId:'20',status:'DRAFT',version:4},
const detail = id => ({order:{id,orderNo:`FL${id}`,tenantId:'20',receivingWarehouseId:'30',status:'DRAFT',version:4},
cargoes:[{skuNo:'current'}],parties:[],internalNotes:[],statusHistory:[]})
return {marker:'CURRENT-RUN',customerId:'20',registeredCustomerId:'20',merchant:detail('101'),platform:detail('102')}
}
@@ -20,23 +20,28 @@ function dependencyObservation(plan, fixture, dependency, role) {
test('dependency plan binds two new-customer drafts and only named Order upstream properties; it performs no runtime actions',()=>{
const fixture=dependencyFixture(),plan=buildOrderDependencyFaultPlan(fixture)
assert.equal(plan.runtimeChangesExecuted,false)
assert.deepEqual(plan.phases.map(row=>row.urlProperty),['baiyue.clients.iam.url','baiyue.clients.finance.url'])
assert.deepEqual(plan.phases.map(row=>row.urlProperty),['baiyue.clients.iam.url','baiyue.clients.finance.url','baiyue.clients.warehouse.url'])
assert.equal(plan.receivingWarehouseId,'30')
assert.equal(plan.phases[2].targetPath,'/internal/warehouses/0/30/active')
assert.equal(plan.commands.merchant.body.expectedVersion,4)
fixture.merchant.order.version=9
assert.equal(plan.commands.merchant.body.expectedVersion,4)
assert.ok(Object.isFrozen(plan.commands.merchant.body))
for(const change of [f=>f.customerId='0',f=>f.registeredCustomerId='21',f=>f.platform.order.id='101',f=>f.merchant.order.status='SUBMITTED',f=>delete f.merchant.cargoes]) {
for(const change of [f=>f.customerId='0',f=>f.registeredCustomerId='21',f=>f.platform.order.id='101',f=>f.merchant.order.status='SUBMITTED',f=>delete f.merchant.cargoes,f=>f.platform.order.receivingWarehouseId='31',f=>delete f.merchant.order.receivingWarehouseId]) {
const value=dependencyFixture();change(value);assert.throws(()=>buildOrderDependencyFaultPlan(value))
}
})
test('all four dependency-role observations require matching authenticated 503 injection and actual Order S0503 with zero drift',()=>{
test('all six dependency-role observations require matching authenticated 503 injection and actual Order S0503 with zero drift',()=>{
const fixture=dependencyFixture(),plan=buildOrderDependencyFaultPlan(fixture)
for(const dependency of ['IAM','FINANCE'])for(const role of ['merchant','platform']) {
for(const dependency of ['IAM','FINANCE','WAREHOUSE'])for(const role of ['merchant','platform']) {
const evidence=dependencyObservation(plan,fixture,dependency,role)
assert.equal(assertOrderDependencyFaultObservation(plan,dependency,role,evidence).verified,true)
const wrapped=structuredClone(evidence)
wrapped.response.body.message=plan.phases.find(row=>row.dependency===dependency).expectedMessages[1]
assert.equal(assertOrderDependencyFaultObservation(plan,dependency,role,wrapped).verified,true)
const alternative=plan.phases.find(row=>row.dependency===dependency).expectedMessages[1]
if(alternative) {
const wrapped=structuredClone(evidence)
wrapped.response.body.message=alternative
assert.equal(assertOrderDependencyFaultObservation(plan,dependency,role,wrapped).verified,true)
}
for(const mutate of [
value=>value.response.http=503,value=>value.response.body.code='A0403',value=>value.response.body.message='资格未审核',
value=>value.request.body.expectedVersion=5,value=>value.request.headers['Idempotency-Key']='new-key',
+7 -1
View File
@@ -260,12 +260,18 @@ export function buildOrderDependencyFaultPlan(fixture) {
initialOrderSha256:createHash('sha256').update(orderPersistenceFacts(detail)).digest('hex') }
}
if (commands.merchant.orderId === commands.platform.orderId) throw new Error('Use two independent drafts for same-key recovery')
return { marker:fixture.marker, customerId, runtimeChangesExecuted:false, commands,
const receivingWarehouseId = id(fixture.merchant.order.receivingWarehouseId)
if (!/^[1-9]\d*$/.test(receivingWarehouseId) || receivingWarehouseId !== id(fixture.platform.order.receivingWarehouseId)) {
throw new Error('Dependency drafts must reference the same valid public receiving warehouse')
}
return { marker:fixture.marker, customerId, receivingWarehouseId, runtimeChangesExecuted:false, commands,
phases:[
{ dependency:'IAM', urlProperty:'baiyue.clients.iam.url', targetMethod:'GET', targetPath:`/internal/iam/tenants/${customerId}/order-eligibility`,
faultHttp:503, expectedHttp:200, expectedCode:'S0503', expectedMessages:['IAM eligibility service is unavailable','IAM 下单资格响应无效'] },
{ dependency:'FINANCE', urlProperty:'baiyue.clients.finance.url', targetMethod:'GET', targetPath:`/internal/finance/v1/wallet/accounts/${customerId}`,
faultHttp:503, expectedHttp:200, expectedCode:'S0503', expectedMessages:['Finance account service is unavailable','Finance account response is invalid'] },
{ dependency:'WAREHOUSE', urlProperty:'baiyue.clients.warehouse.url', targetMethod:'GET', targetPath:`/internal/warehouses/0/${receivingWarehouseId}/active`,
faultHttp:503, expectedHttp:200, expectedCode:'S0503', expectedMessages:['主数据服务暂时不可用'] },
],
recovery:'Restore original Order image/config/network identity; submit each original frozen command once, then replay it; require one submission history and no duplicate Firstleg facts.' }
}