test(acceptance): recover completed-plan rejection probe after verified connection loss
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { replayCompletedCommand } from './phase3-loading-finalize.mjs'
|
||||
import { replayCompletedCommand, completedPlanAssignmentProbe } from './phase3-loading-finalize.mjs'
|
||||
|
||||
test('completed-command replay retries one connection failure and keeps its diagnostic', async () => {
|
||||
const failure = { http: 0, raw: 'fetch failed' }
|
||||
@@ -20,3 +20,47 @@ test('business failures are never retried; a second connection failure remains f
|
||||
assert.equal(calls, response.http === 0 ? 2 : 1)
|
||||
}
|
||||
})
|
||||
|
||||
function assignmentFixture(change = false) {
|
||||
const baseline = { status: 'COMPLETED', version: 7, items: [{ id: '11', packageCount: 2 }] }
|
||||
const reads = [], writes = []
|
||||
const context = { marker: 'batch', createdResources: {}, async request(method, path) {
|
||||
reads.push({ method, path }); return { http: 200, body: { code: '200', data: change ? { ...baseline, version: 8 } : structuredClone(baseline) } }
|
||||
} }
|
||||
const input = { planPath: '/plans/1', baseline,
|
||||
body: { inboundId: '10', inboundDetailId: '11', packageCount: 1, expectedVersion: 7, operationId: 'batch-negative' } }
|
||||
const send = async (_context, method, path, body) => {
|
||||
writes.push({ method, path, body: structuredClone(body) })
|
||||
return writes.length === 1 ? { http: 0, raw: 'fetch failed', transportError: { code: 'ECONNRESET' } }
|
||||
: { http: 200, body: { code: 'A0409' } }
|
||||
}
|
||||
return { baseline, context, input, reads, writes, send }
|
||||
}
|
||||
|
||||
test('completed plan rejection probe checks unchanged authoritative state before exact original replay and retains first transport evidence', async () => {
|
||||
const f = assignmentFixture()
|
||||
const result = await completedPlanAssignmentProbe(f.context, f.input, f.send)
|
||||
assert.equal(result.body.code, 'A0409'); assert.equal(f.writes.length, 2)
|
||||
assert.deepEqual(f.writes[0], f.writes[1]); assert.equal(f.reads.length, 2)
|
||||
const proof = f.context.createdResources.completedPlanAssignmentProbes[0]
|
||||
assert.equal(proof.attempts[0].transportError.code, 'ECONNRESET')
|
||||
assert.equal(proof.status, 'VERIFIED_REJECTION_AFTER_TRANSPORT_RECOVERY')
|
||||
})
|
||||
|
||||
test('changed completed plan after lost response stops without any second write', async () => {
|
||||
const f = assignmentFixture(true)
|
||||
await assert.rejects(completedPlanAssignmentProbe(f.context, f.input, f.send), /禁止重放/)
|
||||
assert.equal(f.writes.length, 1)
|
||||
assert.equal(f.context.createdResources.completedPlanAssignmentProbes[0].status, 'UNKNOWN_RESULT_STATE_CHANGED')
|
||||
})
|
||||
|
||||
test('completed plan rejection recovery never retries business rejection, server error or permits an ordinary active plan', async () => {
|
||||
for (const response of [{ http: 200, body: { code: 'A0409' } }, { http: 503 }, { http: 0 }]) {
|
||||
const f = assignmentFixture(); let count = 0
|
||||
await completedPlanAssignmentProbe(f.context, f.input, async () => { count++; return response })
|
||||
assert.equal(count, response.http === 0 ? 2 : 1)
|
||||
}
|
||||
const f = assignmentFixture(); f.input.baseline.status = 'ACTIVE'
|
||||
await assert.rejects(completedPlanAssignmentProbe(f.context, f.input, f.send), /不可变命令/)
|
||||
assert.equal(f.writes.length, 0)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user