优化
This commit is contained in:
@@ -24,7 +24,7 @@ const submission = moduleFrom(readFileSync(new URL('./procurementSubmission.ts',
|
||||
})
|
||||
const normalized = value => JSON.parse(JSON.stringify(value))
|
||||
const draft = () => ({ ...presentation.emptyProcurementDraft(), purchaseCategory: 'Sample', productName: 'Test product', productSpecification: 'A',
|
||||
purchaseQuantity: '3', budgetUnitPrice: '0.29', receivingAddress: '测试省测试市测试街 1 号', contactName: '测试收件人', contactPhone: '13800000000' })
|
||||
productUrl: 'https://example.com/product', purchaseQuantity: '3', budgetUnitPrice: '0.29', receivingAddress: '测试省测试市测试街 1 号', contactName: '测试收件人', contactPhone: '13800000000' })
|
||||
const fee = (purpose, walletStatus, amount) => ({ purpose, walletStatus, amount, currency: 'CNY' })
|
||||
|
||||
test('budget distinguishes unknown from zero, preserves cents, and requires a real telephone/address', () => {
|
||||
@@ -100,7 +100,7 @@ test('procurement required fields validate individually and blank submissions ne
|
||||
const calls = []
|
||||
const harness = viewHarness({ createPage: true, api: { createProcurement: async payload => { calls.push(payload); return { order: { orderNo: 'PO-VALID' } } } } })
|
||||
try {
|
||||
const fields = ['procurementDirection', 'productName', 'purchaseQuantity', 'budgetUnitPrice', 'currency', 'contactName', 'contactPhone', 'receivingAddress']
|
||||
const fields = ['procurementDirection', 'productName', 'productUrl', 'purchaseQuantity', 'budgetUnitPrice', 'currency', 'contactName', 'contactPhone', 'receivingAddress']
|
||||
const source = readFileSync(new URL('./views/ProcurementView.vue', import.meta.url), 'utf8')
|
||||
for (const field of fields) {
|
||||
assert.match(source, new RegExp(`<el-form-item[^>]*prop="${field}"`))
|
||||
@@ -116,6 +116,7 @@ test('procurement required fields validate individually and blank submissions ne
|
||||
await harness.state.submit()
|
||||
assert.equal(calls.length, 1)
|
||||
assert.equal(calls[0].budgetUnitPrice, 0)
|
||||
assert.deepEqual(normalized(harness.pushes), [{ name: 'cross-border-procurement' }])
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
@@ -250,44 +251,16 @@ test('domestic purchase creates an export order and displays the actual origin a
|
||||
})
|
||||
|
||||
|
||||
test('customer confirms with current workflow version and double-click only sends once', async () => {
|
||||
let resolve, calls = []
|
||||
const harness = viewHarness({ detailPage: true, api: { confirmProcurement: (orderNo, payload) => {
|
||||
calls.push({ orderNo, ...payload }); return new Promise(done => { resolve = done })
|
||||
} } })
|
||||
test('unchanged create retries reuse the idempotency key', async () => {
|
||||
const calls = []
|
||||
const harness = viewHarness({ createPage: true, api: { createProcurement: async payload => { calls.push(payload); throw new ApiError('timeout', 'TIMEOUT') } } })
|
||||
try {
|
||||
await flush()
|
||||
const first = harness.state.confirmReceived(); await flush(); await harness.state.confirmReceived()
|
||||
assert.equal(calls.length, 1); assert.equal(calls[0].version, 1); assert.equal(calls[0].orderNo, 'PO-1')
|
||||
assert.ok(calls[0].idempotencyKey)
|
||||
resolve({ orderNo: 'PO-1', version: 2, status: 'CONFIRMED' }); await first
|
||||
assert.equal(harness.state.workflow.value.status, 'CONFIRMED'); assert.equal(harness.state.canConfirm.value, false)
|
||||
Object.assign(harness.state.form, draft()); await harness.state.submit(); await harness.state.submit()
|
||||
assert.equal(calls[0].idempotencyKey, calls[1].idempotencyKey)
|
||||
assert.equal(harness.pushes.length, 0)
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('failed customer confirmation preserves receipt context for recovery', async () => {
|
||||
const harness = viewHarness({ detailPage: true, api: { confirmProcurement: async () => { throw new ApiError('版本已更新,请刷新', 'A0409') } } })
|
||||
try {
|
||||
await flush(); await harness.state.confirmReceived()
|
||||
assert.equal(harness.state.workflow.value.status, 'WAIT_CUSTOMER_CONFIRM')
|
||||
assert.equal(harness.state.confirming.value, false); assert.equal(harness.state.confirmError.value, '版本已更新,请刷新')
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
|
||||
test('unknown create and confirmation results reuse idempotency keys for unchanged retries', async () => {
|
||||
const creates = [], confirms = []
|
||||
const create = viewHarness({ createPage: true, api: { createProcurement: async payload => { creates.push(payload); throw new ApiError('timeout', 'TIMEOUT') } } })
|
||||
const confirm = viewHarness({ detailPage: true, api: { confirmProcurement: async (_, payload) => { confirms.push(payload); throw new ApiError('timeout', 'TIMEOUT') } } })
|
||||
try {
|
||||
Object.assign(create.state.form, draft()); await create.state.submit(); await create.state.submit()
|
||||
assert.equal(creates[0].idempotencyKey, creates[1].idempotencyKey)
|
||||
await flush(); await confirm.state.confirmReceived(); await confirm.state.confirmReceived()
|
||||
assert.equal(confirms[0].idempotencyKey, confirms[1].idempotencyKey)
|
||||
} finally { create.dispose(); confirm.dispose() }
|
||||
})
|
||||
|
||||
|
||||
test('bill-settled manual fees show review results without counting a balance debit', () => {
|
||||
const bill = determination => ({ ...fee('OTHER', 'NOT_REQUIRED', '100'), determination })
|
||||
assert.equal(presentation.feeStatus(bill('PENDING')), '账单结算 · 待审核')
|
||||
@@ -308,15 +281,36 @@ test('insufficient balance and credit blocks procurement creation and preserves
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('customer rejection requires a reason and carries current order version', async () => {
|
||||
const calls = []
|
||||
const harness = viewHarness({ detailPage: true, api: { rejectProcurement: async (orderNo, command) => { calls.push({ orderNo, ...command }); return { orderNo, status: 'RECEIVING', version: 2, rejectionReason: command.reason } } } })
|
||||
test('confirmation and rejection are list actions only while waiting for customer confirmation', async () => {
|
||||
let status = 'WAIT_CUSTOMER_CONFIRM'
|
||||
const harness = viewHarness({ api: { listProcurements: async () => ({ records: [{ orderNo: 'PO-1', status, purchaseQuantity: 10 }], total: 1 }) } })
|
||||
try {
|
||||
await flush(); await harness.state.rejectReceived()
|
||||
assert.equal(calls.length, 1); assert.equal(calls[0].orderNo, 'PO-1'); assert.equal(calls[0].version, 1)
|
||||
assert.equal(calls[0].reason, '照片不清晰'); assert.equal(harness.state.workflow.value.status, 'RECEIVING')
|
||||
await flush()
|
||||
const actions = harness.state.rowActions(harness.state.rows.value[0])
|
||||
assert.deepEqual(normalized(actions.map(action => action.key)), ['detail', 'reject', 'confirm'])
|
||||
actions[1].run(); assert.equal(harness.state.decision.value, 'reject')
|
||||
assert.equal(harness.state.decisionTarget.value.orderNo, 'PO-1')
|
||||
status = 'CONFIRMED'; harness.state.decisionChanged(); await flush()
|
||||
assert.equal(harness.state.decisionTarget.value, null)
|
||||
assert.deepEqual(normalized(harness.state.rowActions(harness.state.rows.value[0]).map(action => action.key)), ['detail'])
|
||||
harness.auth.hasPermission = () => false
|
||||
assert.equal(harness.state.rowActions({ orderNo: 'PO-1', status: 'WAIT_CUSTOMER_CONFIRM' }).length, 1)
|
||||
const source = readFileSync(new URL('./views/ProcurementView.vue', import.meta.url), 'utf8')
|
||||
const detailHeader = source.slice(source.indexOf('<header class="procurement-heading">'), source.indexOf('</header>', source.indexOf('<header class="procurement-heading">')))
|
||||
assert.doesNotMatch(detailHeader, /确认采购结果|驳回/)
|
||||
} finally { harness.dispose() }
|
||||
})
|
||||
|
||||
test('product links must be nonblank and use HTTP or HTTPS before budget reservation', async () => {
|
||||
for (const productUrl of ['', ' ', 'javascript:alert(1)', 'invalid', 'https://' ]) {
|
||||
assert.throws(() => presentation.buildProcurementCreate({ ...draft(), productUrl }, 'Company', 'key'), /商品链接/)
|
||||
}
|
||||
let budgets = 0, creates = 0
|
||||
const harness = viewHarness({ createPage: true, api: { requireOrderBudget: async () => { budgets++ }, createProcurement: async () => { creates++ } } })
|
||||
try {
|
||||
for (const productUrl of ['', ' ']) {
|
||||
Object.assign(harness.state.form, draft(), { productUrl }); await harness.state.submit()
|
||||
}
|
||||
assert.equal(budgets, 0); assert.equal(creates, 0); assert.equal(harness.pushes.length, 0)
|
||||
} finally { harness.dispose() }
|
||||
const blank = viewHarness({ detailPage: true, api: { rejectionReason: ' ', rejectProcurement: async () => { throw new Error('不得发送空原因') } } })
|
||||
try { await flush(); await blank.state.rejectReceived(); assert.match(blank.state.confirmError.value, /请填写驳回原因/) }
|
||||
finally { blank.dispose() }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user