test: verify fee void ACK loss with original event recovery
This commit is contained in:
@@ -4,6 +4,8 @@ import { readFileSync } from 'node:fs'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
import { assertTestDeliveryFaultProof, deliveryFaultScopeKeys } from './test-delivery-fault-proof.mjs'
|
||||
import { assertTestFeeVoidFaultProof, feeVoidScopeKeys, feeSourceTypes } from './test-fee-void-fault-proof.mjs'
|
||||
export { assertTestFeeVoidFaultProof } from './test-fee-void-fault-proof.mjs'
|
||||
export { assertTestDeliveryFaultProof } from './test-delivery-fault-proof.mjs'
|
||||
import { validateEvidenceConfig } from './fba-core-regression-evidence.mjs'
|
||||
|
||||
@@ -19,10 +21,12 @@ export function renderTestUnloadFaultAction(action, scope, agent) {
|
||||
return renderAction(action,scope,agent,'UNLOAD')
|
||||
}
|
||||
function renderAction(action,scope,agent,direction) {
|
||||
const keys=direction==='DELIVERY'?deliveryFaultScopeKeys:direction==='UNLOAD'?['marker','customerId','containerBookingId']:['marker','customerId','loadingPlanId','containerBookingId']
|
||||
if (!['prepare', 'activate', 'proxy-ready', 'health', 'status', 'recover', 'proof', 'restore', 'finish'].includes(action)
|
||||
const keys=direction==='FEE_VOID'?feeVoidScopeKeys:direction==='DELIVERY'?deliveryFaultScopeKeys:direction==='UNLOAD'?['marker','customerId','containerBookingId']:['marker','customerId','loadingPlanId','containerBookingId']
|
||||
if (!(action==='preflight'&&direction==='FEE_VOID')&&!['prepare', 'activate', 'proxy-ready', 'health', 'status', 'recover', 'proof', 'restore', 'finish'].includes(action)
|
||||
|| !/^[A-Za-z0-9_.-]{1,128}$/.test(agent) || !/^[A-Za-z0-9_-]{1,96}$/.test(scope?.marker || '')
|
||||
|| keys.filter(key=>key!=='marker').some(key => !/^[1-9][0-9]{0,19}$/.test(scope?.[key] || ''))
|
||||
|| keys.filter(key=>!['marker','sourceType'].includes(key)).some(key => !/^[1-9][0-9]{0,19}$/.test(scope?.[key] || ''))
|
||||
|| (direction==='FEE_VOID'&&(!feeSourceTypes.includes(scope?.sourceType)||Number(scope?.sourceVersion)>2147483647))
|
||||
|| (direction==='FEE_VOID'&&keys.some(key=>typeof scope?.[key]!=='string'))
|
||||
|| (direction!=='LOADING'&&Object.keys(scope).some(key=>!keys.includes(key)))) {
|
||||
throw Error('Invalid TEST fault action or scope')
|
||||
}
|
||||
@@ -37,6 +41,9 @@ export function createTestFaultExecutor(env, fetcher = globalThis.fetch) {
|
||||
export function createTestDeliveryFaultExecutor(env, fetcher = globalThis.fetch) {
|
||||
return createExecutor(env,fetcher,'DELIVERY')
|
||||
}
|
||||
export function createTestFeeVoidFaultExecutor(env, fetcher = globalThis.fetch) {
|
||||
return createExecutor(env,fetcher,'FEE_VOID')
|
||||
}
|
||||
export function createTestUnloadFaultExecutor(env, fetcher = globalThis.fetch) {
|
||||
return createExecutor(env,fetcher,'UNLOAD')
|
||||
}
|
||||
@@ -93,7 +100,7 @@ export function assertTestUnloadFaultProof(proof,scope) {
|
||||
function createExecutor(env,fetcher,direction) {
|
||||
validateEvidenceConfig(env)
|
||||
if (env.CHONGCHONG_ENV !== 'test') throw Error('TEST fault requires CHONGCHONG_ENV=test')
|
||||
let scope = null, finished = false, preparedEvidence=null, restoration=null
|
||||
let scope = null, finished = false, preparedEvidence=null, restoration=null, preparationAttempted=false, ready=false
|
||||
async function action(name) {
|
||||
const script = renderAction(name, scope, env.FBA_CORE_JENKINS_AGENT,direction)
|
||||
const response = await fetcher(new URL('scriptText', env.JENKINS_URL.replace(/\/?$/, '/')), {
|
||||
@@ -118,6 +125,7 @@ function createExecutor(env,fetcher,direction) {
|
||||
throw Error('TEST fault observation timed out: ' + name)
|
||||
}
|
||||
async function cleanup() {
|
||||
if(direction==='FEE_VOID'&&!preparationAttempted)return {attempted:false}
|
||||
if (!scope || finished) return restoration
|
||||
await action('restore')
|
||||
await wait('health', row => row.healthy)
|
||||
@@ -127,7 +135,7 @@ function createExecutor(env,fetcher,direction) {
|
||||
&&restoration?.restored===true&&restoration.healthy===true&&restoration.proxyStopped===true
|
||||
&&restoration.producerName==='firstleg-test-lastleg'&&restoration.originalImageId===preparedEvidence?.originalImageId
|
||||
&&restoration.currentImageId===preparedEvidence?.originalImageId&&restoration.originalFingerprint===preparedEvidence?.originalFingerprint
|
||||
&&restoration.currentFingerprint===preparedEvidence?.originalFingerprint))throw Error((direction==='DELIVERY'?'Delivery':'Unload')+' restoration evidence mismatch')
|
||||
&&restoration.currentFingerprint===preparedEvidence?.originalFingerprint))throw Error((direction==='FEE_VOID'?'Fee void':direction==='DELIVERY'?'Delivery':'Unload')+' restoration evidence mismatch')
|
||||
finished = true
|
||||
return restoration
|
||||
}
|
||||
@@ -135,15 +143,19 @@ function createExecutor(env,fetcher,direction) {
|
||||
async prepare(next) {
|
||||
if (scope) {
|
||||
if(direction!=='LOADING'&&!isDeepStrictEqual(scope,next))throw Error('TEST unload fault scope drift')
|
||||
if(direction==='FEE_VOID'&&!ready)throw Error('Fee void preparation did not complete')
|
||||
return // Pin only the first actual current-run plan; other events pass through.
|
||||
}
|
||||
if(direction!=='LOADING')renderAction('prepare',next,env.FBA_CORE_JENKINS_AGENT,direction)
|
||||
scope = direction!=='LOADING'?{...next}:{ marker: next.marker, customerId: next.customerId,
|
||||
loadingPlanId: next.loadingPlanId, containerBookingId: next.containerBookingId }
|
||||
if(direction==='FEE_VOID')await wait('preflight', row=>row.ready===true,60)
|
||||
preparationAttempted=true
|
||||
preparedEvidence=await action('prepare')
|
||||
await wait('proxy-ready', row => row.ready, 10, true)
|
||||
await action('activate')
|
||||
await wait('health', row => row.healthy)
|
||||
ready=true
|
||||
},
|
||||
async readProof(expected) {
|
||||
if (!scope || (direction!=='LOADING'?!isDeepStrictEqual(scope,expected):Object.keys(scope).some(key => expected?.[key] !== scope[key]))) throw Error('TEST fault scope drift')
|
||||
@@ -154,6 +166,10 @@ function createExecutor(env,fetcher,direction) {
|
||||
const proof = await action('proof')
|
||||
if(direction==='UNLOAD')assertTestUnloadFaultProof(proof,scope)
|
||||
if(direction==='DELIVERY')assertTestDeliveryFaultProof(proof,scope)
|
||||
if(direction==='FEE_VOID') {
|
||||
if(!isDeepStrictEqual(proof.prerequisite,preparedEvidence?.prerequisite))throw Error('Fee prerequisite evidence drift')
|
||||
assertTestFeeVoidFaultProof(proof,scope)
|
||||
}
|
||||
await cleanup()
|
||||
return { ...proof, restored: true, ...(direction!=='LOADING'?{restoration}:{}) }
|
||||
} finally { if(direction!=='LOADING')await cleanup() }
|
||||
|
||||
Reference in New Issue
Block a user