fix: align audit and dispatch workflows and release failed requests
This commit is contained in:
@@ -18,7 +18,7 @@ async function mount(readonly = false, realControls = false) {
|
||||
ElAlert: { props: ['title'], template: '<div>{{ title }}<slot/></div>' }, ApiErrorMeta: true, DataTable: true,
|
||||
} } }); wrappers.push(wrapper); await wrapper.setProps({ modelValue: true }); await flushPromises(); return wrapper
|
||||
}
|
||||
beforeEach(() => { vi.clearAllMocks(); vi.mocked(api.getTripOperationsRecord).mockResolvedValue(record); vi.mocked(api.listTripOperationsRecordHistory).mockResolvedValue({ records: [], total: 0, page: 1, size: 20, pages: 0 }) })
|
||||
beforeEach(() => { vi.resetAllMocks(); vi.mocked(api.getTripOperationsRecord).mockResolvedValue(record); vi.mocked(api.listTripOperationsRecordHistory).mockResolvedValue({ records: [], total: 0, page: 1, size: 20, pages: 0 }) })
|
||||
afterEach(() => wrappers.splice(0).forEach(w => w.unmount()))
|
||||
it.each([[null, 0], [0, null]])('delayed records keep distance %s and fuel %s editable in real number controls', async (distance, fuel) => {
|
||||
let resolve!: (value: api.TripOperationsRecord) => void
|
||||
@@ -71,7 +71,7 @@ it('loads current record on open, history only when selected, and hides writes f
|
||||
wrapper.findComponent({ name: 'ElTabs' }).vm.$emit('update:modelValue', 'history'); await flushPromises()
|
||||
expect(api.listTripOperationsRecordHistory).toHaveBeenCalledExactlyOnceWith('TR1', 1, 20)
|
||||
})
|
||||
it('unknown write retry preserves original key, version and measured values', async () => {
|
||||
it('unknown write releases fields and the next save uses changed measurements', async () => {
|
||||
const wrapper = await mount(); vi.mocked(api.saveTripOperationsRecord).mockRejectedValueOnce(new Error('network'))
|
||||
wrapper.findAllComponents({ name: 'ElInputNumber' })[0]!.vm.$emit('update:modelValue', 12.5); await flushPromises()
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
@@ -79,8 +79,8 @@ it('unknown write retry preserves original key, version and measured values', as
|
||||
expect(original).toMatchObject({ recordVersion: '0', details: { actualDistanceMiles: 12.5, actualFuelLitres: null } })
|
||||
wrapper.findAllComponents({ name: 'ElInputNumber' })[0]!.vm.$emit('update:modelValue', 99)
|
||||
vi.mocked(api.saveTripOperationsRecord).mockResolvedValue({ ...record, recordVersion: '1' })
|
||||
await wrapper.findAll('button').find(b => b.text() === '按原请求重试')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1]).toEqual(original); expect(wrapper.emitted('saved')).toHaveLength(1)
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1].idempotencyKey).not.toBe(original.idempotencyKey); expect(wrapper.emitted('saved')).toHaveLength(1)
|
||||
})
|
||||
it('a failed initial read cannot save a guessed record version', async () => {
|
||||
vi.mocked(api.getTripOperationsRecord).mockRejectedValue(new Error('unavailable')); const wrapper = await mount()
|
||||
@@ -100,29 +100,17 @@ it('uploads optionally without creating records; saving binds only the exact IDs
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
const request = vi.mocked(api.saveTripOperationsRecord).mock.calls[0]![1]
|
||||
expect(request.details.evidenceFileIds).toEqual([evidence.fileId]); expect(request.details).not.toHaveProperty('evidenceFiles')
|
||||
await wrapper.findAll('button').find(b => b.text() === '按原请求重试')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1]).toEqual(request)
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1].details).toEqual(request.details); expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1].idempotencyKey).not.toBe(request.idempotencyKey)
|
||||
})
|
||||
it('a committed upload with lost response never retransmits after reopen or reload and preserves bound evidence', async () => {
|
||||
it('upload failure keeps bound evidence and allows another file selection', async () => {
|
||||
const existing = { ...evidence, fileId: '81', fileName: 'bound.pdf' }
|
||||
vi.mocked(api.getTripOperationsRecord).mockResolvedValue({ ...record, details: { ...record.details, evidenceFiles: [existing] } })
|
||||
let storedFiles = 0
|
||||
vi.mocked(api.uploadTripOperationsEvidence).mockImplementation(async () => { storedFiles++; throw new Error('response lost after storage commit') })
|
||||
vi.mocked(api.uploadTripOperationsEvidence).mockRejectedValueOnce(new Error('response lost')).mockResolvedValueOnce(evidence)
|
||||
const wrapper = await mount(); await choose(wrapper)
|
||||
expect(storedFiles).toBe(1); expect(wrapper.text()).toContain('可能已上传但尚未绑定'); expect(wrapper.text()).toContain('bound.pdf')
|
||||
expect(wrapper.text()).not.toContain('重试原文件'); expect(api.saveTripOperationsRecord).not.toHaveBeenCalled()
|
||||
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
|
||||
expect(wrapper.text()).toContain('meter.pdf'); expect(wrapper.text()).toContain('可能已上传但尚未绑定')
|
||||
expect(wrapper.find('[data-testid="evidence-input"]').attributes('disabled')).toBeDefined()
|
||||
await wrapper.findAll('button').find(b => b.text() === '重新读取')!.trigger('click'); await flushPromises()
|
||||
await choose(wrapper)
|
||||
expect(storedFiles).toBe(1); expect(api.uploadTripOperationsEvidence).toHaveBeenCalledTimes(1); expect(api.saveTripOperationsRecord).not.toHaveBeenCalled()
|
||||
await wrapper.findAll('button').find(b => b.text() === '暂不绑定此附件')!.trigger('click'); await flushPromises()
|
||||
vi.mocked(api.saveTripOperationsRecord).mockResolvedValue({ ...record, recordVersion: '1' })
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[0]![1].details.evidenceFileIds).toEqual(['81'])
|
||||
expect(storedFiles).toBe(1)
|
||||
const refreshed = await mount(); expect(refreshed.text()).toContain('bound.pdf'); expect(storedFiles).toBe(1)
|
||||
expect(wrapper.text()).toContain('bound.pdf'); expect(wrapper.find('[data-testid="evidence-input"]').attributes('disabled')).toBeUndefined()
|
||||
await choose(wrapper); expect(api.uploadTripOperationsEvidence).toHaveBeenCalledTimes(2); expect(wrapper.text()).toContain('meter.pdf')
|
||||
expect(api.saveTripOperationsRecord).not.toHaveBeenCalled()
|
||||
})
|
||||
it('explicit upload rejection allows corrected file selection without creating a record', async () => {
|
||||
vi.mocked(api.uploadTripOperationsEvidence).mockRejectedValueOnce(new ApiResponseError('文件格式不符', 'A0400', 400)).mockResolvedValueOnce(evidence)
|
||||
@@ -179,12 +167,12 @@ it('closing and reopening the same trip cannot start a concurrent save retry', a
|
||||
const wrapper = await mount(); await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click')
|
||||
const original = vi.mocked(api.saveTripOperationsRecord).mock.calls[0]![1]
|
||||
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
|
||||
const retry = wrapper.findAll('button').find(b => b.text() === '按原请求重试')!
|
||||
const retry = wrapper.findAll('button').find(b => b.text() === '保存记录')!
|
||||
expect(retry.attributes('disabled')).toBeDefined(); await retry.trigger('click'); expect(api.saveTripOperationsRecord).toHaveBeenCalledTimes(1)
|
||||
reject(new Error('unknown')); await flushPromises()
|
||||
vi.mocked(api.saveTripOperationsRecord).mockResolvedValue({ ...record, recordVersion: '1' })
|
||||
await wrapper.findAll('button').find(b => b.text() === '按原请求重试')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1]).toEqual(original)
|
||||
await wrapper.findAll('button').find(b => b.text() === '保存记录')!.trigger('click'); await flushPromises()
|
||||
expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[1]![1].idempotencyKey).not.toBe(original.idempotencyKey)
|
||||
})
|
||||
it('closing and reopening the same trip cannot duplicate an in-flight upload', async () => {
|
||||
let done!: (value: api.TripOperationsEvidence) => void
|
||||
|
||||
Reference in New Issue
Block a user