fix: complete October 22 workflow feedback and request failure recovery
This commit is contained in:
@@ -54,16 +54,8 @@ test('successful net debit excludes the original estimate and counts only succes
|
||||
assert.equal(presentation.feeStatus(fee('DUTY', null, 100)), '待核定')
|
||||
})
|
||||
|
||||
test('request recovery preserves content and identity while malformed recovery cannot create a replacement', () => {
|
||||
const original = presentation.buildProcurementCreate(draft(), 'Company', 'fixed-key')
|
||||
assert.deepEqual(normalized(submission.restoreProcurementSubmission(JSON.stringify(original))), normalized(original))
|
||||
assert.throws(() => submission.restoreProcurementSubmission('{}'))
|
||||
assert.throws(() => submission.restoreProcurementSubmission(JSON.stringify({ ...original, currency: 'EUR' })))
|
||||
assert.equal(submission.definitelyRejected({ code: 'TIMEOUT' }), false)
|
||||
assert.equal(submission.definitelyRejected({ status: 500 }), false)
|
||||
assert.equal(submission.definitelyRejected({ status: 409 }), false)
|
||||
assert.equal(submission.definitelyRejected({ status: 400 }), true)
|
||||
assert.equal(submission.procurementRequestKey({ getRandomValues: target => { target.set([1, 2, 3, 4]); return target } }), 'proc-00000001000000020000000300000004')
|
||||
test('new requests get unique keys without restoring stored submissions', () => {
|
||||
assert.notEqual(submission.procurementRequestKey(crypto), submission.procurementRequestKey(crypto))
|
||||
})
|
||||
|
||||
class ApiError extends Error { constructor(message, code, status) { super(message); this.code = code; this.status = status } }
|
||||
@@ -109,18 +101,16 @@ test('detail has one in-flight request and an older response cannot overwrite an
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('timeout retries the exact original body after reload and does not accept changed form data', async () => {
|
||||
const storage = new Map(), calls = []
|
||||
const first = viewHarness({ createPage: true, storage, api: { createProcurement: async payload => { calls.push(normalized(payload)); throw new ApiError('timeout', 'TIMEOUT') } } })
|
||||
test('timeout releases the form and another click submits the edited current values', async () => {
|
||||
const calls = []
|
||||
const harness = viewHarness({ createPage: true, api: { createProcurement: async payload => { calls.push(normalized(payload)); throw new ApiError('timeout', 'TIMEOUT') } } })
|
||||
try {
|
||||
Object.assign(first.state.form, draft()); await first.state.submit(); assert.equal(calls.length, 1); assert.ok(first.state.pending.value)
|
||||
first.state.form.productName = 'changed'; await first.state.submit(); assert.deepEqual(calls[1], calls[0])
|
||||
} finally { first.dispose() }
|
||||
const recovered = viewHarness({ createPage: true, storage, api: { createProcurement: async payload => { calls.push(normalized(payload)); return { order: { orderNo: 'PO-SAME' } } } } })
|
||||
try {
|
||||
assert.equal(recovered.state.form.productName, 'Test product'); await recovered.state.submit(); assert.deepEqual(calls[2], calls[0])
|
||||
assert.equal(storage.size, 0); assert.equal(recovered.pushes[0].params.orderNo, 'PO-SAME')
|
||||
} finally { recovered.dispose() }
|
||||
Object.assign(harness.state.form, draft()); await harness.state.submit()
|
||||
assert.equal(harness.state.submitting.value, false); assert.equal(harness.storage.size, 0)
|
||||
harness.state.form.productName = 'changed'; await harness.state.submit()
|
||||
assert.equal(calls[1].productName, 'changed'); assert.notEqual(calls[0].idempotencyKey, calls[1].idempotencyKey)
|
||||
assert.equal(harness.state.submitting.value, false)
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('submission double-click sends once and definitive validation rejection permits correction', async () => {
|
||||
@@ -129,7 +119,7 @@ test('submission double-click sends once and definitive validation rejection per
|
||||
try {
|
||||
Object.assign(harness.state.form, draft()); const first = harness.state.submit(); await harness.state.submit(); assert.equal(calls, 1)
|
||||
reject(new ApiError('invalid', 'A0422')); await first
|
||||
assert.equal(harness.state.pending.value, null); assert.equal(harness.storage.size, 0)
|
||||
assert.equal(harness.state.submitting.value, false); assert.equal(harness.storage.size, 0)
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
@@ -149,24 +139,18 @@ test('pagination batches one request and switching account clears old rows befor
|
||||
})
|
||||
|
||||
|
||||
test('replay rejection cannot discard an earlier unknown creation outcome', async () => {
|
||||
const calls = []; let attempt = 0
|
||||
const harness = viewHarness({ createPage: true, api: { createProcurement: async payload => {
|
||||
calls.push(normalized(payload)); attempt++
|
||||
if (attempt === 1) throw new ApiError('timeout', 'TIMEOUT')
|
||||
throw new ApiError('permission revoked', 'A0403')
|
||||
} } })
|
||||
test('consecutive timeout and permission failures never persist or freeze a submission', async () => {
|
||||
let attempt = 0
|
||||
const harness = viewHarness({ createPage: true, api: { createProcurement: async () => { throw new ApiError('failed', ++attempt === 1 ? 'TIMEOUT' : 'A0403') } } })
|
||||
try {
|
||||
Object.assign(harness.state.form, draft()); await harness.state.submit()
|
||||
const saved = [...harness.storage.values()][0]; await harness.state.submit()
|
||||
assert.equal([...harness.storage.values()][0], saved); assert.ok(harness.state.pending.value)
|
||||
assert.deepEqual(calls[1], calls[0])
|
||||
Object.assign(harness.state.form, draft()); await harness.state.submit(); await harness.state.submit()
|
||||
assert.equal(attempt, 2); assert.equal(harness.state.submitting.value, false); assert.equal(harness.storage.size, 0)
|
||||
harness.state.form.productName = 'editable'; assert.equal(harness.state.form.productName, 'editable')
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('purchase category is optional while its length remains bounded', () => {
|
||||
const payload = presentation.buildProcurementCreate({ ...draft(), purchaseCategory: ' ' }, 'Company', 'key')
|
||||
assert.equal(payload.purchaseCategory, '')
|
||||
assert.equal(submission.restoreProcurementSubmission(JSON.stringify(payload)).purchaseCategory, '')
|
||||
assert.throws(() => presentation.buildProcurementCreate({ ...draft(), purchaseCategory: 'x'.repeat(129) }, 'Company', 'key'), /品类/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user